|
1 | | -use std::{str::FromStr, sync::Arc}; |
| 1 | +use std::{collections::HashMap, str::FromStr, sync::Arc}; |
2 | 2 |
|
3 | 3 | use alloy::{ |
4 | 4 | rpc::types::{state::AccountOverride, Block, TransactionRequest}, |
@@ -27,6 +27,17 @@ use tycho_simulation::{ |
27 | 27 |
|
28 | 28 | use crate::{execution::tenderly::OverwriteMetadata, RPCTools}; |
29 | 29 |
|
| 30 | +const USER_ADDR: &str = "0xf847a638E44186F3287ee9F8cAF73FF4d4B80784"; |
| 31 | + |
| 32 | +/// Contains the detected storage slots for a token. |
| 33 | +#[derive(Debug, Clone, Default)] |
| 34 | +pub struct TokenSlots { |
| 35 | + pub balance_storage_addr: Vec<u8>, |
| 36 | + pub balance_slot: Vec<u8>, |
| 37 | + pub allowance_storage_addr: Vec<u8>, |
| 38 | + pub allowance_slot: Vec<u8>, |
| 39 | +} |
| 40 | + |
30 | 41 | pub fn encode_swap( |
31 | 42 | component: &ProtocolComponent, |
32 | 43 | state: Arc<dyn ProtocolSim>, |
@@ -72,8 +83,7 @@ fn create_solution( |
72 | 83 | amount_in: BigUint, |
73 | 84 | expected_amount_out: BigUint, |
74 | 85 | ) -> miette::Result<Solution> { |
75 | | - let user_address = |
76 | | - Bytes::from_str("0xf847a638E44186F3287ee9F8cAF73FF4d4B80784").into_diagnostic()?; |
| 86 | + let user_address = Bytes::from_str(USER_ADDR).into_diagnostic()?; |
77 | 87 |
|
78 | 88 | // Prepare data to encode. First we need to create a swap object |
79 | 89 | let simple_swap = |
@@ -162,71 +172,107 @@ fn encode_input(selector: &str, mut encoded_args: Vec<u8>) -> Vec<u8> { |
162 | 172 | call_data |
163 | 173 | } |
164 | 174 |
|
165 | | -/// Set up all state overrides needed for simulation. |
| 175 | +/// Detects balance and allowance storage slots for all given tokens in a single batch operation. |
166 | 176 | /// |
167 | | -/// This includes balance overrides and allowance overrides of the sell token for the sender. |
168 | | -/// Returns both the overwrites and metadata for human-readable logging. |
169 | | -pub(crate) async fn setup_user_overwrites( |
| 177 | +/// Returns a mapping from token address to their detected storage slots. |
| 178 | +/// This function should be called once per block with all tokens of interest to optimize RPC calls. |
| 179 | +/// Tokens that fail slot detection are silently skipped and not included in the result. |
| 180 | +pub(crate) async fn detect_token_slots( |
170 | 181 | rpc_tools: &RPCTools, |
171 | | - solution: &Solution, |
172 | | - transaction: &Transaction, |
173 | 182 | block: &Block, |
174 | | - user_address: Address, |
175 | | -) -> miette::Result<(AddressHashMap<AccountOverride>, OverwriteMetadata)> { |
| 183 | + token_addresses: &[Bytes], |
| 184 | + to_address: &Bytes, |
| 185 | +) -> HashMap<Bytes, TokenSlots> { |
| 186 | + let user_address = match Address::from_str(USER_ADDR).into_diagnostic() { |
| 187 | + Ok(addr) => addr, |
| 188 | + Err(_) => return HashMap::new(), |
| 189 | + }; |
| 190 | + |
| 191 | + let mut token_slots = HashMap::new(); |
| 192 | + // Add one entry for ETH |
| 193 | + token_slots.insert(Bytes::zero(20), TokenSlots::default()); |
| 194 | + |
| 195 | + // Filter out ETH (zero address) as it doesn't need slot detection |
| 196 | + let erc20_tokens: Vec<Bytes> = token_addresses |
| 197 | + .iter() |
| 198 | + .filter(|&addr| addr != &Bytes::zero(20)) |
| 199 | + .cloned() |
| 200 | + .collect(); |
| 201 | + |
| 202 | + if erc20_tokens.is_empty() { |
| 203 | + return token_slots; |
| 204 | + } |
| 205 | + |
| 206 | + let balance_results = rpc_tools |
| 207 | + .evm_balance_slot_detector |
| 208 | + .detect_balance_slots(&erc20_tokens, (**user_address).into(), (*block.header.hash).into()) |
| 209 | + .await; |
| 210 | + |
| 211 | + let allowance_results = rpc_tools |
| 212 | + .evm_allowance_slot_detector |
| 213 | + .detect_allowance_slots( |
| 214 | + &erc20_tokens, |
| 215 | + (**user_address).into(), |
| 216 | + to_address.clone(), |
| 217 | + (*block.header.hash).into(), |
| 218 | + ) |
| 219 | + .await; |
| 220 | + |
| 221 | + for token_address in &erc20_tokens { |
| 222 | + let balance_slot_data = match balance_results.get(token_address) { |
| 223 | + Some(Ok((storage_addr, slot))) => (storage_addr.clone(), slot.clone()), |
| 224 | + Some(Err(_)) | None => continue, // Skip tokens with detection failures |
| 225 | + }; |
| 226 | + |
| 227 | + let allowance_slot_data = match allowance_results.get(token_address) { |
| 228 | + Some(Ok((storage_addr, slot))) => (storage_addr.clone(), slot.clone()), |
| 229 | + Some(Err(_)) | None => continue, // Skip tokens with detection failures |
| 230 | + }; |
| 231 | + |
| 232 | + token_slots.insert( |
| 233 | + token_address.clone(), |
| 234 | + TokenSlots { |
| 235 | + balance_storage_addr: balance_slot_data.0.to_vec(), |
| 236 | + balance_slot: balance_slot_data.1.to_vec(), |
| 237 | + allowance_storage_addr: allowance_slot_data.0.to_vec(), |
| 238 | + allowance_slot: allowance_slot_data.1.to_vec(), |
| 239 | + }, |
| 240 | + ); |
| 241 | + } |
| 242 | + |
| 243 | + token_slots |
| 244 | +} |
| 245 | + |
| 246 | +/// Set up all state overrides needed for simulation using pre-computed token slots. |
| 247 | +/// |
| 248 | +/// This includes balance overrides and allowance overrides of the sell token for the sender. |
| 249 | +/// Returns both the overwrites and metadata for human-readable logging. |
| 250 | +pub(crate) fn setup_user_overwrites( |
| 251 | + to_address: &Bytes, |
| 252 | + token_address: &Bytes, |
| 253 | + amount: &BigUint, |
| 254 | + token_slots: &TokenSlots, |
| 255 | +) -> (AddressHashMap<AccountOverride>, OverwriteMetadata) { |
176 | 256 | let mut overwrites = AddressHashMap::default(); |
177 | 257 | let mut metadata = OverwriteMetadata::new(); |
178 | | - let token_address = Address::from_slice(&solution.given_token[..20]); |
179 | | - // If given token is ETH, add the given amount + 10 ETH for gas |
180 | | - if solution.given_token == Bytes::zero(20) { |
181 | | - let eth_balance = biguint_to_u256(&solution.given_amount) + |
182 | | - U256::from_str("10000000000000000000").unwrap(); // given_amount + 1 ETH for gas |
| 258 | + let user_address = Address::from_str(USER_ADDR).expect("Valid user address"); |
| 259 | + let spender_address = Address::from_slice(&to_address[..20]); |
| 260 | + |
| 261 | + // ETH |
| 262 | + if token_address == &Bytes::zero(20) { |
| 263 | + let eth_balance = biguint_to_u256(amount) + |
| 264 | + U256::from_str("100000000000000000000").expect("Couldn't convert eth amount to U256"); // given_amount + 10 ETH for gas |
183 | 265 | overwrites.insert(user_address, AccountOverride::default().with_balance(eth_balance)); |
184 | | - // if the given token is not ETH, do balance and allowance slots overwrites |
185 | 266 | } else { |
186 | | - let results = rpc_tools |
187 | | - .evm_balance_slot_detector |
188 | | - .detect_balance_slots( |
189 | | - std::slice::from_ref(&solution.given_token), |
190 | | - (**user_address).into(), |
191 | | - (*block.header.hash).into(), |
192 | | - ) |
193 | | - .await; |
194 | | - |
195 | | - let (balance_storage_addr, balance_slot) = |
196 | | - if let Some(Ok((storage_addr, slot))) = results.get(&solution.given_token.clone()) { |
197 | | - (storage_addr, slot) |
198 | | - } else { |
199 | | - return Err(miette!("Couldn't find balance storage slot for token {token_address}")); |
200 | | - }; |
201 | | - |
202 | | - let results = rpc_tools |
203 | | - .evm_allowance_slot_detector |
204 | | - .detect_allowance_slots( |
205 | | - std::slice::from_ref(&solution.given_token), |
206 | | - (**user_address).into(), |
207 | | - transaction.to.clone(), // tycho router |
208 | | - (*block.header.hash).into(), |
209 | | - ) |
210 | | - .await; |
211 | | - |
212 | | - let (allowance_storage_addr, allowance_slot) = if let Some(Ok((storage_addr, slot))) = |
213 | | - results.get(&solution.given_token.clone()) |
214 | | - { |
215 | | - (storage_addr, slot) |
216 | | - } else { |
217 | | - return Err(miette!("Couldn't find allowance storage slot for token {token_address}")); |
218 | | - }; |
| 267 | + let token_balance = biguint_to_u256(amount); |
| 268 | + let token_allowance = biguint_to_u256(amount); |
219 | 269 |
|
220 | | - // Use the exact given amount for balance and allowance (no buffer, no max) |
221 | | - let token_balance = biguint_to_u256(&solution.given_amount); |
222 | | - let token_allowance = biguint_to_u256(&solution.given_amount); |
| 270 | + let balance_storage_address = Address::from_slice(&token_slots.balance_storage_addr[..20]); |
| 271 | + let allowance_storage_address = |
| 272 | + Address::from_slice(&token_slots.allowance_storage_addr[..20]); |
223 | 273 |
|
224 | | - let balance_storage_address = Address::from_slice(&balance_storage_addr[..20]); |
225 | | - let allowance_storage_address = Address::from_slice(&allowance_storage_addr[..20]); |
226 | | - |
227 | | - let balance_slot_b256 = alloy::primitives::B256::from_slice(balance_slot); |
228 | | - let allowance_slot_b256 = alloy::primitives::B256::from_slice(allowance_slot); |
229 | | - let spender_address = Address::from_slice(&transaction.to[..20]); |
| 274 | + let balance_slot_b256 = alloy::primitives::B256::from_slice(&token_slots.balance_slot); |
| 275 | + let allowance_slot_b256 = alloy::primitives::B256::from_slice(&token_slots.allowance_slot); |
230 | 276 |
|
231 | 277 | debug!( |
232 | 278 | "Setting token override for {token_address}: balance={}, allowance={}, balance_storage={}, allowance_storage={}", |
@@ -275,22 +321,21 @@ pub(crate) async fn setup_user_overwrites( |
275 | 321 | )]), |
276 | 322 | ); |
277 | 323 | } |
278 | | - |
279 | 324 | // Add 10 ETH for gas for non-ETH token swaps |
280 | | - let eth_balance = U256::from_str("10000000000000000000").unwrap(); // 1 ETH for gas |
| 325 | + let eth_balance = |
| 326 | + U256::from_str("10000000000000000000").expect("Couldn't convert eth amount to U256"); |
281 | 327 | overwrites.insert(user_address, AccountOverride::default().with_balance(eth_balance)); |
282 | | - debug!("Setting ETH balance override for user {user_address}: {eth_balance} (for gas)"); |
283 | 328 | } |
284 | 329 |
|
285 | | - Ok((overwrites, metadata)) |
| 330 | + (overwrites, metadata) |
286 | 331 | } |
287 | 332 |
|
288 | 333 | pub(crate) fn swap_request( |
289 | 334 | transaction: &Transaction, |
290 | 335 | block: &Block, |
291 | | - user_address: Address, |
292 | 336 | ) -> miette::Result<TransactionRequest> { |
293 | 337 | let (max_fee_per_gas, max_priority_fee_per_gas) = calculate_gas_fees(block)?; |
| 338 | + let user_address = Address::from_str(USER_ADDR).expect("Valid user address"); |
294 | 339 | Ok(TransactionRequest::default() |
295 | 340 | .to(Address::from_slice(&transaction.to[..20])) |
296 | 341 | .input(transaction.data.clone().into()) |
|
0 commit comments