soroban_sdk/testutils/cost_estimate.rs
1use soroban_env_host::{fees::FeeConfiguration, FeeEstimate, InvocationResources};
2
3use crate::{testutils::budget::Budget, Env};
4
5pub struct CostEstimate {
6 env: Env,
7}
8
9impl CostEstimate {
10 pub(crate) fn new(env: Env) -> Self {
11 Self { env }
12 }
13
14 /// Returns the resources metered during the last top level contract
15 /// invocation.
16 /// Take the return value with a grain of salt. The returned resources mostly
17 /// correspond only to the operations that have happened during the host
18 /// invocation, i.e. this won't try to simulate the work that happens in
19 /// production scenarios (e.g. certain XDR rountrips). This also doesn't try
20 /// to model resources related to the transaction size.
21 ///
22 /// The returned value is as useful as the preceding setup, e.g. if a test
23 /// contract is used instead of a Wasm contract, all the costs related to
24 /// VM instantiation and execution, as well as Wasm reads/rent bumps will be
25 /// missed.
26 pub fn resources(&self) -> InvocationResources {
27 if let Some(res) = self.env.host().get_last_invocation_resources() {
28 res
29 } else {
30 panic!("Invocation cost estimate is not available. Make sure invocation cost metering is enabled in the EnvTestConfig and this is called after an invocation.")
31 }
32 }
33
34 /// Estimates the fee for the last invocation's resources, i.e. the
35 /// resources returned by `resources()`.
36 ///
37 /// The fees are computed using the snapshot of the Stellar Pubnet fees made
38 /// on 2024-12-11.
39 ///
40 /// Take the return value with a grain of salt as both the resource estimate
41 /// and the fee rates may be imprecise.
42 ///
43 /// The returned value is as useful as the preceding setup, e.g. if a test
44 /// contract is used instead of a Wasm contract, all the costs related to
45 /// VM instantiation and execution, as well as Wasm reads/rent bumps will be
46 /// missed.
47 pub fn fee(&self) -> FeeEstimate {
48 // This is a snapshot of the fees as of 2024-12-11.
49 let pubnet_fee_config = FeeConfiguration {
50 fee_per_instruction_increment: 25,
51 fee_per_read_entry: 6250,
52 fee_per_write_entry: 10000,
53 fee_per_read_1kb: 1786,
54 // This is a bit higher than the current network fee, it's an
55 // overestimate for the sake of providing a bit more conservative
56 // results in case if the state grows.
57 fee_per_write_1kb: 12000,
58 fee_per_historical_1kb: 16235,
59 fee_per_contract_event_1kb: 10000,
60 fee_per_transaction_size_1kb: 1624,
61 };
62 let pubnet_persistent_rent_rate_denominator = 2103;
63 let pubnet_temp_rent_rate_denominator = 4206;
64
65 self.resources().estimate_fees(
66 &pubnet_fee_config,
67 pubnet_persistent_rent_rate_denominator,
68 pubnet_temp_rent_rate_denominator,
69 )
70 }
71
72 /// Returns the budget object that provides the detailed CPU and memory
73 /// metering information recorded thus far.
74 ///
75 /// The budget metering resets before every top-level contract level
76 /// invocation.
77 ///
78 /// budget() may also be used to adjust the CPU and memory limits via the
79 /// `reset_` methods.
80 ///
81 /// Note, that unlike `resources()`/`fee()` this will always return some
82 /// value. If there was no contract call, then the resulting value will
83 /// correspond to metering any environment setup that has been made thus
84 /// far.
85 pub fn budget(&self) -> Budget {
86 Budget::new(self.env.host().budget_cloned())
87 }
88}