1use crate::network_config::NetworkConfigBuilder;
2use crate::test_environment::{
3 TestEnvironment, TestEnvironmentHandle, mock_genesis_root_contract,
4 test_network_hash,
5};
6use thruput_storage_cost::StorageCostModel;
7use thruput_xdr::contract::Contract;
8use thruput_xdr::network_config::{ConfigParam, NetworkConfig};
9use thruput_xdr::types::NetworkHash;
10
11#[must_use = "a TestEnvironmentBuilder does nothing until `build` is called"]
38pub struct TestEnvironmentBuilder {
39 cost_model: Option<StorageCostModel>,
40 network_hash: NetworkHash,
41 genesis_root_contract: Contract,
42 network_config: NetworkConfigBuilder,
43}
44
45impl Default for TestEnvironmentBuilder {
46 fn default() -> Self {
47 Self {
48 cost_model: None,
49 network_hash: test_network_hash(),
50 genesis_root_contract: mock_genesis_root_contract(),
51 network_config: NetworkConfigBuilder::new(),
52 }
53 }
54}
55
56impl TestEnvironmentBuilder {
57 pub fn new() -> Self {
59 Self::default()
60 }
61
62 pub fn with_cost_model(mut self, cost_model: StorageCostModel) -> Self {
64 self.cost_model = Some(cost_model);
65 self
66 }
67
68 pub fn no_cost(self) -> Self {
71 self.with_cost_model(StorageCostModel::no_cost_model())
72 }
73
74 pub fn with_network_hash(mut self, network_hash: NetworkHash) -> Self {
80 self.network_hash = network_hash;
81 self
82 }
83
84 pub fn with_genesis_root_contract(mut self, contract: Contract) -> Self {
87 self.genesis_root_contract = contract;
88 self
89 }
90
91 pub fn with_network_config(mut self, config: NetworkConfig) -> Self {
98 self.network_config = self.network_config.with_base(config);
99 self
100 }
101
102 pub fn with_config_param(mut self, param: ConfigParam) -> Self {
104 self.network_config = self.network_config.with_config_param(param);
105 self
106 }
107
108 pub fn with_max_sha256_buffers(mut self, max_sha256_buffers: u32) -> Self {
111 self.network_config = self
112 .network_config
113 .with_max_sha256_buffers(max_sha256_buffers);
114 self
115 }
116
117 pub fn with_max_cross_invoke_depth(
125 mut self,
126 max_cross_invoke_depth: u32,
127 ) -> Self {
128 self.network_config = self
129 .network_config
130 .with_max_cross_invoke_depth(max_cross_invoke_depth);
131 self
132 }
133
134 pub fn with_max_new_contracts_per_tx(
136 mut self,
137 max_new_contracts_per_tx: u32,
138 ) -> Self {
139 self.network_config = self
140 .network_config
141 .with_max_new_contracts_per_tx(max_new_contracts_per_tx);
142 self
143 }
144
145 pub fn with_max_bytestring_len(mut self, max_bytestring_len: u32) -> Self {
147 self.network_config = self
148 .network_config
149 .with_max_bytestring_len(max_bytestring_len);
150 self
151 }
152
153 pub fn with_max_contract_size_bytes(
156 mut self,
157 max_contract_size_bytes: u32,
158 ) -> Self {
159 self.network_config = self
160 .network_config
161 .with_max_contract_size_bytes(max_contract_size_bytes);
162 self
163 }
164
165 pub fn with_max_savepoint_depth(
169 mut self,
170 max_savepoint_depth: u32,
171 ) -> Self {
172 self.network_config = self
173 .network_config
174 .with_max_savepoint_depth(max_savepoint_depth);
175 self
176 }
177
178 pub fn with_max_sha256_buffer_bytes(
182 mut self,
183 max_sha256_buffer_bytes: u32,
184 ) -> Self {
185 self.network_config = self
186 .network_config
187 .with_max_sha256_buffer_bytes(max_sha256_buffer_bytes);
188 self
189 }
190
191 pub fn with_max_auth_token_depth(
194 mut self,
195 max_auth_token_depth: u32,
196 ) -> Self {
197 self.network_config = self
198 .network_config
199 .with_max_auth_token_depth(max_auth_token_depth);
200 self
201 }
202
203 pub fn with_max_rv32_code_bytes(
206 mut self,
207 max_rv32_code_bytes: u32,
208 ) -> Self {
209 self.network_config = self
210 .network_config
211 .with_max_rv32_code_bytes(max_rv32_code_bytes);
212 self
213 }
214
215 pub fn with_max_rv32_memory_bytes(
218 mut self,
219 max_rv32_memory_bytes: u32,
220 ) -> Self {
221 self.network_config = self
222 .network_config
223 .with_max_rv32_memory_bytes(max_rv32_memory_bytes);
224 self
225 }
226
227 pub fn build(self) -> TestEnvironmentHandle {
229 let network_config = if self.network_config.is_stock() {
233 None
234 } else {
235 Some(self.network_config.build())
236 };
237
238 TestEnvironment::from_parts(
239 self.cost_model.as_ref(),
240 &self.network_hash,
241 &self.genesis_root_contract,
242 network_config.as_ref(),
243 )
244 }
245}
246
247#[cfg(test)]
248mod tests {
249 use super::*;
250 use crate::network_config::{
251 config_params, find_config_param, stock_network_config,
252 };
253 use crate::test_helpers::Named;
254 use thruput_xdr::network_config::ConfigParamID;
255 use thruput_xdr::syscalls::{raw_BytestringSet, raw_SHA256Update};
256 use thruput_xdr::types::{ContractAddress, StorageKeyContractData};
257
258 fn max_sha256_buffers(config: &NetworkConfig) -> u32 {
259 match find_config_param(
260 config,
261 ConfigParamID::kConfigParamMaxSHA256Buffers,
262 ) {
263 Some(ConfigParam::kConfigParamMaxSHA256Buffers {
264 max_sha256_buffers,
265 }) => *max_sha256_buffers,
266 other => panic!("no SHA-256 buffer cap in the config: {other:?}"),
267 }
268 }
269
270 #[test]
271 fn a_builder_with_no_overrides_matches_the_plain_constructor() {
272 let built = TestEnvironment::builder().build();
273 let plain = TestEnvironment::new(None);
274
275 assert_eq!(built.get_network_config(), plain.get_network_config());
276 assert_eq!(built.get_network_config(), stock_network_config());
277 }
278
279 #[test]
280 fn no_cost_builds_the_free_cost_model() {
281 assert_eq!(
282 TestEnvironment::builder()
283 .no_cost()
284 .build()
285 .get_current_cost_model()
286 .bytestring_cost(32),
287 0,
288 );
289 assert!(
290 TestEnvironment::builder()
291 .build()
292 .get_current_cost_model()
293 .bytestring_cost(32)
294 > 0,
295 "the chain default charges for storage",
296 );
297 }
298
299 #[test]
300 fn an_overridden_parameter_is_the_only_one_that_changes() {
301 let stock = stock_network_config();
302 let env = TestEnvironment::builder()
303 .with_max_sha256_buffers(max_sha256_buffers(&stock) + 1)
304 .build();
305 let config = env.get_network_config();
306
307 assert_eq!(
308 max_sha256_buffers(&config),
309 max_sha256_buffers(&stock) + 1,
310 );
311 assert_eq!(config_params(&config).len(), config_params(&stock).len());
312 assert_eq!(
313 &config_params(&config)[1..],
314 &config_params(&stock)[1..],
315 "the parameters left alone keep their stock values",
316 );
317 }
318
319 #[test]
324 fn the_sha256_buffer_cap_the_builder_sets_is_the_one_the_vm_enforces() {
325 let env = TestEnvironment::builder()
326 .no_cost()
327 .with_max_sha256_buffers(2)
328 .build();
329
330 env.transact(|tx| {
331 tx.call(&ContractAddress::named("sha256_cap")).run(|| {
332 assert!(raw_SHA256Update(0, &[], true).is_ok());
333 assert!(raw_SHA256Update(1, &[], true).is_ok());
334 assert!(
335 raw_SHA256Update(2, &[], true).is_err(),
336 "a third live stream must exceed the cap of 2",
337 );
338 })
339 })
340 .assert_success();
341 }
342
343 #[test]
344 fn the_stock_cap_leaves_room_for_more_streams_than_that() {
345 let env = TestEnvironment::builder().no_cost().build();
346 let cap = max_sha256_buffers(&env.get_network_config());
347 assert!(cap > 2, "this test needs a stock cap above the one above");
348
349 env.transact(|tx| {
350 tx.call(&ContractAddress::named("sha256_stock_cap")).run(
351 move || {
352 for index in 0..cap {
353 assert!(raw_SHA256Update(index, &[], true).is_ok());
354 }
355 assert!(raw_SHA256Update(cap, &[], true).is_err());
356 },
357 )
358 })
359 .assert_success();
360 }
361
362 #[test]
363 fn the_max_bytestring_len_parameter_is_settable() {
364 let env = TestEnvironment::builder()
365 .no_cost()
366 .with_max_bytestring_len(64)
367 .build();
368
369 assert_eq!(
370 find_config_param(
371 &env.get_network_config(),
372 ConfigParamID::kConfigParamMaxBytestringLen,
373 ),
374 Some(&ConfigParam::kConfigParamMaxBytestringLen {
375 max_bytestring_len: 64
376 }),
377 );
378 }
379
380 #[test]
384 fn the_bytestring_cap_the_builder_sets_is_the_one_the_vm_enforces() {
385 let env = TestEnvironment::builder()
386 .no_cost()
387 .with_max_bytestring_len(64)
388 .build();
389 let key = StorageKeyContractData::new([0x77; 32]);
390
391 env.transact(|tx| {
392 tx.call(&ContractAddress::named("bytestring_cap"))
393 .run(move || {
394 assert!(
395 raw_BytestringSet(&key, &[0u8; 64]).is_ok(),
396 "a bytestring at the cap must still be admitted",
397 );
398 assert!(
399 raw_BytestringSet(&key, &[0u8; 65])
400 .unwrap_err()
401 .is_invalid_input(),
402 "one byte over must not be",
403 );
404 })
405 })
406 .assert_success();
407 }
408
409 #[test]
410 fn an_explicit_network_config_is_taken_verbatim() {
411 let stock = stock_network_config();
412 let env = TestEnvironment::builder()
413 .with_network_config(stock.clone())
414 .build();
415
416 assert_eq!(env.get_network_config(), stock);
417 }
418}