diff --git a/Cargo.lock b/Cargo.lock index cb22988b3..7853628eb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1664,6 +1664,13 @@ dependencies = [ "soroban-sdk", ] +[[package]] +name = "test_associated_types" +version = "23.0.3" +dependencies = [ + "soroban-sdk", +] + [[package]] name = "test_auth" version = "23.0.3" diff --git a/tests-expanded/test_associated_types_tests.rs b/tests-expanded/test_associated_types_tests.rs new file mode 100644 index 000000000..a110d509d --- /dev/null +++ b/tests-expanded/test_associated_types_tests.rs @@ -0,0 +1,375 @@ +#![feature(prelude_import)] +#![no_std] +#[macro_use] +extern crate core; +#[prelude_import] +use core::prelude::rust_2021::*; +use soroban_sdk::{contract, contractimpl, Env, String}; +pub struct DefaultImpl; +impl Trait for DefaultImpl { + type Impl = Self; + fn exec(env: &Env) -> String { + String::from_str(env, "default") + } +} +pub trait Trait { + type Impl: Trait; + fn exec(env: &Env) -> String { + Self::Impl::exec(env) + } +} +pub struct Contract; +///ContractArgs is a type for building arg lists for functions defined in "Contract". +pub struct ContractArgs; +///ContractClient is a client for calling the contract defined in "Contract". +pub struct ContractClient<'a> { + pub env: soroban_sdk::Env, + pub address: soroban_sdk::Address, + #[doc(hidden)] + set_auths: Option<&'a [soroban_sdk::xdr::SorobanAuthorizationEntry]>, + #[doc(hidden)] + mock_auths: Option<&'a [soroban_sdk::testutils::MockAuth<'a>]>, + #[doc(hidden)] + mock_all_auths: bool, + #[doc(hidden)] + allow_non_root_auth: bool, +} +impl<'a> ContractClient<'a> { + pub fn new(env: &soroban_sdk::Env, address: &soroban_sdk::Address) -> Self { + Self { + env: env.clone(), + address: address.clone(), + set_auths: None, + mock_auths: None, + mock_all_auths: false, + allow_non_root_auth: false, + } + } + /// Set authorizations in the environment which will be consumed by + /// contracts when they invoke `Address::require_auth` or + /// `Address::require_auth_for_args` functions. + /// + /// Requires valid signatures for the authorization to be successful. + /// To mock auth without requiring valid signatures, use `mock_auths`. + /// + /// See `soroban_sdk::Env::set_auths` for more details and examples. + pub fn set_auths(&self, auths: &'a [soroban_sdk::xdr::SorobanAuthorizationEntry]) -> Self { + Self { + env: self.env.clone(), + address: self.address.clone(), + set_auths: Some(auths), + mock_auths: self.mock_auths.clone(), + mock_all_auths: false, + allow_non_root_auth: false, + } + } + /// Mock authorizations in the environment which will cause matching invokes + /// of `Address::require_auth` and `Address::require_auth_for_args` to + /// pass. + /// + /// See `soroban_sdk::Env::set_auths` for more details and examples. + pub fn mock_auths(&self, mock_auths: &'a [soroban_sdk::testutils::MockAuth<'a>]) -> Self { + Self { + env: self.env.clone(), + address: self.address.clone(), + set_auths: self.set_auths.clone(), + mock_auths: Some(mock_auths), + mock_all_auths: false, + allow_non_root_auth: false, + } + } + /// Mock all calls to the `Address::require_auth` and + /// `Address::require_auth_for_args` functions in invoked contracts, + /// having them succeed as if authorization was provided. + /// + /// See `soroban_sdk::Env::mock_all_auths` for more details and + /// examples. + pub fn mock_all_auths(&self) -> Self { + Self { + env: self.env.clone(), + address: self.address.clone(), + set_auths: None, + mock_auths: None, + mock_all_auths: true, + allow_non_root_auth: false, + } + } + /// A version of `mock_all_auths` that allows authorizations that + /// are not present in the root invocation. + /// + /// Refer to `mock_all_auths` documentation for details and + /// prefer using `mock_all_auths` unless non-root authorization is + /// required. + /// + /// See `soroban_sdk::Env::mock_all_auths_allowing_non_root_auth` + /// for more details and examples. + pub fn mock_all_auths_allowing_non_root_auth(&self) -> Self { + Self { + env: self.env.clone(), + address: self.address.clone(), + set_auths: None, + mock_auths: None, + mock_all_auths: true, + allow_non_root_auth: true, + } + } +} +mod __contract_fn_set_registry { + use super::*; + extern crate std; + use std::collections::BTreeMap; + use std::sync::Mutex; + pub type F = soroban_sdk::testutils::ContractFunctionF; + static FUNCS: Mutex> = Mutex::new(BTreeMap::new()); + pub fn register(name: &'static str, func: &'static F) { + FUNCS.lock().unwrap().insert(name, func); + } + pub fn call( + name: &str, + env: soroban_sdk::Env, + args: &[soroban_sdk::Val], + ) -> Option { + let fopt: Option<&'static F> = FUNCS.lock().unwrap().get(name).map(|f| f.clone()); + fopt.map(|f| f(env, args)) + } +} +impl soroban_sdk::testutils::ContractFunctionRegister for Contract { + fn register(name: &'static str, func: &'static __contract_fn_set_registry::F) { + __contract_fn_set_registry::register(name, func); + } +} +#[doc(hidden)] +impl soroban_sdk::testutils::ContractFunctionSet for Contract { + fn call( + &self, + func: &str, + env: soroban_sdk::Env, + args: &[soroban_sdk::Val], + ) -> Option { + __contract_fn_set_registry::call(func, env, args) + } +} +impl Trait for Contract { + type Impl = DefaultImpl; + fn exec(env: &Env) -> String { + Self::Impl::exec(env) + } +} +#[doc(hidden)] +#[allow(non_snake_case)] +pub mod __Contract__exec__spec { + #[doc(hidden)] + #[allow(non_snake_case)] + #[allow(non_upper_case_globals)] + pub static __SPEC_XDR_FN_EXEC: [u8; 28usize] = super::Contract::spec_xdr_exec(); +} +impl Contract { + #[allow(non_snake_case)] + pub const fn spec_xdr_exec() -> [u8; 28usize] { + *b"\0\0\0\0\0\0\0\0\0\0\0\x04exec\0\0\0\0\0\0\0\x01\0\0\0\x10" + } +} +impl<'a> ContractClient<'a> { + pub fn exec(&self) -> String { + use core::ops::Not; + let old_auth_manager = self + .env + .in_contract() + .not() + .then(|| self.env.host().snapshot_auth_manager().unwrap()); + { + if let Some(set_auths) = self.set_auths { + self.env.set_auths(set_auths); + } + if let Some(mock_auths) = self.mock_auths { + self.env.mock_auths(mock_auths); + } + if self.mock_all_auths { + if self.allow_non_root_auth { + self.env.mock_all_auths_allowing_non_root_auth(); + } else { + self.env.mock_all_auths(); + } + } + } + use soroban_sdk::{FromVal, IntoVal}; + let res = self.env.invoke_contract( + &self.address, + &{ + #[allow(deprecated)] + const SYMBOL: soroban_sdk::Symbol = soroban_sdk::Symbol::short("exec"); + SYMBOL + }, + ::soroban_sdk::Vec::new(&self.env), + ); + if let Some(old_auth_manager) = old_auth_manager { + self.env.host().set_auth_manager(old_auth_manager).unwrap(); + } + res + } + pub fn try_exec( + &self, + ) -> Result< + Result< + String, + >::Error, + >, + Result, + > { + use core::ops::Not; + let old_auth_manager = self + .env + .in_contract() + .not() + .then(|| self.env.host().snapshot_auth_manager().unwrap()); + { + if let Some(set_auths) = self.set_auths { + self.env.set_auths(set_auths); + } + if let Some(mock_auths) = self.mock_auths { + self.env.mock_auths(mock_auths); + } + if self.mock_all_auths { + self.env.mock_all_auths(); + } + } + use soroban_sdk::{FromVal, IntoVal}; + let res = self.env.try_invoke_contract( + &self.address, + &{ + #[allow(deprecated)] + const SYMBOL: soroban_sdk::Symbol = soroban_sdk::Symbol::short("exec"); + SYMBOL + }, + ::soroban_sdk::Vec::new(&self.env), + ); + if let Some(old_auth_manager) = old_auth_manager { + self.env.host().set_auth_manager(old_auth_manager).unwrap(); + } + res + } +} +impl ContractArgs { + #[inline(always)] + #[allow(clippy::unused_unit)] + pub fn exec<'i>() -> () { + () + } +} +#[doc(hidden)] +#[allow(non_snake_case)] +pub mod __Contract__exec { + use super::*; + #[deprecated(note = "use `ContractClient::new(&env, &contract_id).exec` instead")] + pub fn invoke_raw(env: soroban_sdk::Env) -> soroban_sdk::Val { + use super::Trait; + <_ as soroban_sdk::IntoVal>::into_val( + #[allow(deprecated)] + &::exec(&env), + &env, + ) + } + #[deprecated(note = "use `ContractClient::new(&env, &contract_id).exec` instead")] + pub fn invoke_raw_slice(env: soroban_sdk::Env, args: &[soroban_sdk::Val]) -> soroban_sdk::Val { + if args.len() != 0usize { + { + ::core::panicking::panic_fmt(format_args!( + "invalid number of input arguments: {0} expected, got {1}", + 0usize, + args.len(), + )); + }; + } + #[allow(deprecated)] + invoke_raw(env) + } + #[deprecated(note = "use `ContractClient::new(&env, &contract_id).exec` instead")] + pub extern "C" fn invoke_raw_extern() -> soroban_sdk::Val { + #[allow(deprecated)] + invoke_raw(soroban_sdk::Env::default()) + } + use super::*; +} +#[doc(hidden)] +#[allow(non_snake_case)] +#[allow(unused)] +fn __Contract_Trait_2706c619fe73f0cf112473c6ee02e66c04e1c01c110b0c37b88d8eb509630c9f_ctor() { + #[allow(unsafe_code)] + { + #[link_section = ".init_array"] + #[used] + #[allow(non_upper_case_globals, non_snake_case)] + #[doc(hidden)] + static f: extern "C" fn() -> ::ctor::__support::CtorRetType = { + #[link_section = ".text.startup"] + #[allow(non_snake_case)] + extern "C" fn f() -> ::ctor::__support::CtorRetType { + unsafe { + __Contract_Trait_2706c619fe73f0cf112473c6ee02e66c04e1c01c110b0c37b88d8eb509630c9f_ctor(); + }; + core::default::Default::default() + } + f + }; + } + { + ::register( + "exec", + #[allow(deprecated)] + &__Contract__exec::invoke_raw_slice, + ); + } +} +mod test { + use crate::{Contract, ContractClient}; + use soroban_sdk::{Env, String}; + extern crate test; + #[rustc_test_marker = "test::test_exec"] + #[doc(hidden)] + pub const test_exec: test::TestDescAndFn = test::TestDescAndFn { + desc: test::TestDesc { + name: test::StaticTestName("test::test_exec"), + ignore: false, + ignore_message: ::core::option::Option::None, + source_file: "tests/associated_type/src/lib.rs", + start_line: 42usize, + start_col: 8usize, + end_line: 42usize, + end_col: 17usize, + compile_fail: false, + no_run: false, + should_panic: test::ShouldPanic::No, + test_type: test::TestType::UnitTest, + }, + testfn: test::StaticTestFn( + #[coverage(off)] + || test::assert_test_result(test_exec()), + ), + }; + fn test_exec() { + let e = Env::default(); + let contract_id = e.register(Contract, ()); + let client = ContractClient::new(&e, &contract_id); + let res = client.exec(); + match (&res, &String::from_str(&e, "default")) { + (left_val, right_val) => { + if !(*left_val == *right_val) { + let kind = ::core::panicking::AssertKind::Eq; + ::core::panicking::assert_failed( + kind, + &*left_val, + &*right_val, + ::core::option::Option::None, + ); + } + } + }; + } +} +#[rustc_main] +#[coverage(off)] +#[doc(hidden)] +pub fn main() -> () { + extern crate test; + test::test_main_static(&[&test_exec]) +} diff --git a/tests-expanded/test_associated_types_wasm32v1-none.rs b/tests-expanded/test_associated_types_wasm32v1-none.rs new file mode 100644 index 000000000..890a095e8 --- /dev/null +++ b/tests-expanded/test_associated_types_wasm32v1-none.rs @@ -0,0 +1,125 @@ +#![feature(prelude_import)] +#![no_std] +#[macro_use] +extern crate core; +#[prelude_import] +use core::prelude::rust_2021::*; +use soroban_sdk::{contract, contractimpl, Env, String}; +pub struct DefaultImpl; +impl Trait for DefaultImpl { + type Impl = Self; + fn exec(env: &Env) -> String { + String::from_str(env, "default") + } +} +pub trait Trait { + type Impl: Trait; + fn exec(env: &Env) -> String { + Self::Impl::exec(env) + } +} +pub struct Contract; +///ContractArgs is a type for building arg lists for functions defined in "Contract". +pub struct ContractArgs; +///ContractClient is a client for calling the contract defined in "Contract". +pub struct ContractClient<'a> { + pub env: soroban_sdk::Env, + pub address: soroban_sdk::Address, + #[doc(hidden)] + _phantom: core::marker::PhantomData<&'a ()>, +} +impl<'a> ContractClient<'a> { + pub fn new(env: &soroban_sdk::Env, address: &soroban_sdk::Address) -> Self { + Self { + env: env.clone(), + address: address.clone(), + _phantom: core::marker::PhantomData, + } + } +} +impl Trait for Contract { + type Impl = DefaultImpl; + fn exec(env: &Env) -> String { + Self::Impl::exec(env) + } +} +#[doc(hidden)] +#[allow(non_snake_case)] +pub mod __Contract__exec__spec { + #[doc(hidden)] + #[allow(non_snake_case)] + #[allow(non_upper_case_globals)] + #[link_section = "contractspecv0"] + pub static __SPEC_XDR_FN_EXEC: [u8; 28usize] = super::Contract::spec_xdr_exec(); +} +impl Contract { + #[allow(non_snake_case)] + pub const fn spec_xdr_exec() -> [u8; 28usize] { + *b"\0\0\0\0\0\0\0\0\0\0\0\x04exec\0\0\0\0\0\0\0\x01\0\0\0\x10" + } +} +impl<'a> ContractClient<'a> { + pub fn exec(&self) -> String { + use core::ops::Not; + use soroban_sdk::{FromVal, IntoVal}; + let res = self.env.invoke_contract( + &self.address, + &{ + #[allow(deprecated)] + const SYMBOL: soroban_sdk::Symbol = soroban_sdk::Symbol::short("exec"); + SYMBOL + }, + ::soroban_sdk::Vec::new(&self.env), + ); + res + } + pub fn try_exec( + &self, + ) -> Result< + Result< + String, + >::Error, + >, + Result, + > { + use soroban_sdk::{FromVal, IntoVal}; + let res = self.env.try_invoke_contract( + &self.address, + &{ + #[allow(deprecated)] + const SYMBOL: soroban_sdk::Symbol = soroban_sdk::Symbol::short("exec"); + SYMBOL + }, + ::soroban_sdk::Vec::new(&self.env), + ); + res + } +} +impl ContractArgs { + #[inline(always)] + #[allow(clippy::unused_unit)] + pub fn exec<'i>() -> () { + () + } +} +#[doc(hidden)] +#[allow(non_snake_case)] +pub mod __Contract__exec { + use super::*; + #[deprecated(note = "use `ContractClient::new(&env, &contract_id).exec` instead")] + pub fn invoke_raw(env: soroban_sdk::Env) -> soroban_sdk::Val { + use super::Trait; + <_ as soroban_sdk::IntoVal>::into_val( + #[allow(deprecated)] + &::exec(&env), + &env, + ) + } + #[deprecated(note = "use `ContractClient::new(&env, &contract_id).exec` instead")] + #[export_name = "exec"] + pub extern "C" fn invoke_raw_extern() -> soroban_sdk::Val { + #[allow(deprecated)] + invoke_raw(soroban_sdk::Env::default()) + } + use super::*; +} diff --git a/tests/associated_type/Cargo.toml b/tests/associated_type/Cargo.toml new file mode 100644 index 000000000..77c276c2f --- /dev/null +++ b/tests/associated_type/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "test_associated_types" +version.workspace = true +authors = ["Stellar Development Foundation "] +license = "Apache-2.0" +edition = "2021" +publish = false +rust-version.workspace = true + +[lib] +crate-type = ["cdylib"] +doctest = false + +[dependencies] +soroban-sdk = {path = "../../soroban-sdk"} + +[dev-dependencies] +soroban-sdk = {path = "../../soroban-sdk", features = ["testutils"]} diff --git a/tests/associated_type/src/lib.rs b/tests/associated_type/src/lib.rs new file mode 100644 index 000000000..b41f937bb --- /dev/null +++ b/tests/associated_type/src/lib.rs @@ -0,0 +1,50 @@ +#![no_std] +use soroban_sdk::{contract, contractimpl, Env, String}; + +// The associated type to hold a default impl for a trait pattern is a pattern that's seen in the +// OpenZeppelin contract library. + +pub struct DefaultImpl; + +impl Trait for DefaultImpl { + type Impl = Self; + fn exec(env: &Env) -> String { + String::from_str(env, "default") + } +} + +pub trait Trait { + type Impl: Trait; + + fn exec(env: &Env) -> String { + Self::Impl::exec(env) + } +} + +#[contract] +pub struct Contract; + +#[contractimpl] +impl Trait for Contract { + type Impl = DefaultImpl; + fn exec(env: &Env) -> String { + Self::Impl::exec(env) + } +} + +#[cfg(test)] +mod test { + use soroban_sdk::{Env, String}; + + use crate::{Contract, ContractClient}; + + #[test] + fn test_exec() { + let e = Env::default(); + let contract_id = e.register(Contract, ()); + let client = ContractClient::new(&e, &contract_id); + + let res = client.exec(); + assert_eq!(res, String::from_str(&e, "default")); + } +} diff --git a/tests/associated_type/test_snapshots/test/test_exec.1.json b/tests/associated_type/test_snapshots/test/test_exec.1.json new file mode 100644 index 000000000..6a19fbf66 --- /dev/null +++ b/tests/associated_type/test_snapshots/test/test_exec.1.json @@ -0,0 +1,77 @@ +{ + "generators": { + "address": 1, + "nonce": 0, + "mux_id": 0 + }, + "auth": [ + [], + [] + ], + "ledger": { + "protocol_version": 23, + "sequence_number": 0, + "timestamp": 0, + "network_id": "0000000000000000000000000000000000000000000000000000000000000000", + "base_reserve": 0, + "min_persistent_entry_ttl": 4096, + "min_temp_entry_ttl": 16, + "max_entry_ttl": 6312000, + "ledger_entries": [ + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": "ledger_key_contract_instance", + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": "ledger_key_contract_instance", + "durability": "persistent", + "val": { + "contract_instance": { + "executable": { + "wasm": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + "storage": null + } + } + } + }, + "ext": "v0" + }, + 4095 + ] + ], + [ + { + "contract_code": { + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_code": { + "ext": "v0", + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "code": "" + } + }, + "ext": "v0" + }, + 4095 + ] + ] + ] + }, + "events": [] +} \ No newline at end of file