soroban_env_host/
testutils.rs

1use crate::e2e_invoke::ledger_entry_to_ledger_key;
2use crate::storage::EntryWithLiveUntil;
3use crate::ErrorHandler;
4use crate::{
5    budget::Budget,
6    builtin_contracts::testutils::create_account,
7    storage::{SnapshotSource, Storage},
8    xdr::{
9        AccountId, ContractCostType, LedgerEntry, LedgerKey, PublicKey, ScAddress, ScVal, ScVec,
10        Uint256,
11    },
12    AddressObject, BytesObject, Env, EnvBase, Host, HostError, LedgerInfo, MeteredOrdMap,
13    StorageType, SymbolSmall, Val, VecObject,
14};
15use ed25519_dalek::SigningKey;
16use rand::RngCore;
17use std::panic::{catch_unwind, set_hook, take_hook, UnwindSafe};
18use std::{cell::Cell, collections::BTreeMap, rc::Rc, sync::Once};
19
20/// Catch panics while suppressing the default panic hook that prints to the
21/// console.
22///
23/// For the purposes of test reporting we don't want every panicking (but
24/// caught) contract call to print to the console. This requires overriding
25/// the panic hook, a global resource. This is an awkward thing to do with
26/// tests running in parallel.
27///
28/// This function lazily performs a one-time wrapping of the existing panic
29/// hook. It then uses a thread local variable to track contract call depth.
30/// If a panick occurs during a contract call the original hook is not
31/// called, otherwise it is called.
32pub fn call_with_suppressed_panic_hook<C, R>(closure: C) -> std::thread::Result<R>
33where
34    C: FnOnce() -> R + UnwindSafe,
35{
36    thread_local! {
37        static TEST_CONTRACT_CALL_COUNT: Cell<u64> = const { Cell::new(0) };
38    }
39
40    static WRAP_PANIC_HOOK: Once = Once::new();
41
42    WRAP_PANIC_HOOK.call_once(|| {
43        let existing_panic_hook = take_hook();
44        set_hook(Box::new(move |info| {
45            let calling_test_contract = TEST_CONTRACT_CALL_COUNT.with(|c| c.get() != 0);
46            if !calling_test_contract {
47                existing_panic_hook(info)
48            }
49        }))
50    });
51
52    TEST_CONTRACT_CALL_COUNT.with(|c| {
53        let old_count = c.get();
54        let new_count = old_count.checked_add(1).expect("overflow");
55        c.set(new_count);
56    });
57
58    let res = catch_unwind(closure);
59
60    TEST_CONTRACT_CALL_COUNT.with(|c| {
61        let old_count = c.get();
62        let new_count = old_count.checked_sub(1).expect("overflow");
63        c.set(new_count);
64    });
65
66    res
67}
68
69// Test utilities for the host, used in various tests in sub-modules.
70pub trait AsScVal {
71    fn as_scval(&self) -> ScVal;
72}
73
74impl AsScVal for u32 {
75    fn as_scval(&self) -> ScVal {
76        ScVal::U32(*self)
77    }
78}
79
80impl AsScVal for i32 {
81    fn as_scval(&self) -> ScVal {
82        ScVal::I32(*self)
83    }
84}
85
86impl AsScVal for u64 {
87    fn as_scval(&self) -> ScVal {
88        ScVal::U64(*self)
89    }
90}
91
92impl AsScVal for i64 {
93    fn as_scval(&self) -> ScVal {
94        ScVal::I64(*self)
95    }
96}
97
98impl AsScVal for ScVec {
99    fn as_scval(&self) -> ScVal {
100        ScVal::Vec(Some(self.clone()))
101    }
102}
103
104pub fn generate_account_id(host: &Host) -> AccountId {
105    AccountId(PublicKey::PublicKeyTypeEd25519(Uint256(
106        generate_bytes_array(host),
107    )))
108}
109
110pub fn generate_bytes_array(host: &Host) -> [u8; 32] {
111    let mut bytes: [u8; 32] = Default::default();
112    host.with_test_prng(|chacha| {
113        chacha.fill_bytes(&mut bytes);
114        Ok(())
115    })
116    .unwrap();
117    bytes
118}
119
120pub struct MockSnapshotSource(BTreeMap<Rc<LedgerKey>, (Rc<LedgerEntry>, Option<u32>)>);
121
122impl MockSnapshotSource {
123    pub fn new() -> Self {
124        Self(BTreeMap::<Rc<LedgerKey>, (Rc<LedgerEntry>, Option<u32>)>::new())
125    }
126
127    pub fn from_entries(entries: Vec<(LedgerEntry, Option<u32>)>) -> Self {
128        let mut map = BTreeMap::<Rc<LedgerKey>, (Rc<LedgerEntry>, Option<u32>)>::new();
129        let dummy_budget = Budget::default();
130        for (e, maybe_ttl) in entries {
131            let key = Rc::new(ledger_entry_to_ledger_key(&e, &dummy_budget).unwrap());
132            map.insert(key, (Rc::new(e), maybe_ttl));
133        }
134        Self(map)
135    }
136}
137
138impl SnapshotSource for MockSnapshotSource {
139    fn get(&self, key: &Rc<LedgerKey>) -> Result<Option<EntryWithLiveUntil>, HostError> {
140        if let Some((entry, live_until)) = self.0.get(key) {
141            Ok(Some((Rc::clone(entry), *live_until)))
142        } else {
143            Ok(None)
144        }
145    }
146}
147
148#[cfg(test)]
149pub(crate) fn interface_meta_with_custom_versions(proto: u32, pre: u32) -> Vec<u8> {
150    use crate::xdr::{Limited, Limits, ScEnvMetaEntry, ScEnvMetaEntryInterfaceVersion, WriteXdr};
151    let entry = ScEnvMetaEntry::ScEnvMetaKindInterfaceVersion(ScEnvMetaEntryInterfaceVersion {
152        protocol: proto,
153        pre_release: pre,
154    });
155    let bytes = Vec::<u8>::new();
156    let mut w = Limited::new(bytes, Limits::none());
157    entry.write_xdr(&mut w).unwrap();
158    w.inner
159}
160
161impl Host {
162    pub const TEST_PRNG_SEED: &'static [u8; 32] = b"12345678901234567890123456789012";
163
164    pub fn set_test_prng(&self) {
165        self.set_base_prng_seed(*Self::TEST_PRNG_SEED).unwrap();
166    }
167
168    pub fn current_test_protocol() -> u32 {
169        let max_supported_protocol = crate::meta::INTERFACE_VERSION.protocol;
170        let min_supported_protocol = crate::host::MIN_LEDGER_PROTOCOL_VERSION;
171        if let Ok(vers) = std::env::var("TEST_PROTOCOL") {
172            let test_protocol = vers.parse().expect("parsing TEST_PROTOCOL");
173            if test_protocol >= min_supported_protocol && test_protocol <= max_supported_protocol {
174                test_protocol
175            } else if test_protocol > max_supported_protocol {
176                let next_advice = if cfg!(feature = "next") {
177                    ""
178                } else {
179                    " (consider building with --feature=next)"
180                };
181                panic!(
182                    "TEST_PROTOCOL={} is higher than the max supported protocol {}{}",
183                    test_protocol, max_supported_protocol, next_advice
184                );
185            } else {
186                panic!(
187                    "TEST_PROTOCOL={} is lower than the min supported protocol {}",
188                    test_protocol, min_supported_protocol
189                );
190            }
191        } else {
192            max_supported_protocol
193        }
194    }
195
196    pub fn set_test_ledger_info_with_current_test_protocol(&self) {
197        self.set_ledger_info(LedgerInfo {
198            protocol_version: Self::current_test_protocol(),
199            sequence_number: 0,
200            timestamp: 0,
201            network_id: [0; 32],
202            base_reserve: 0,
203            min_persistent_entry_ttl: 4096,
204            min_temp_entry_ttl: 16,
205            max_entry_ttl: 6_312_000,
206        })
207        .unwrap();
208    }
209
210    pub fn test_host() -> Self {
211        let host = Host::default();
212        host.set_test_ledger_info_with_current_test_protocol();
213        host
214    }
215
216    pub fn test_host_with_prng() -> Self {
217        let host = Self::test_host();
218        host.set_test_prng();
219        host
220    }
221
222    pub fn test_host_with_recording_footprint() -> Self {
223        let snapshot_source = Rc::<MockSnapshotSource>::new(MockSnapshotSource::new());
224        let storage = Storage::with_recording_footprint(snapshot_source);
225        let host = Host::with_storage_and_budget(storage, Budget::default());
226        host.set_test_ledger_info_with_current_test_protocol();
227        host.set_test_prng();
228        host
229    }
230
231    pub fn test_budget(self, cpu: u64, mem: u64) -> Self {
232        self.with_budget(|budget| {
233            budget.reset_limits(cpu, mem)?; // something big but finite that we may exceed
234            budget.reset_models()?;
235            Ok(())
236        })
237        .unwrap();
238        self
239    }
240
241    pub fn enable_model(
242        self,
243        ty: ContractCostType,
244        const_cpu: u64,
245        lin_cpu: u64,
246        const_mem: u64,
247        lin_mem: u64,
248    ) -> Self {
249        self.with_budget(|budget| {
250            budget.override_model_with_unscaled_params(ty, const_cpu, lin_cpu, const_mem, lin_mem)
251        })
252        .unwrap();
253        self
254    }
255
256    pub fn test_scvec<T: AsScVal>(&self, vals: &[T]) -> Result<ScVec, HostError> {
257        let v: Vec<ScVal> = vals.iter().map(|x| x.as_scval()).collect();
258        self.map_err(v.try_into())
259    }
260
261    pub fn test_vec_obj<T: AsScVal>(&self, vals: &[T]) -> Result<VecObject, HostError> {
262        let v = self.test_scvec(vals)?;
263        Ok(self.to_host_val(&ScVal::Vec(Some(v)))?.try_into()?)
264    }
265
266    pub fn test_vec_val<T: AsScVal>(&self, vals: &[T]) -> Result<Val, HostError> {
267        let v = self.test_scvec(vals)?;
268        self.to_host_val(&ScVal::Vec(Some(v)))
269    }
270
271    pub fn test_bin_scobj(&self, vals: &[u8]) -> Result<ScVal, HostError> {
272        Ok(ScVal::Bytes(self.map_err(vals.to_vec().try_into())?))
273    }
274
275    pub fn test_bin_obj(&self, vals: &[u8]) -> Result<BytesObject, HostError> {
276        let scval: ScVal = self.test_bin_scobj(vals)?;
277        let val: Val = self.to_host_val(&scval)?;
278        Ok(val.try_into()?)
279    }
280
281    // Registers a contract with provided Wasm code and returns the registered
282    // contract's address.
283    // The contract address deterministically depends on the input account and
284    // salt, so this can be used with enforcing ledger footprint (but the
285    // footprint still has to be specified outside of this).
286    pub fn register_test_contract_wasm_from_source_account(
287        &self,
288        contract_wasm: &[u8],
289        account: AccountId,
290        salt: [u8; 32],
291    ) -> Result<AddressObject, HostError> {
292        let _span = tracy_span!("register_test_contract_wasm_from_source_account");
293        #[cfg(any(test, feature = "testutils"))]
294        let _invocation_meter_scope = self.maybe_meter_invocation(
295            crate::host::invocation_metering::MeteringInvocation::CreateContractEntryPoint,
296        );
297
298        // Use source account-based auth in order to avoid using nonces which
299        // won't work well with enforcing ledger footprint.
300        let prev_source_account = self.source_account_id()?;
301        // Use recording auth to skip specifying the auth payload.
302        let prev_auth_manager = self.snapshot_auth_manager()?;
303        self.switch_to_recording_auth_inherited_from_snapshot(&prev_auth_manager)?;
304
305        let wasm_hash = self.upload_wasm(self.bytes_new_from_slice(contract_wasm)?)?;
306        self.set_source_account(account.clone())?;
307        let contract_address = self.create_contract(
308            self.add_host_object(ScAddress::Account(account.clone()))?,
309            wasm_hash,
310            self.bytes_new_from_slice(&salt)?,
311        )?;
312        if let Some(prev_account) = prev_source_account {
313            self.set_source_account(prev_account)?;
314        }
315        self.set_auth_manager(prev_auth_manager)?;
316        Ok(contract_address)
317    }
318
319    // Registers a contract with provided Wasm code and returns the registered
320    // contract's address.
321    // The contract address will be generated randomly, so this won't work with
322    // enforcing ledger footprint.
323    pub fn register_test_contract_wasm(&self, contract_wasm: &[u8]) -> AddressObject {
324        self.register_test_contract_wasm_from_source_account(
325            contract_wasm,
326            generate_account_id(self),
327            generate_bytes_array(self),
328        )
329        .unwrap()
330    }
331
332    pub fn new_recording_fuzz_host(
333        contract_wasms: &[&[u8]],
334        data_keys: &BTreeMap<ScVal, (StorageType, bool)>,
335        n_signers: usize,
336    ) -> (Host, Vec<AddressObject>, Vec<ed25519_dalek::SigningKey>) {
337        use crate::builtin_contracts::testutils::{
338            generate_signing_key, signing_key_to_account_id,
339        };
340
341        let host = Self::test_host_with_recording_footprint();
342        host.switch_to_recording_auth(false).unwrap();
343        host.with_budget(|budget| {
344            budget.reset_unlimited()?;
345            Ok(())
346        })
347        .unwrap();
348        let mut contract_addresses = Vec::new();
349        for contract_wasm in contract_wasms.iter() {
350            contract_addresses.push(host.register_test_contract_wasm(contract_wasm));
351        }
352        let ScAddress::Contract(contract_hash) =
353            host.scaddress_from_address(contract_addresses[0]).unwrap()
354        else {
355            panic!()
356        };
357
358        let test = SymbolSmall::try_from_str("test").unwrap();
359
360        // First step: insert all the data values in question into the storage map.
361        host.with_test_contract_frame(contract_hash.clone(), test.into(), || {
362            for (k, (t, _)) in data_keys.iter() {
363                let v = host.to_host_val(k).unwrap();
364                host.put_contract_data(v, v, *t).unwrap();
365            }
366            Ok(Val::VOID.into())
367        })
368        .unwrap();
369
370        // Second step: generate some accounts to sign things with.
371        let signing_keys = (0..n_signers)
372            .map(|_| generate_signing_key(&host))
373            .collect();
374        for signing_key in &signing_keys {
375            create_account(
376                &host,
377                &signing_key_to_account_id(signing_key),
378                vec![(&signing_key, 1)],
379                100_000_000,
380                1,
381                [1, 0, 0, 0],
382                None,
383                None,
384                0,
385            )
386        }
387
388        (host, contract_addresses, signing_keys)
389    }
390
391    pub fn switch_fuzz_host_to_enforcing(
392        &self,
393        data_keys: &BTreeMap<ScVal, (StorageType, bool)>,
394        signing_keys: &[ed25519_dalek::SigningKey],
395    ) {
396        use crate::builtin_contracts::testutils::TestSigner;
397        use crate::xdr::{
398            HashIdPreimage, HashIdPreimageSorobanAuthorization, SorobanAddressCredentials,
399            SorobanAuthorizationEntry, SorobanCredentials,
400        };
401        self.with_budget(|budget| {
402            budget.reset_unlimited()?;
403            Ok(())
404        })
405        .unwrap();
406
407        // Modify footprint entries to read-only-ness as required, synthesize
408        // empty storage-map entries that were accessed by keys the contract made
409        // up, and switch to enforcing mode.
410        self.with_mut_storage(|storage| {
411            storage.footprint.0 = MeteredOrdMap::from_exact_iter(
412                storage
413                    .footprint
414                    .0
415                    .iter(self.budget_ref())
416                    .unwrap()
417                    .map(|(k, accesstype)| {
418                        let mut accesstype = *accesstype;
419                        if let LedgerKey::ContractData(k) = k.as_ref() {
420                            if let Some((_, ro)) = data_keys.get(&k.key) {
421                                if *ro {
422                                    accesstype = crate::storage::AccessType::ReadOnly;
423                                } else {
424                                    accesstype = crate::storage::AccessType::ReadWrite;
425                                }
426                            }
427                        }
428                        (k.clone(), accesstype)
429                    }),
430                self.budget_ref(),
431            )
432            .unwrap();
433
434            // Synthesize empty entries for anything the contract made up (these
435            // will be in the footprint but not yet in the map, which is an
436            // invariant violation we need to repair here).
437            let mut map = BTreeMap::new();
438            for (k, v) in storage.map.iter(self.budget_ref()).unwrap() {
439                map.insert(k.clone(), v.clone());
440            }
441            for (k, _) in storage.footprint.0.iter(self.budget_ref()).unwrap() {
442                if !map.contains_key(k) {
443                    map.insert(k.clone(), None);
444                }
445            }
446            // Reset any nonces so they can be consumed.
447            for (k, v) in map.iter_mut() {
448                if let LedgerKey::ContractData(k) = k.as_ref() {
449                    if let ScVal::LedgerKeyNonce(_) = &k.key {
450                        *v = None;
451                    }
452                }
453            }
454            storage.map = MeteredOrdMap::from_exact_iter(
455                map.iter().map(|(k, v)| (k.clone(), v.clone())),
456                self.budget_ref(),
457            )
458            .unwrap();
459            storage.mode = crate::storage::FootprintMode::Enforcing;
460            Ok(())
461        })
462        .unwrap();
463
464        // Sign and install auth entries for all the recorded auth payloads.
465        let mut auth_entries = Vec::new();
466        let account_signers = signing_keys
467            .iter()
468            .map(TestSigner::account)
469            .collect::<Vec<_>>();
470        for payload in self.get_recorded_auth_payloads().unwrap().iter() {
471            for signer in account_signers.iter() {
472                let Some(address) = &payload.address else {
473                    continue;
474                };
475                let Some(nonce) = payload.nonce else { continue };
476                if *address == ScAddress::Account(signer.account_id()) {
477                    let address = address.clone();
478                    let signature_expiration_ledger =
479                        u32::from(self.get_ledger_sequence().unwrap()) + 10000;
480                    let network_id = self
481                        .with_ledger_info(|li: &LedgerInfo| Ok(li.network_id))
482                        .unwrap()
483                        .try_into()
484                        .unwrap();
485                    let signature_payload_preimage =
486                        HashIdPreimage::SorobanAuthorization(HashIdPreimageSorobanAuthorization {
487                            network_id,
488                            invocation: payload.invocation.clone(),
489                            nonce,
490                            signature_expiration_ledger,
491                        });
492                    let signature_payload =
493                        self.metered_hash_xdr(&signature_payload_preimage).unwrap();
494                    let signature = signer.sign(&self, &signature_payload);
495                    let credentials = SorobanCredentials::Address(SorobanAddressCredentials {
496                        address: address.clone(),
497                        nonce: nonce,
498                        signature_expiration_ledger,
499                        signature,
500                    });
501                    let entry = SorobanAuthorizationEntry {
502                        credentials,
503                        root_invocation: payload.invocation.clone(),
504                    };
505                    auth_entries.push(entry);
506                }
507            }
508        }
509        self.set_authorization_entries(auth_entries).unwrap();
510    }
511
512    #[cfg(all(test, feature = "testutils"))]
513    pub(crate) fn measured_call(
514        &self,
515        contract: AddressObject,
516        func: crate::Symbol,
517        args: VecObject,
518    ) -> Result<Val, HostError> {
519        use crate::{budget::AsBudget, host::TraceEvent};
520        use soroban_bench_utils::HostTracker;
521        use std::cell::RefCell;
522
523        let _span = tracy_span!("measured_call");
524        let budget = self.as_budget();
525        budget.reset_unlimited()?;
526        let ht = Rc::new(RefCell::new(HostTracker::new()));
527
528        if std::env::var("EXCLUDE_VM_INSTANTIATION").is_ok() {
529            let ht2 = ht.clone();
530            let budget2 = budget.clone();
531            self.set_trace_hook(Some(Rc::new(move |_, evt| {
532                if let TraceEvent::PushCtx(_) = evt {
533                    budget2.reset_unlimited()?;
534                    ht2.borrow_mut().start(None);
535                }
536                Ok(())
537            })))?;
538        } else {
539            ht.borrow_mut().start(None);
540        }
541        let val = self.call(contract, func, args);
542        self.set_trace_hook(None)?;
543
544        let (cpu_actual, mem_actual, time_nsecs) = Rc::into_inner(ht).unwrap().into_inner().stop();
545
546        let cpu_metered = budget
547            .get_cpu_insns_consumed()
548            .expect("unable to retrieve cpu consumed");
549        let mem_metered = budget
550            .get_mem_bytes_consumed()
551            .expect("unable to retrieve mem consumed");
552
553        let cpu_diff = (cpu_metered - cpu_actual) as i64;
554        let cpu_metered_diff_percent = 100 * cpu_diff / (cpu_metered as i64).max(1);
555        let mem_diff = (mem_metered - mem_actual) as i64;
556        let mem_metered_diff_percent = 100 * mem_diff / (mem_metered as i64).max(1);
557        let metered_insn_nsecs_ratio: f64 = (cpu_metered as f64) / (time_nsecs as f64).max(1.0);
558        let actual_insn_nsecs_ratio: f64 = (cpu_actual as f64) / (time_nsecs as f64).max(1.0);
559
560        println!();
561        println!(
562            "metered cpu insns: {}, actual cpu insns {}, diff: {} ({:.3}%)",
563            cpu_metered, cpu_actual, cpu_diff, cpu_metered_diff_percent
564        );
565        println!(
566            "metered mem bytes: {}, actual mem bytes {}, diff: {} ({:.3}%)",
567            mem_metered, mem_actual, mem_diff, mem_metered_diff_percent
568        );
569        println!("time_nsecs: {}", time_nsecs);
570        println!(
571            "metered cpu_insn/time_nsecs ratio: {:.3}",
572            metered_insn_nsecs_ratio
573        );
574        println!(
575            "actual cpu_insn/time_nsecs ratio: {:.3}",
576            actual_insn_nsecs_ratio
577        );
578        println!();
579
580        val
581    }
582}
583
584#[cfg(test)]
585pub(crate) mod wasm {
586    use crate::{Symbol, Tag, U32Val, Val};
587    use soroban_synth_wasm::{Arity, FuncRef, LocalRef, ModEmitter, Operand};
588    use wasm_encoder::{ConstExpr, Elements, RefType};
589
590    pub(crate) fn wasm_module_with_4n_insns(n: usize) -> Vec<u8> {
591        let mut fe = ModEmitter::default_with_test_protocol().func(Arity(1), 0);
592        let arg = fe.args[0];
593        fe.push(Operand::Const64(1));
594        for i in 0..n {
595            fe.push(arg.0);
596            fe.push(Operand::Const64(i as i64));
597            fe.i64_mul();
598            fe.i64_add();
599        }
600        fe.drop();
601        fe.push(Symbol::try_from_small_str("pass").unwrap());
602        fe.finish_and_export("test").finish()
603    }
604
605    pub(crate) fn wasm_module_with_n_funcs_no_export(n: usize) -> Vec<u8> {
606        let mut me = ModEmitter::default_with_test_protocol();
607        for _i in 0..n {
608            let mut fe = me.func(Arity(0), 0);
609            fe.push(Symbol::try_from_small_str("pass").unwrap());
610            me = fe.finish().0;
611        }
612        me.finish()
613    }
614
615    pub(crate) fn wasm_module_with_repeated_exporting_the_same_func(n: usize) -> Vec<u8> {
616        let me = ModEmitter::default_with_test_protocol();
617        let mut fe = me.func(Arity(0), 0);
618        fe.push(Symbol::try_from_small_str("pass").unwrap());
619        let (mut me, fid) = fe.finish();
620        for i in 0..n {
621            me.export_func(fid, format!("test{}", i).as_str());
622        }
623        me.finish_no_validate()
624    }
625
626    pub(crate) fn wasm_module_with_mem_grow(n_pages: usize) -> Vec<u8> {
627        let mut fe = ModEmitter::default_with_test_protocol().func(Arity(0), 0);
628        fe.push(Operand::Const32(n_pages as i32));
629        fe.memory_grow();
630        fe.drop();
631        fe.push(Symbol::try_from_small_str("pass").unwrap());
632        fe.finish_and_export("test").finish()
633    }
634
635    pub(crate) fn wasm_module_with_linear_memory_logging() -> Vec<u8> {
636        let mut me = ModEmitter::default_with_test_protocol();
637        // log_from_linear_memory
638        let f0 = me.import_func("x", "_", Arity(4));
639        // the caller
640        let mut fe = me.func(Arity(4), 0);
641        fe.push(Operand::Local(LocalRef(0)));
642        fe.push(Operand::Local(LocalRef(1)));
643        fe.push(Operand::Local(LocalRef(2)));
644        fe.push(Operand::Local(LocalRef(3)));
645        fe.call_func(f0);
646        fe.drop();
647        fe.push(Symbol::try_from_small_str("pass").unwrap());
648        fe.finish_and_export("test").finish()
649    }
650
651    pub(crate) fn wasm_module_with_unreachable() -> Vec<u8> {
652        let me = ModEmitter::default_with_test_protocol();
653        let mut fe = me.func(Arity(0), 0);
654        fe.trap();
655        fe.finish_and_export("test").finish()
656    }
657
658    pub(crate) fn wasm_module_with_indirect_call() -> Vec<u8> {
659        let mut me = ModEmitter::default_with_test_protocol();
660        // an imported function
661        let f0 = me.import_func("t", "_", Arity(0));
662        // a local wasm function
663        let mut fe = me.func(Arity(0), 0);
664        fe.push(Symbol::try_from_small_str("pass").unwrap());
665        let (me, f1) = fe.finish();
666        // another local wasm function
667        let mut fe = me.func(Arity(0), 0);
668        fe.push(Symbol::try_from_small_str("pass2").unwrap());
669        let (mut me, f2) = fe.finish();
670        // store in table
671        me.define_elem_funcs(&[f0, f1, f2]);
672        let ty = me.get_fn_type(Arity(0), Arity(1));
673        // the caller
674        fe = me.func(Arity(1), 0);
675        fe.local_get(LocalRef(0));
676        fe.i32_wrap_i64();
677        // fe.i32_const(0);
678        fe.call_func_indirect(ty);
679        fe.finish_and_export("test").finish()
680    }
681
682    pub(crate) fn wasm_module_with_div_by_zero() -> Vec<u8> {
683        let me = ModEmitter::default_with_test_protocol();
684        let mut fe = me.func(Arity(0), 0);
685        fe.push(Operand::Const64(123));
686        fe.push(Operand::Const64(0));
687        fe.i64_div_s();
688        fe.finish_and_export("test").finish()
689    }
690
691    pub(crate) fn wasm_module_with_integer_overflow() -> Vec<u8> {
692        let me = ModEmitter::default_with_test_protocol();
693        let mut fe = me.func(Arity(0), 0);
694        fe.push(Operand::Const64(i64::MIN));
695        fe.push(Operand::Const64(-1));
696        // interestingly the only operation that can trigger `IntegerOverflow`
697        // is an overflowing division. Other arithmatic opeartions add, sub, mul
698        // are wrapping.
699        fe.i64_div_s();
700        fe.finish_and_export("test").finish()
701    }
702
703    pub(crate) fn wasm_module_with_user_specified_initial_size(
704        mem_pages: u32,
705        elem_count: u32,
706    ) -> Vec<u8> {
707        let me = ModEmitter::from_configs(mem_pages, elem_count);
708        // a local wasm function
709        let mut fe = me.func(Arity(0), 0);
710        fe.push(Symbol::try_from_small_str("pass").unwrap());
711        fe.finish_and_export("test").finish()
712    }
713
714    pub(crate) fn wasm_module_with_large_data_segment(
715        mem_pages: u32,
716        mem_offset: u32,
717        len: u32,
718    ) -> Vec<u8> {
719        let mut me = ModEmitter::from_configs(mem_pages, 128);
720        me.define_data_segment(mem_offset, vec![0; len as usize]);
721        // a local wasm function
722        let mut fe = me.func(Arity(0), 0);
723        fe.push(Symbol::try_from_small_str("pass").unwrap());
724        fe.finish_and_export("test").finish()
725    }
726
727    pub(crate) fn wasm_module_with_multiple_data_segments(
728        num_pages: u32,
729        num_sgmts: u32,
730        seg_size: u32,
731    ) -> Vec<u8> {
732        let mut me = ModEmitter::from_configs(num_pages, 128);
733        let mem_len = num_pages * 0x10_000;
734        // we just make sure the total memory can fit one segments the segments
735        // will just cycle through the space and possibly override earlier ones
736        let max_segments = (mem_len / seg_size.max(1)).max(1);
737        for i in 0..num_sgmts % max_segments {
738            me.define_data_segment(i, vec![0; seg_size as usize]);
739        }
740        // a local wasm function
741        let mut fe = me.func(Arity(0), 0);
742        fe.push(Symbol::try_from_small_str("pass").unwrap());
743        fe.finish_and_export("test").finish()
744    }
745
746    pub(crate) fn wasm_module_with_large_bytes_from_linear_memory(
747        num_bytes: u32,
748        initial: u8,
749    ) -> Vec<u8> {
750        let num_pages = num_bytes / 0x10_000 + 1;
751        let mut me = ModEmitter::from_configs(num_pages, 128);
752        me.define_data_segment(0, vec![initial; num_bytes as usize]);
753        let mut fe = me.func(Arity(0), 0);
754        fe.bytes_new_from_linear_memory(U32Val::from(0), U32Val::from(num_bytes));
755        fe.finish_and_export("test").finish()
756    }
757
758    pub(crate) fn wasm_module_with_large_vector_from_linear_memory(
759        num_vals: u32,
760        initial: Val,
761    ) -> Vec<u8> {
762        let num_pages = num_vals * 8 / 0x10_000 + 1;
763        let mut me = ModEmitter::from_configs(num_pages, 128);
764        let bytes: Vec<u8> = (0..num_vals)
765            .map(|_| initial.get_payload().to_le_bytes())
766            .flat_map(|a| a.into_iter())
767            .collect();
768        me.define_data_segment(0, bytes);
769        let mut fe = me.func(Arity(0), 0);
770        fe.vec_new_from_linear_memory(U32Val::from(0), U32Val::from(num_vals));
771        fe.finish_and_export("test").finish()
772    }
773
774    pub(crate) fn wasm_module_with_large_map_from_linear_memory(
775        num_vals: u32,
776        initial: Val,
777    ) -> Vec<u8> {
778        // slice is 8 bytes, we choose 8 byte key, val is 8 bytes
779        let num_pages = (num_vals * 8) * 3 / 0x10_000 + 1;
780        let mut me = ModEmitter::from_configs(num_pages, 128);
781
782        let key_bytes: Vec<u8> = (0..num_vals)
783            .map(|i| format!("{:0>width$}", i, width = 8))
784            .flat_map(|s| s.into_bytes().into_iter())
785            .collect();
786
787        let val_bytes: Vec<u8> = (0..num_vals)
788            .map(|_| initial.get_payload().to_le_bytes())
789            .flat_map(|a| a.into_iter())
790            .collect();
791
792        let slices: Vec<u8> = (0..num_vals)
793            .map(|ptr| {
794                let slice = 8_u64 << 32 | (ptr * 8) as u64;
795                slice.to_le_bytes()
796            })
797            .flat_map(|s| s.into_iter())
798            .collect();
799
800        let bytes: Vec<u8> = key_bytes
801            .into_iter()
802            .chain(val_bytes)
803            .chain(slices)
804            .collect();
805
806        me.define_data_segment(0, bytes);
807        let mut fe = me.func(Arity(0), 0);
808
809        let vals_pos = U32Val::from(num_vals * 8);
810        let keys_pos = U32Val::from(num_vals * 16);
811        fe.map_new_from_linear_memory(keys_pos, vals_pos, U32Val::from(num_vals));
812
813        fe.finish_and_export("test").finish()
814    }
815
816    pub(crate) fn wasm_module_with_data_count(
817        num_sgmts: u32,
818        seg_size: u32,
819        data_count: u32,
820    ) -> Vec<u8> {
821        // first calculate the number of memory pages needed to fit all the data
822        // segments non-overlapping
823        let pages = num_sgmts * seg_size / 0x10_000 + 1;
824        let mut me = ModEmitter::from_configs(pages, 128);
825        for i in 0..num_sgmts {
826            me.define_data_segment(i * seg_size, vec![7; seg_size as usize]);
827        }
828        // define the data count, if count != num_sgmts, then the validator is
829        // supposed to catch it
830        me.data_count(data_count);
831        me.finish_no_validate()
832    }
833
834    // Emit a wasm module that uses post-MVP WASM features. Specifically
835    // mutable-globals and sign-ext.
836    pub fn post_mvp_wasm_module() -> Vec<u8> {
837        let mut me = ModEmitter::default_with_test_protocol();
838
839        // Emit an exported mutable global
840        me.define_global_i64(-100, true, Some("global"));
841
842        let mut fe = me.func(Arity(0), 0);
843        fe.i64_const(0x0000_0000_ffff_abcd_u64 as i64);
844
845        // Emit an op from the new sign-ext repertoire
846        fe.i64_extend32s();
847
848        // Turn this into a I64Small
849        fe.i64_const(8);
850        fe.i64_shl();
851        fe.i64_const(Tag::I64Small as i64);
852        fe.i64_or();
853
854        fe.finish_and_export("test").finish()
855    }
856
857    pub fn empty_wasm_module() -> Vec<u8> {
858        ModEmitter::new().finish()
859    }
860
861    pub fn wasm_module_with_custom_section(name: &str, data: &[u8]) -> Vec<u8> {
862        let mut me = ModEmitter::new();
863        me.custom_section(name, data);
864        me.finish()
865    }
866
867    pub fn wasm_module_with_floating_point_ops() -> Vec<u8> {
868        let me = ModEmitter::default_with_test_protocol();
869        let mut fe = me.func(Arity(0), 0);
870        fe.f64_const(1.1f64);
871        fe.f64_const(2.2f64);
872        fe.f64_add();
873        fe.drop();
874        fe.push(Symbol::try_from_small_str("pass").unwrap());
875        fe.finish_and_export("test").finish()
876    }
877
878    pub fn wasm_module_with_multiple_memories() -> Vec<u8> {
879        let mut me = ModEmitter::new();
880        me.memory(1, None, false, false);
881        me.memory(1, None, false, false);
882        me.finish_no_validate()
883    }
884
885    pub fn wasm_module_lying_about_import_function_type() -> Vec<u8> {
886        let mut me = ModEmitter::default_with_test_protocol();
887        // function {"t", "_"} is "dummy0", taking 0 arguments
888        // this will not pass the wasmi linker
889        me.import_func("t", "_", Arity(1));
890        me.finish()
891    }
892
893    pub fn wasm_module_importing_nonexistent_function() -> Vec<u8> {
894        let mut me = ModEmitter::default_with_test_protocol();
895        me.import_func("t", "z", Arity(1));
896        me.finish()
897    }
898
899    pub fn wasm_module_with_duplicate_function_import(n: u32) -> Vec<u8> {
900        let mut me = ModEmitter::default_with_test_protocol();
901        for _ in 0..n {
902            me.import_func_no_check("t", "_", Arity(0));
903        }
904        me.finish_no_validate()
905    }
906
907    pub fn wasm_module_with_nonexistent_function_export() -> Vec<u8> {
908        let mut me = ModEmitter::default_with_test_protocol();
909        me.import_func("t", "_", Arity(0));
910        let mut fe = me.func(Arity(0), 0);
911        fe.push(Symbol::try_from_small_str("pass").unwrap());
912        let (mut me, fid) = fe.finish();
913        // exporting a function I defined is okay
914        me.export_func(fid, "test");
915        // exporting an imported function is also okay, although weird
916        me.export_func(FuncRef(0), "test0");
917        // importing an non-existent function will not pass validation
918        me.export_func(FuncRef(100), "test100");
919        me.finish_no_validate()
920    }
921
922    pub(crate) fn wasm_module_with_nonexistent_func_element() -> Vec<u8> {
923        let mut me = ModEmitter::default_with_test_protocol();
924        // an imported function
925        let f0 = me.import_func("t", "_", Arity(0));
926        // a local wasm function
927        let mut fe = me.func(Arity(0), 0);
928        fe.push(Symbol::try_from_small_str("pass").unwrap());
929        let (me, f1) = fe.finish();
930        // another local wasm function
931        let mut fe = me.func(Arity(0), 0);
932        fe.push(Symbol::try_from_small_str("pass2").unwrap());
933        let (mut me, f2) = fe.finish();
934        // store in table, FuncRef(100) is invalid
935        me.define_elem_funcs(&[f0, f1, f2, FuncRef(100)]);
936        me.finish_no_validate()
937    }
938
939    pub(crate) fn wasm_module_with_start_function() -> Vec<u8> {
940        let me = ModEmitter::default_with_test_protocol();
941        let fe = me.func_with_arity_and_ret(Arity(0), Arity(0), 0);
942        let (mut me, fid) = fe.finish();
943        me.export_func(fid, "start");
944        me.start(fid);
945        me.finish_no_validate()
946    }
947
948    pub(crate) fn wasm_module_with_multi_value() -> Vec<u8> {
949        let me = ModEmitter::default_with_test_protocol();
950        let mut fe = me.func_with_arity_and_ret(Arity(0), Arity(2), 0);
951        fe.push(Symbol::try_from_small_str("pass1").unwrap());
952        fe.push(Symbol::try_from_small_str("pass2").unwrap());
953        fe.finish_and_export("test").finish()
954    }
955
956    // if n > m, we have oob elements
957    pub(crate) fn wasm_module_large_elements(m: u32, n: u32) -> Vec<u8> {
958        let mut me = ModEmitter::from_configs(1, m);
959        // an imported function
960        let f0 = me.import_func("t", "_", Arity(0));
961        me.define_elem_funcs(vec![f0; n as usize].as_slice());
962        me.finish()
963    }
964
965    pub(crate) fn wasm_module_various_constexpr_in_elements(case: u32) -> Vec<u8> {
966        let mut me = ModEmitter::default_with_test_protocol();
967        // an imported function
968        let f0 = me.import_func("t", "_", Arity(0));
969
970        match case {
971            0 =>
972            // err: wrong type, expect i32, passing i64
973            {
974                me.define_active_elements(
975                    None,
976                    &ConstExpr::i64_const(0),
977                    Elements::Functions(&[f0.0]),
978                )
979            }
980            // err: fp
981            1 => me.define_active_elements(
982                None,
983                &ConstExpr::f64_const(1.0),
984                Elements::Functions(&[f0.0]),
985            ),
986            // err: simd
987            2 => me.define_active_elements(
988                None,
989                &ConstExpr::v128_const(1),
990                Elements::Functions(&[f0.0]),
991            ),
992            // err: wrong type, expect i32, passing funcref
993            3 => me.define_active_elements(
994                None,
995                &ConstExpr::ref_func(f0.0),
996                Elements::Functions(&[f0.0]),
997            ),
998            _ => panic!("not a valid option"),
999        }
1000        me.finish_no_validate()
1001    }
1002
1003    pub(crate) fn wasm_module_large_globals(n: u32) -> Vec<u8> {
1004        let mut me = ModEmitter::default_with_test_protocol();
1005        for i in 0..n {
1006            me.define_global_i64(i as i64, true, None);
1007        }
1008        me.finish()
1009    }
1010
1011    pub(crate) fn wasm_module_various_constexr_in_global(case: u32) -> Vec<u8> {
1012        let mut me = ModEmitter::default_with_test_protocol();
1013        match case {
1014            0 =>
1015            // err: mismatch
1016            {
1017                me.define_global(wasm_encoder::ValType::I32, true, &ConstExpr::i64_const(1))
1018            }
1019            1 =>
1020            // err: fp
1021            {
1022                me.define_global(wasm_encoder::ValType::F32, true, &ConstExpr::f32_const(1.0))
1023            }
1024            2 =>
1025            // err: simd
1026            {
1027                me.define_global(wasm_encoder::ValType::V128, true, &ConstExpr::v128_const(1))
1028            }
1029            3 =>
1030            // okay: func
1031            {
1032                let fr = me.import_func("t", "_", Arity(0));
1033                me.define_global(
1034                    wasm_encoder::ValType::Ref(RefType::FUNCREF),
1035                    true,
1036                    &ConstExpr::ref_func(fr.0),
1037                )
1038            }
1039            _ => panic!("not a valid option"),
1040        }
1041        me.finish_no_validate()
1042    }
1043
1044    pub(crate) fn wasm_module_various_constexr_in_data_segment(case: u32) -> Vec<u8> {
1045        let mut me = ModEmitter::default_with_test_protocol();
1046        // an imported function
1047        let f0 = me.import_func("t", "_", Arity(0));
1048
1049        match case {
1050            0 =>
1051            // err: wrong type, expect i32, passing i64
1052            {
1053                me.define_active_data(0, &ConstExpr::i64_const(0), vec![0; 8]);
1054            }
1055            // err: fp
1056            1 => {
1057                me.define_active_data(0, &ConstExpr::f64_const(0.0), vec![0; 8]);
1058            }
1059            // err: simd
1060            2 => {
1061                me.define_active_data(0, &ConstExpr::v128_const(0), vec![0; 8]);
1062            }
1063            // err: wrong type, expect i32, passing funcref
1064            3 => {
1065                me.define_active_data(0, &ConstExpr::ref_func(f0.0), vec![0; 8]);
1066            }
1067            _ => panic!("not a valid option"),
1068        }
1069        me.finish_no_validate()
1070    }
1071
1072    pub(crate) fn wasm_module_with_extern_ref() -> Vec<u8> {
1073        let mut me = ModEmitter::new();
1074        me.table(RefType::EXTERNREF, 2, None);
1075        me.add_test_protocol_version_meta();
1076        me.finish_no_validate()
1077    }
1078
1079    pub(crate) fn wasm_module_with_additional_tables(n: u32) -> Vec<u8> {
1080        let mut me = ModEmitter::default_with_test_protocol();
1081        // by default, module already includes a table, here we are creating
1082        // additional ones
1083        for _i in 0..n {
1084            me.table(RefType::FUNCREF, 2, None);
1085        }
1086        // wasmparser has an limit of 100 tables. wasmi does not have such a limit
1087        me.finish_no_validate()
1088    }
1089
1090    // The only type allowed in the MVP is the function type. There are more
1091    // composite types defined by the GC proposal, which should not be allowed
1092    // and we will verify that in another test.
1093    pub(crate) fn wasm_module_with_many_func_types(n: u32) -> Vec<u8> {
1094        let mut me = ModEmitter::default_with_test_protocol();
1095        for _i in 0..n {
1096            // it is allowed to define the same types over and over
1097            me.add_fn_type_no_check(Arity(0), Arity(0));
1098        }
1099        me.finish()
1100    }
1101
1102    pub(crate) fn wasm_module_with_simd_add_i32x4() -> Vec<u8> {
1103        let me = ModEmitter::default_with_test_protocol();
1104        let mut fe = me.func(Arity(0), 0);
1105        // we load [u32, u32, u32, u32] x 2, add them and store back
1106        fe.i32_const(32); // ptr for storing the result
1107        fe.i32_const(0); // ptr for the first 4xi32
1108        fe.v128_load(0, 0);
1109        fe.i32_const(16); // ptr for the second 4xi32
1110        fe.v128_load(0, 0);
1111        fe.i32x4_add();
1112        fe.v128_store(0, 0);
1113        fe.push(Symbol::try_from_small_str("pass").unwrap());
1114        fe.finish_and_export("test").finish()
1115    }
1116
1117    pub(crate) fn wasm_module_calling_protocol_gated_host_fn(wasm_proto: u32) -> Vec<u8> {
1118        let mut me = ModEmitter::new();
1119        me.add_protocol_version_meta(wasm_proto);
1120        // protocol_gated_dummy
1121        let f0 = me.import_func("t", "0", Arity(0));
1122        // the caller
1123        let mut fe = me.func(Arity(0), 0);
1124        fe.call_func(f0);
1125        fe.finish_and_export("test").finish()
1126    }
1127
1128    pub(crate) fn wasm_module_with_a_bit_of_everything(wasm_proto: u32) -> Vec<u8> {
1129        let mut me = ModEmitter::new();
1130        me.add_protocol_version_meta(wasm_proto);
1131        me.table(RefType::FUNCREF, 128, None);
1132        me.memory(1, None, false, false);
1133        me.global(wasm_encoder::ValType::I64, true, &ConstExpr::i64_const(42));
1134        me.export("memory", wasm_encoder::ExportKind::Memory, 0);
1135        let _f0 = me.import_func("t", "_", Arity(0));
1136        let mut fe = me.func(Arity(0), 0);
1137        fe.push(Operand::Const64(1));
1138        fe.push(Operand::Const64(2));
1139        fe.i64_add();
1140        fe.drop();
1141        fe.push(Symbol::try_from_small_str("pass").unwrap());
1142        let (mut me, fid) = fe.finish();
1143        me.export_func(fid, "test");
1144        me.define_elem_funcs(&[fid]);
1145        me.define_data_segment(0x1234, vec![0; 512]);
1146        me.finish()
1147    }
1148}
1149
1150#[allow(clippy::type_complexity)]
1151pub fn simple_account_sign_fn<'a>(
1152    host: &'a Host,
1153    kp: &'a SigningKey,
1154) -> Box<dyn Fn(&[u8]) -> Val + 'a> {
1155    use crate::builtin_contracts::testutils::sign_payload_for_ed25519;
1156    Box::new(|payload: &[u8]| -> Val { sign_payload_for_ed25519(host, kp, payload).into() })
1157}