// 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()
}
}