// SPDX-License-Identifier: Apache-2.0 #[cfg(feature = "soroban")] pub mod soroban_testcases; use solang::codegen::Options; use solang::file_resolver::FileResolver; use solang::sema::ast::Namespace; use solang::sema::diagnostics::Diagnostics; use solang::{compile, Target}; use soroban_sdk::testutils::Logs; use soroban_sdk::{vec, Address, ConstructorArgs, Env, Symbol, Val}; use std::ffi::OsStr; // TODO: register accounts, related balances, events, etc. pub struct SorobanEnv { env: Env, contracts: Vec
, compiler_diagnostics: Diagnostics, } pub fn build_solidity(src: &str, configure_env: F) -> SorobanEnv where F: FnOnce(&mut SorobanEnv), { let (wasm_blob, ns) = build_wasm(src); SorobanEnv::new_with_contract(wasm_blob, configure_env).insert_diagnostics(ns.diagnostics) } fn build_wasm(src: &str) -> (Vec, Namespace) { let tmp_file = OsStr::new("test.sol"); let mut cache = FileResolver::default(); cache.set_file_contents(tmp_file.to_str().unwrap(), src.to_string()); let opt = inkwell::OptimizationLevel::Default; let target = Target::Soroban; let (wasm, ns) = compile( tmp_file, &mut cache, target, &Options { opt_level: opt.into(), log_runtime_errors: true, log_prints: true, #[cfg(feature = "wasm_opt")] wasm_opt: Some(contract_build::OptimizationPasses::Z), soroban_version: None, ..Default::default() }, std::vec!["unknown".to_string()], "0.0.1", ); assert!(!wasm.is_empty()); (wasm[0].0.clone(), ns) } impl SorobanEnv { pub fn new() -> Self { Self { env: Env::default(), contracts: Vec::new(), compiler_diagnostics: Diagnostics::default(), } } pub fn insert_diagnostics(mut self, diagnostics: Diagnostics) -> Self { self.compiler_diagnostics = diagnostics; self } pub fn new_with_contract(contract_wasm: Vec, configure_env: F) -> Self where F: FnOnce(&mut SorobanEnv), { let mut env = Self::new(); configure_env(&mut env); env.register_contract(contract_wasm); env } pub fn register_contract(&mut self, contract_wasm: Vec) -> Address { // For now, we keep using `register_contract_wasm`. To use `register`, we have to figure // out first what to pass for `constructor_args` #[allow(deprecated)] let addr = self .env .register_contract_wasm(None, contract_wasm.as_slice()); self.contracts.push(addr.clone()); addr } pub fn invoke_contract(&self, addr: &Address, function_name: &str, args: Vec) -> Val { let func = Symbol::new(&self.env, function_name); let mut args_soroban = vec![&self.env]; for arg in args { args_soroban.push_back(arg) } println!("args: {:?}", args_soroban); // To avoid running out of fuel self.env.cost_estimate().budget().reset_unlimited(); self.env.invoke_contract(addr, &func, args_soroban) } /// Invoke a contract and expect an error. Returns the logs. pub fn invoke_contract_expect_error( &self, addr: &Address, function_name: &str, args: Vec, ) -> Vec { let func = Symbol::new(&self.env, function_name); let mut args_soroban = vec![&self.env]; for arg in args { args_soroban.push_back(arg) } let _ = self .env .try_invoke_contract::(addr, &func, args_soroban); self.env.logs().all() } pub fn deploy_contract(&mut self, src: &str) -> Address { let wasm = build_wasm(src).0; let addr = self.register_contract(wasm); self.contracts.push(addr.clone()); addr } pub fn deploy_contract_with_args(&mut self, src: &str, args: A) -> Address where A: ConstructorArgs, { let wasm = build_wasm(src).0; let addr = self.env.register(wasm.as_slice(), args); self.contracts.push(addr.clone()); addr } } impl Default for SorobanEnv { fn default() -> Self { Self::new() } }