soroban_sdk/testutils/cost_estimate.rs
1use soroban_env_host::{
2 fees::FeeConfiguration, FeeEstimate, InvocationResourceLimits, InvocationResources,
3};
4
5use crate::{testutils::budget::Budget, Env};
6
7pub struct CostEstimate {
8 env: Env,
9}
10
11impl CostEstimate {
12 pub(crate) fn new(env: Env) -> Self {
13 Self { env }
14 }
15
16 /// Returns the resources metered during the last top level contract
17 /// invocation.
18 /// Take the return value with a grain of salt. The returned resources mostly
19 /// correspond only to the operations that have happened during the host
20 /// invocation, i.e. this won't try to simulate the work that happens in
21 /// production scenarios (e.g. certain XDR rountrips). This also doesn't try
22 /// to model resources related to the transaction size.
23 ///
24 /// The returned value is as useful as the preceding setup, e.g. if a test
25 /// contract is used instead of a Wasm contract, all the costs related to
26 /// VM instantiation and execution, as well as Wasm reads/rent bumps will be
27 /// missed.
28 pub fn resources(&self) -> InvocationResources {
29 if let Some(res) = self.env.host().get_last_invocation_resources() {
30 res
31 } else {
32 panic!("Invocation cost estimate is not available. Make sure invocation cost metering is enabled in the EnvTestConfig and this is called after an invocation.")
33 }
34 }
35
36 /// Estimates the fee for the last invocation's resources, i.e. the
37 /// resources returned by `resources()`.
38 ///
39 /// The fees are computed using a snapshot of the Stellar Mainnet fees made
40 /// on 2026-07-10. Because the fees are hardcoded rather than pulled
41 /// dynamically, they may drift from the live network over time; the current
42 /// values can be checked via `stellar network settings --network mainnet`
43 /// or on Stellar Lab: <https://lab.stellar.org/network-limits>. The one
44 /// exception is the per-1KB storage rent rate, which is a deliberate
45 /// conservative overestimate rather than the snapshot value, so storage
46 /// rent estimates may be higher than the live network charges.
47 ///
48 /// Take the return value with a grain of salt as both the resource estimate
49 /// and the fee rates may be imprecise.
50 ///
51 /// The returned value is as useful as the preceding setup, e.g. if a test
52 /// contract is used instead of a Wasm contract, all the costs related to
53 /// VM instantiation and execution, as well as Wasm reads/rent bumps will be
54 /// missed.
55 pub fn fee(&self) -> FeeEstimate {
56 // This is a snapshot of the Stellar Mainnet fees as of 2026-07-10.
57 // Refresh it with the values from `stellar network settings --network
58 // mainnet` (or <https://lab.stellar.org/network-limits>) when it drifts.
59 let pubnet_fee_config = FeeConfiguration {
60 fee_per_instruction_increment: 7,
61 fee_per_disk_read_entry: 1563,
62 fee_per_write_entry: 2500,
63 fee_per_disk_read_1kb: 447,
64 fee_per_write_1kb: 875,
65 fee_per_historical_1kb: 4059,
66 fee_per_contract_event_1kb: 5000,
67 fee_per_transaction_size_1kb: 406,
68 };
69 let pubnet_persistent_rent_rate_denominator = 1215;
70 let pubnet_temp_rent_rate_denominator = 2430;
71 // This is a bit higher than the current network fee, it's an
72 // overestimate for the sake of providing a bit more conservative
73 // results in case if the state grows.
74 let fee_per_rent_1kb = 12000;
75 self.resources().estimate_fees(
76 &pubnet_fee_config,
77 fee_per_rent_1kb,
78 pubnet_persistent_rent_rate_denominator,
79 pubnet_temp_rent_rate_denominator,
80 )
81 }
82
83 /// Returns the budget object that provides the detailed CPU and memory
84 /// metering information recorded thus far.
85 ///
86 /// The budget metering resets before every top-level contract level
87 /// invocation.
88 ///
89 /// budget() may also be used to adjust the CPU and memory limits via the
90 /// `reset_` methods.
91 ///
92 /// Note, that unlike `resources()`/`fee()` this will always return some
93 /// value. If there was no contract call, then the resulting value will
94 /// correspond to metering any environment setup that has been made thus
95 /// far.
96 pub fn budget(&self) -> Budget {
97 Budget::new(self.env.host().budget_cloned())
98 }
99
100 /// Enforces custom resource limits for contract invocations in tests.
101 ///
102 /// When limit enforcement is enabled, for every contract invocation the
103 /// resource usage is checked against the provided limits, and if any of the
104 /// limits is exceeded, the contract invocation will result in a panic
105 /// that indicates which limits were exceeded.
106 ///
107 /// Limit enforcement is meant to provide an early warning sign that a
108 /// contract might be too resource heavy to run on a real network. If the
109 /// high resource usage is intentional and expected (e.g. for
110 /// experimentation), disable the enforcement via
111 /// `disable_resource_limits()`.
112 ///
113 /// By default, `InvocationResourceLimits::mainnet()` limits are enforced.
114 pub fn enforce_resource_limits(&self, limits: InvocationResourceLimits) {
115 self.env
116 .host()
117 .set_invocation_resource_limits(Some(limits))
118 .unwrap();
119 }
120
121 /// Disables resource limit enforcement for contract invocations in tests.
122 ///
123 /// This may be useful for the experimental contracts that are still being
124 /// optimized.
125 pub fn disable_resource_limits(&self) {
126 self.env
127 .host()
128 .set_invocation_resource_limits(None)
129 .unwrap();
130 }
131}
132
133/// Predefined network invocation resource limits.
134pub trait NetworkInvocationResourceLimits {
135 fn mainnet() -> Self;
136}
137
138impl NetworkInvocationResourceLimits for InvocationResourceLimits {
139 /// Returns the invocation resource limits used on Stellar Mainnet.
140 ///
141 /// These values are a snapshot of the Mainnet network settings as of
142 /// 2026-07-10; they are hardcoded rather than pulled dynamically, so
143 /// updating the SDK is necessary to pick up the most recent values. The
144 /// current values can be checked via `stellar network settings --network
145 /// mainnet` or on Stellar Lab: <https://lab.stellar.org/network-limits>.
146 fn mainnet() -> Self {
147 InvocationResourceLimits {
148 instructions: 400_000_000,
149 mem_bytes: 41943040,
150 disk_read_entries: 200,
151 write_entries: 200,
152 ledger_entries: 400,
153 disk_read_bytes: 200000,
154 write_bytes: 132096,
155 contract_events_size_bytes: 16384,
156 max_contract_data_key_size_bytes: 250,
157 max_contract_data_entry_size_bytes: 65536,
158 max_contract_code_entry_size_bytes: 131072,
159 }
160 }
161}