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