Skip to content

Commit 9b041e5

Browse files
Merge pull request #407 from propeller-heads/cluster-test/dc/ENG-5104-batch-execute-per-block
feat: Batch RPC calls for execution simulation
2 parents 1fa18c9 + 99c6d01 commit 9b041e5

File tree

8 files changed

+625
-286
lines changed

8 files changed

+625
-286
lines changed

Cargo.toml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -112,8 +112,8 @@ approx = "0.5.1"
112112
rstest = "0.23.0"
113113
rstest_reuse = "0.7.0"
114114
tracing-subscriber = { workspace = true, default-features = false, features = [
115-
"env-filter",
116-
"fmt",
115+
"env-filter",
116+
"fmt",
117117
] }
118118
tempfile = "3.13.0"
119119

@@ -132,7 +132,7 @@ tracing-appender = "0.2.3"
132132
default = ["evm", "rfq"]
133133
network_tests = []
134134
evm = [
135-
"dep:foundry-config", "dep:foundry-evm", "dep:revm", "dep:revm-inspectors", "dep:alloy",
135+
"dep:foundry-config", "dep:foundry-evm", "dep:revm", "dep:revm-inspectors", "dep:alloy",
136136
]
137137
rfq = ["dep:reqwest", "dep:async-trait", "dep:tokio-tungstenite", "dep:async-stream", "dep:http", "dep:prost"]
138138

tycho-integration-test/src/execution/encoding.rs

Lines changed: 109 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use std::{str::FromStr, sync::Arc};
1+
use std::{collections::HashMap, str::FromStr, sync::Arc};
22

33
use alloy::{
44
rpc::types::{state::AccountOverride, Block, TransactionRequest},
@@ -27,6 +27,17 @@ use tycho_simulation::{
2727

2828
use crate::{execution::tenderly::OverwriteMetadata, RPCTools};
2929

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+
3041
pub fn encode_swap(
3142
component: &ProtocolComponent,
3243
state: Arc<dyn ProtocolSim>,
@@ -72,8 +83,7 @@ fn create_solution(
7283
amount_in: BigUint,
7384
expected_amount_out: BigUint,
7485
) -> 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()?;
7787

7888
// Prepare data to encode. First we need to create a swap object
7989
let simple_swap =
@@ -162,71 +172,107 @@ fn encode_input(selector: &str, mut encoded_args: Vec<u8>) -> Vec<u8> {
162172
call_data
163173
}
164174

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.
166176
///
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(
170181
rpc_tools: &RPCTools,
171-
solution: &Solution,
172-
transaction: &Transaction,
173182
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) {
176256
let mut overwrites = AddressHashMap::default();
177257
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
183265
overwrites.insert(user_address, AccountOverride::default().with_balance(eth_balance));
184-
// if the given token is not ETH, do balance and allowance slots overwrites
185266
} 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);
219269

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]);
223273

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);
230276

231277
debug!(
232278
"Setting token override for {token_address}: balance={}, allowance={}, balance_storage={}, allowance_storage={}",
@@ -275,22 +321,21 @@ pub(crate) async fn setup_user_overwrites(
275321
)]),
276322
);
277323
}
278-
279324
// 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");
281327
overwrites.insert(user_address, AccountOverride::default().with_balance(eth_balance));
282-
debug!("Setting ETH balance override for user {user_address}: {eth_balance} (for gas)");
283328
}
284329

285-
Ok((overwrites, metadata))
330+
(overwrites, metadata)
286331
}
287332

288333
pub(crate) fn swap_request(
289334
transaction: &Transaction,
290335
block: &Block,
291-
user_address: Address,
292336
) -> miette::Result<TransactionRequest> {
293337
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");
294339
Ok(TransactionRequest::default()
295340
.to(Address::from_slice(&transaction.to[..20]))
296341
.input(transaction.data.clone().into())

0 commit comments

Comments
 (0)