1#![cfg(any(test, feature = "testutils"))]
2#![cfg_attr(feature = "docs", doc(cfg(feature = "testutils")))]
3
4pub mod arbitrary;
7
8mod sign;
9use std::rc::Rc;
10
11pub use sign::ed25519;
12
13mod mock_auth;
14pub use mock_auth::{
15 AuthorizedFunction, AuthorizedInvocation, MockAuth, MockAuthContract, MockAuthInvoke,
16};
17use soroban_env_host::TryIntoVal;
18
19pub mod storage;
20
21pub mod cost_estimate;
22
23use crate::{xdr, ConstructorArgs, Env, Val, Vec};
24use soroban_ledger_snapshot::LedgerSnapshot;
25
26pub use crate::env::EnvTestConfig;
27
28pub trait Register {
29 fn register<'i, I, A>(self, env: &Env, id: I, args: A) -> crate::Address
30 where
31 I: Into<Option<&'i crate::Address>>,
32 A: ConstructorArgs;
33}
34
35impl<C> Register for C
36where
37 C: ContractFunctionSet + 'static,
38{
39 fn register<'i, I, A>(self, env: &Env, id: I, args: A) -> crate::Address
40 where
41 I: Into<Option<&'i crate::Address>>,
42 A: ConstructorArgs,
43 {
44 env.register_contract_with_constructor(id, self, args)
45 }
46}
47
48impl<'w> Register for &'w [u8] {
49 fn register<'i, I, A>(self, env: &Env, id: I, args: A) -> crate::Address
50 where
51 I: Into<Option<&'i crate::Address>>,
52 A: ConstructorArgs,
53 {
54 env.register_contract_wasm_with_constructor(id, self, args)
55 }
56}
57
58#[derive(Default, Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
59#[serde(rename_all = "snake_case")]
60pub struct Snapshot {
61 pub generators: Generators,
62 pub auth: AuthSnapshot,
63 pub ledger: LedgerSnapshot,
64 pub events: EventsSnapshot,
65}
66
67impl Snapshot {
68 pub fn read(r: impl std::io::Read) -> Result<Snapshot, std::io::Error> {
70 Ok(serde_json::from_reader::<_, Snapshot>(r)?)
71 }
72
73 pub fn read_file(p: impl AsRef<std::path::Path>) -> Result<Snapshot, std::io::Error> {
75 Self::read(std::fs::File::open(p)?)
76 }
77
78 pub fn write(&self, w: impl std::io::Write) -> Result<(), std::io::Error> {
80 Ok(serde_json::to_writer_pretty(w, self)?)
81 }
82
83 pub fn write_file(&self, p: impl AsRef<std::path::Path>) -> Result<(), std::io::Error> {
85 let p = p.as_ref();
86 if let Some(dir) = p.parent() {
87 if !dir.exists() {
88 std::fs::create_dir_all(dir)?;
89 }
90 }
91 self.write(std::fs::File::create(p)?)
92 }
93}
94
95#[derive(Default, Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
96#[serde(rename_all = "snake_case")]
97pub struct EventsSnapshot(pub std::vec::Vec<EventSnapshot>);
98
99impl EventsSnapshot {
100 pub fn read(r: impl std::io::Read) -> Result<EventsSnapshot, std::io::Error> {
102 Ok(serde_json::from_reader::<_, EventsSnapshot>(r)?)
103 }
104
105 pub fn read_file(p: impl AsRef<std::path::Path>) -> Result<EventsSnapshot, std::io::Error> {
107 Self::read(std::fs::File::open(p)?)
108 }
109
110 pub fn write(&self, w: impl std::io::Write) -> Result<(), std::io::Error> {
112 Ok(serde_json::to_writer_pretty(w, self)?)
113 }
114
115 pub fn write_file(&self, p: impl AsRef<std::path::Path>) -> Result<(), std::io::Error> {
117 let p = p.as_ref();
118 if let Some(dir) = p.parent() {
119 if !dir.exists() {
120 std::fs::create_dir_all(dir)?;
121 }
122 }
123 self.write(std::fs::File::create(p)?)
124 }
125}
126
127#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
128#[serde(rename_all = "snake_case")]
129pub struct EventSnapshot {
130 pub event: xdr::ContractEvent,
131 pub failed_call: bool,
132}
133
134impl From<crate::env::internal::events::HostEvent> for EventSnapshot {
135 fn from(v: crate::env::internal::events::HostEvent) -> Self {
136 Self {
137 event: v.event,
138 failed_call: v.failed_call,
139 }
140 }
141}
142
143#[derive(Default, Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
144#[serde(rename_all = "snake_case")]
145pub struct AuthSnapshot(
146 pub std::vec::Vec<std::vec::Vec<(xdr::ScAddress, xdr::SorobanAuthorizedInvocation)>>,
147);
148
149impl AuthSnapshot {
150 pub fn read(r: impl std::io::Read) -> Result<AuthSnapshot, std::io::Error> {
152 Ok(serde_json::from_reader::<_, AuthSnapshot>(r)?)
153 }
154
155 pub fn read_file(p: impl AsRef<std::path::Path>) -> Result<AuthSnapshot, std::io::Error> {
157 Self::read(std::fs::File::open(p)?)
158 }
159
160 pub fn write(&self, w: impl std::io::Write) -> Result<(), std::io::Error> {
162 Ok(serde_json::to_writer_pretty(w, self)?)
163 }
164
165 pub fn write_file(&self, p: impl AsRef<std::path::Path>) -> Result<(), std::io::Error> {
167 let p = p.as_ref();
168 if let Some(dir) = p.parent() {
169 if !dir.exists() {
170 std::fs::create_dir_all(dir)?;
171 }
172 }
173 self.write(std::fs::File::create(p)?)
174 }
175}
176
177#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
178#[serde(rename_all = "snake_case")]
179pub struct Generators {
180 address: u64,
181 nonce: u64,
182}
183
184impl Default for Generators {
185 fn default() -> Generators {
186 Generators {
187 address: 0,
188 nonce: 0,
189 }
190 }
191}
192
193impl Generators {
194 pub fn read(r: impl std::io::Read) -> Result<Generators, std::io::Error> {
196 Ok(serde_json::from_reader::<_, Generators>(r)?)
197 }
198
199 pub fn read_file(p: impl AsRef<std::path::Path>) -> Result<Generators, std::io::Error> {
201 Self::read(std::fs::File::open(p)?)
202 }
203
204 pub fn write(&self, w: impl std::io::Write) -> Result<(), std::io::Error> {
206 Ok(serde_json::to_writer_pretty(w, self)?)
207 }
208
209 pub fn write_file(&self, p: impl AsRef<std::path::Path>) -> Result<(), std::io::Error> {
211 let p = p.as_ref();
212 if let Some(dir) = p.parent() {
213 if !dir.exists() {
214 std::fs::create_dir_all(dir)?;
215 }
216 }
217 self.write(std::fs::File::create(p)?)
218 }
219}
220
221impl Generators {
222 pub fn address(&mut self) -> [u8; 32] {
223 self.address = self.address.checked_add(1).unwrap();
224 let b: [u8; 8] = self.address.to_be_bytes();
225 [
226 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, b[0], b[1],
227 b[2], b[3], b[4], b[5], b[6], b[7],
228 ]
229 }
230
231 pub fn nonce(&mut self) -> i64 {
232 self.nonce = self.nonce.checked_add(1).unwrap();
233 self.nonce as i64
234 }
235}
236
237#[doc(hidden)]
238pub type ContractFunctionF = dyn Send + Sync + Fn(Env, &[Val]) -> Val;
239#[doc(hidden)]
240pub trait ContractFunctionRegister {
241 fn register(name: &'static str, func: &'static ContractFunctionF);
242}
243#[doc(hidden)]
244pub trait ContractFunctionSet {
245 fn call(&self, func: &str, env: Env, args: &[Val]) -> Option<Val>;
246}
247
248#[doc(inline)]
249pub use crate::env::internal::LedgerInfo;
250
251pub trait Ledger {
253 fn set(&self, l: LedgerInfo);
255
256 fn set_protocol_version(&self, protocol_version: u32);
258
259 fn set_sequence_number(&self, sequence_number: u32);
261
262 fn set_timestamp(&self, timestamp: u64);
264
265 fn set_network_id(&self, network_id: [u8; 32]);
267
268 fn set_base_reserve(&self, base_reserve: u32);
270
271 fn set_min_temp_entry_ttl(&self, min_temp_entry_ttl: u32);
273
274 fn set_min_persistent_entry_ttl(&self, min_persistent_entry_ttl: u32);
276
277 fn set_max_entry_ttl(&self, max_entry_ttl: u32);
279
280 fn get(&self) -> LedgerInfo;
282
283 fn with_mut<F>(&self, f: F)
285 where
286 F: FnMut(&mut LedgerInfo);
287}
288
289pub mod budget {
290 use core::fmt::{Debug, Display};
291
292 #[doc(inline)]
293 use crate::env::internal::budget::CostTracker;
294 #[doc(inline)]
295 pub use crate::xdr::ContractCostType;
296
297 pub struct Budget(pub(crate) crate::env::internal::budget::Budget);
325
326 impl Display for Budget {
327 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
328 writeln!(f, "{}", self.0)
329 }
330 }
331
332 impl Debug for Budget {
333 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
334 writeln!(f, "{:?}", self.0)
335 }
336 }
337
338 impl Budget {
339 pub(crate) fn new(b: crate::env::internal::budget::Budget) -> Self {
340 Self(b)
341 }
342
343 pub fn reset_default(&mut self) {
345 self.0.reset_default().unwrap();
346 }
347
348 pub fn reset_unlimited(&mut self) {
349 self.0.reset_unlimited().unwrap();
350 }
351
352 pub fn reset_limits(&mut self, cpu: u64, mem: u64) {
353 self.0.reset_limits(cpu, mem).unwrap();
354 }
355
356 pub fn reset_tracker(&mut self) {
357 self.0.reset_tracker().unwrap();
358 }
359
360 pub fn cpu_instruction_cost(&self) -> u64 {
365 self.0.get_cpu_insns_consumed().unwrap()
366 }
367
368 pub fn memory_bytes_cost(&self) -> u64 {
373 self.0.get_mem_bytes_consumed().unwrap()
374 }
375
376 pub fn tracker(&self, cost_type: ContractCostType) -> CostTracker {
385 self.0.get_tracker(cost_type).unwrap()
386 }
387
388 pub fn print(&self) {
390 println!("{}", self.0);
391 }
392 }
393}
394
395pub trait Events {
397 fn all(&self) -> Vec<(crate::Address, Vec<Val>, Val)>;
404}
405
406pub trait Logs {
408 fn all(&self) -> std::vec::Vec<String>;
410 fn print(&self);
412}
413
414pub trait BytesN<const N: usize> {
416 fn random(env: &Env) -> crate::BytesN<N>;
420}
421
422pub(crate) fn random<const N: usize>() -> [u8; N] {
426 use rand::RngCore;
427 let mut arr = [0u8; N];
428 rand::thread_rng().fill_bytes(&mut arr);
429 arr
430}
431
432pub trait Address {
433 fn generate(env: &Env) -> crate::Address;
439}
440
441pub trait Deployer {
442 fn get_contract_instance_ttl(&self, contract: &crate::Address) -> u32;
450
451 fn get_contract_code_ttl(&self, contract: &crate::Address) -> u32;
459}
460
461pub use xdr::AccountFlags as IssuerFlags;
462
463#[derive(Clone)]
464pub struct StellarAssetIssuer {
465 env: Env,
466 account_id: xdr::AccountId,
467}
468
469impl StellarAssetIssuer {
470 pub(crate) fn new(env: Env, account_id: xdr::AccountId) -> Self {
471 Self { env, account_id }
472 }
473
474 pub fn flags(&self) -> u32 {
476 self.env
477 .host()
478 .with_mut_storage(|storage| {
479 let k = Rc::new(xdr::LedgerKey::Account(xdr::LedgerKeyAccount {
480 account_id: self.account_id.clone(),
481 }));
482
483 let entry = storage.get(
484 &k,
485 soroban_env_host::budget::AsBudget::as_budget(self.env.host()),
486 )?;
487
488 match entry.data {
489 xdr::LedgerEntryData::Account(ref e) => Ok(e.flags.clone()),
490 _ => panic!("expected account entry but got {:?}", entry.data),
491 }
492 })
493 .unwrap()
494 }
495
496 pub fn set_flag(&self, flag: IssuerFlags) {
498 self.overwrite_issuer_flags(self.flags() | (flag as u32))
499 }
500
501 pub fn clear_flag(&self, flag: IssuerFlags) {
503 self.overwrite_issuer_flags(self.flags() & (!(flag as u32)))
504 }
505
506 pub fn address(&self) -> crate::Address {
507 xdr::ScAddress::Account(self.account_id.clone())
508 .try_into_val(&self.env.clone())
509 .unwrap()
510 }
511
512 fn overwrite_issuer_flags(&self, flags: u32) {
517 if u64::from(flags) > xdr::MASK_ACCOUNT_FLAGS_V17 {
518 panic!(
519 "issuer flags value must be at most {}",
520 xdr::MASK_ACCOUNT_FLAGS_V17
521 );
522 }
523
524 self.env
525 .host()
526 .with_mut_storage(|storage| {
527 let k = Rc::new(xdr::LedgerKey::Account(xdr::LedgerKeyAccount {
528 account_id: self.account_id.clone(),
529 }));
530
531 let mut entry = storage
532 .get(
533 &k,
534 soroban_env_host::budget::AsBudget::as_budget(self.env.host()),
535 )?
536 .as_ref()
537 .clone();
538
539 match entry.data {
540 xdr::LedgerEntryData::Account(ref mut e) => e.flags = flags,
541 _ => panic!("expected account entry but got {:?}", entry.data),
542 }
543
544 storage.put(
545 &k,
546 &Rc::new(entry),
547 None,
548 soroban_env_host::budget::AsBudget::as_budget(self.env.host()),
549 )?;
550 Ok(())
551 })
552 .unwrap();
553 }
554}
555
556pub struct StellarAssetContract {
557 address: crate::Address,
558 issuer: StellarAssetIssuer,
559}
560
561impl StellarAssetContract {
562 pub(crate) fn new(address: crate::Address, issuer: StellarAssetIssuer) -> Self {
563 Self { address, issuer }
564 }
565
566 pub fn address(&self) -> crate::Address {
567 self.address.clone()
568 }
569
570 pub fn issuer(&self) -> StellarAssetIssuer {
571 self.issuer.clone()
572 }
573}