1use crate::network_config::NetworkConfig;
2use crate::resources::{
3 compute_adjusted_transaction_resources, compute_resource_fee, simulate_extend_ttl_op_resources,
4 simulate_restore_op_resources,
5};
6use crate::snapshot_source::SimulationSnapshotSource;
7use anyhow::Result;
8use soroban_env_host::e2e_invoke::extract_rent_changes;
9use soroban_env_host::xdr::SorobanResourcesExtV0;
10use soroban_env_host::{
11 e2e_invoke::invoke_host_function_in_recording_mode,
12 e2e_invoke::{LedgerEntryChange, RecordingInvocationAuthMode},
13 storage::SnapshotSource,
14 xdr::{
15 AccountId, ContractEvent, DiagnosticEvent, HostFunction, InvokeHostFunctionOp, LedgerKey,
16 OperationBody, ScVal, SorobanAuthorizationEntry, SorobanResources, SorobanTransactionData,
17 SorobanTransactionDataExt,
18 },
19 xdr::{ExtendFootprintTtlOp, ExtensionPoint, LedgerEntry, ReadXdr, RestoreFootprintOp},
20 HostError, LedgerInfo, DEFAULT_XDR_RW_LIMITS,
21};
22use std::rc::Rc;
23
24pub struct SimulationAdjustmentFactor {
28 pub multiplicative_factor: f64,
29 pub additive_factor: u32,
30}
31
32pub struct SimulationAdjustmentConfig {
35 pub instructions: SimulationAdjustmentFactor,
36 pub read_bytes: SimulationAdjustmentFactor,
37 pub write_bytes: SimulationAdjustmentFactor,
38 pub tx_size: SimulationAdjustmentFactor,
39 pub refundable_fee: SimulationAdjustmentFactor,
40}
41
42#[derive(Eq, PartialEq, Debug)]
46pub struct LedgerEntryDiff {
47 pub state_before: Option<LedgerEntry>,
48 pub state_after: Option<LedgerEntry>,
49}
50
51#[derive(Debug)]
53pub struct InvokeHostFunctionSimulationResult {
54 pub invoke_result: std::result::Result<ScVal, HostError>,
56 pub auth: Vec<SorobanAuthorizationEntry>,
59 pub contract_events: Vec<ContractEvent>,
62 pub diagnostic_events: Vec<DiagnosticEvent>,
66 pub transaction_data: Option<SorobanTransactionData>,
70 pub simulated_instructions: u32,
75 pub simulated_memory: u32,
78 pub modified_entries: Vec<LedgerEntryDiff>,
82}
83
84#[derive(Eq, PartialEq, Debug)]
86pub struct ExtendTtlOpSimulationResult {
87 pub transaction_data: SorobanTransactionData,
90}
91
92#[derive(Eq, PartialEq, Debug)]
94pub struct RestoreOpSimulationResult {
95 pub transaction_data: SorobanTransactionData,
98}
99
100#[allow(clippy::too_many_arguments)]
121pub fn simulate_invoke_host_function_op(
122 snapshot_source: Rc<dyn SnapshotSource>,
123 network_config: &NetworkConfig,
124 adjustment_config: &SimulationAdjustmentConfig,
125 ledger_info: &LedgerInfo,
126 host_fn: HostFunction,
127 auth_mode: RecordingInvocationAuthMode,
128 source_account: &AccountId,
129 base_prng_seed: [u8; 32],
130 enable_diagnostics: bool,
131) -> Result<InvokeHostFunctionSimulationResult> {
132 let snapshot_source = Rc::new(SimulationSnapshotSource::new_from_rc(snapshot_source));
133 let budget = network_config.create_budget()?;
134 let mut diagnostic_events = vec![];
135 let recording_result = invoke_host_function_in_recording_mode(
136 &budget,
137 enable_diagnostics,
138 &host_fn,
139 source_account,
140 auth_mode,
141 ledger_info.clone(),
142 snapshot_source.clone(),
143 base_prng_seed,
144 &mut diagnostic_events,
145 );
146 let invoke_result = match &recording_result {
147 Ok(r) => r.invoke_result.clone(),
148 Err(e) => Err(e.clone()),
149 };
150 let mut simulation_result = InvokeHostFunctionSimulationResult {
154 invoke_result,
157 simulated_instructions: budget.get_cpu_insns_consumed()?.try_into()?,
158 simulated_memory: budget.get_mem_bytes_consumed()?.try_into()?,
159 diagnostic_events,
160 auth: vec![],
162 contract_events: vec![],
163 transaction_data: None,
164 modified_entries: vec![],
165 };
166 let Ok(recording_result) = recording_result else {
167 return Ok(simulation_result);
168 };
169 if recording_result.invoke_result.is_err() {
170 return Ok(simulation_result);
171 }
172 simulation_result.auth = recording_result.auth;
174 simulation_result.contract_events = recording_result.contract_events;
175 simulation_result.modified_entries = extract_modified_entries(
176 &*snapshot_source,
177 &recording_result.ledger_changes,
178 &ledger_info,
179 )?;
180 let mut resources = recording_result.resources;
181 let rent_changes = extract_rent_changes(&recording_result.ledger_changes);
182 let operation = OperationBody::InvokeHostFunction(InvokeHostFunctionOp {
183 host_function: host_fn,
184 auth: simulation_result.auth.clone().try_into()?,
185 });
186 let transaction_resources = compute_adjusted_transaction_resources(
187 operation,
188 &mut resources,
189 &recording_result.restored_rw_entry_indices,
190 adjustment_config,
191 recording_result.contract_events_and_return_value_size,
192 )?;
193 let resource_fee = compute_resource_fee(
194 network_config,
195 &ledger_info,
196 &transaction_resources,
197 &rent_changes,
198 adjustment_config,
199 );
200 simulation_result.transaction_data = Some(create_transaction_data(
201 resources,
202 &recording_result.restored_rw_entry_indices,
203 resource_fee,
204 )?);
205
206 Ok(simulation_result)
207}
208
209pub fn simulate_extend_ttl_op(
225 snapshot_source: &impl SnapshotSource,
226 network_config: &NetworkConfig,
227 adjustment_config: &SimulationAdjustmentConfig,
228 ledger_info: &LedgerInfo,
229 keys_to_extend: &[LedgerKey],
230 extend_to: u32,
231) -> Result<ExtendTtlOpSimulationResult> {
232 let snapshot_source = SimulationSnapshotSource::new(snapshot_source);
233 let (mut resources, rent_changes) = simulate_extend_ttl_op_resources(
234 keys_to_extend,
235 &snapshot_source,
236 network_config,
237 ledger_info.sequence_number,
238 extend_to,
239 )?;
240 let operation = OperationBody::ExtendFootprintTtl(ExtendFootprintTtlOp {
241 ext: ExtensionPoint::V0,
242 extend_to,
243 });
244 let transaction_resources = compute_adjusted_transaction_resources(
245 operation,
246 &mut resources,
247 &vec![],
248 adjustment_config,
249 0,
250 )?;
251 let resource_fee = compute_resource_fee(
252 network_config,
253 &ledger_info,
254 &transaction_resources,
255 &rent_changes,
256 adjustment_config,
257 );
258 Ok(ExtendTtlOpSimulationResult {
259 transaction_data: create_transaction_data(resources, &vec![], resource_fee)?,
260 })
261}
262
263pub fn simulate_restore_op(
280 snapshot_source: &impl SnapshotSource,
281 network_config: &NetworkConfig,
282 adjustment_config: &SimulationAdjustmentConfig,
283 ledger_info: &LedgerInfo,
284 keys_to_restore: &[LedgerKey],
285) -> Result<RestoreOpSimulationResult> {
286 let snapshot_source = SimulationSnapshotSource::new(snapshot_source);
287 let (mut resources, rent_changes) = simulate_restore_op_resources(
288 keys_to_restore,
289 &snapshot_source,
290 network_config,
291 ledger_info,
292 )?;
293 let operation = OperationBody::RestoreFootprint(RestoreFootprintOp {
294 ext: ExtensionPoint::V0,
295 });
296 let transaction_resources = compute_adjusted_transaction_resources(
297 operation,
298 &mut resources,
299 &vec![],
300 adjustment_config,
301 0,
302 )?;
303 let resource_fee = compute_resource_fee(
304 network_config,
305 &ledger_info,
306 &transaction_resources,
307 &rent_changes,
308 adjustment_config,
309 );
310 Ok(RestoreOpSimulationResult {
311 transaction_data: create_transaction_data(resources, &vec![], resource_fee)?,
312 })
313}
314
315impl SimulationAdjustmentFactor {
316 pub fn new(multiplicative_factor: f64, additive_factor: u32) -> Self {
317 Self {
318 multiplicative_factor,
319 additive_factor,
320 }
321 }
322
323 pub fn no_adjustment() -> Self {
324 Self {
325 multiplicative_factor: 1.0,
326 additive_factor: 0,
327 }
328 }
329}
330
331impl SimulationAdjustmentConfig {
332 pub fn no_adjustments() -> Self {
333 Self {
334 instructions: SimulationAdjustmentFactor::no_adjustment(),
335 read_bytes: SimulationAdjustmentFactor::no_adjustment(),
336 write_bytes: SimulationAdjustmentFactor::no_adjustment(),
337 tx_size: SimulationAdjustmentFactor::no_adjustment(),
338 refundable_fee: SimulationAdjustmentFactor::no_adjustment(),
339 }
340 }
341
342 pub fn default_adjustment() -> Self {
343 Self {
344 instructions: SimulationAdjustmentFactor::new(1.04, 50_000),
345 read_bytes: SimulationAdjustmentFactor::no_adjustment(),
346 write_bytes: SimulationAdjustmentFactor::no_adjustment(),
347 tx_size: SimulationAdjustmentFactor::new(1.1, 500),
350 refundable_fee: SimulationAdjustmentFactor::new(1.15, 0),
351 }
352 }
353}
354
355fn create_transaction_data(
356 resources: SorobanResources,
357 restored_rw_entry_ids: &Vec<u32>,
358 resource_fee: i64,
359) -> Result<SorobanTransactionData> {
360 Ok(SorobanTransactionData {
361 resources,
362 resource_fee,
363 ext: if restored_rw_entry_ids.is_empty() {
364 SorobanTransactionDataExt::V0
365 } else {
366 SorobanTransactionDataExt::V1(SorobanResourcesExtV0 {
367 archived_soroban_entries: restored_rw_entry_ids.try_into()?,
368 })
369 },
370 })
371}
372
373fn extract_modified_entries(
374 snapshot: &(impl SnapshotSource + ?Sized),
375 ledger_changes: &[LedgerEntryChange],
376 ledger_info: &LedgerInfo,
377) -> Result<Vec<LedgerEntryDiff>> {
378 let mut diffs = vec![];
379 for c in ledger_changes {
380 if c.read_only {
381 continue;
382 }
383 let key = LedgerKey::from_xdr(c.encoded_key.clone(), DEFAULT_XDR_RW_LIMITS)?;
384 let state_before =
385 if let Some((entry_before, live_until_before)) = snapshot.get(&Rc::new(key))? {
386 let mut state_before = Some(entry_before.as_ref().clone());
387 if let Some(live_until_before) = live_until_before {
388 if live_until_before < ledger_info.sequence_number {
389 state_before = None;
390 }
391 }
392 state_before
393 } else {
394 None
395 };
396
397 let state_after = match &c.encoded_new_value {
398 Some(v) => Some(LedgerEntry::from_xdr(v.clone(), DEFAULT_XDR_RW_LIMITS)?),
399 None => None,
400 };
401 diffs.push(LedgerEntryDiff {
402 state_before,
403 state_after,
404 });
405 }
406 Ok(diffs)
407}