From 0e41baad5934c0ebd60f96b40d8b47c2ff5ac99e Mon Sep 17 00:00:00 2001 From: igor-casper Date: Mon, 17 Nov 2025 11:32:06 +0100 Subject: [PATCH 1/7] address todos --- smart_contracts/vm2/sdk/src/casper.rs | 17 +- smart_contracts/vm2/sdk/src/casper/native.rs | 1050 ------------------ smart_contracts/vm2/sdk/src/lib.rs | 5 +- smart_contracts/vm2/sdk/src/types.rs | 9 +- 4 files changed, 18 insertions(+), 1063 deletions(-) delete mode 100644 smart_contracts/vm2/sdk/src/casper/native.rs diff --git a/smart_contracts/vm2/sdk/src/casper.rs b/smart_contracts/vm2/sdk/src/casper.rs index 700feae601..467abdd1a9 100644 --- a/smart_contracts/vm2/sdk/src/casper.rs +++ b/smart_contracts/vm2/sdk/src/casper.rs @@ -1,6 +1,4 @@ pub mod altbn128; -#[cfg(all(not(target_arch = "wasm32"), feature = "std"))] -pub mod native; #[cfg(all(not(target_arch = "wasm32"), feature = "std"))] use crate::abi::{CasperABI, EnumVariant}; @@ -199,8 +197,10 @@ pub fn create( None => Err(CallError::InvalidOutput), }, other_status => { - // #TODO! fix this wrap - Err(CallError::try_from(other_status).expect("Couldn't interpret error from host")) + let Ok(error) = CallError::try_from(other_status) else { + panic!("Couldn't interpret error from host: {}", other_status); + }; + Err(error) } } } @@ -281,7 +281,6 @@ pub fn read_into_vec(key: Keyspace) -> Result>, HostResult> { /// Read from the global state into a vector. pub fn has_state() -> Result { - // TODO: Host side optimized `casper_exists` to check if given entry exists in the global state. let mut vec = Vec::new(); let read_info = read(Keyspace::State, |size| reserve_vec_space(&mut vec, size))?; match read_info { @@ -515,11 +514,9 @@ pub fn transferred_value() -> u64 { } /// Transfer tokens from the current contract to another account or contract. -pub fn transfer(target_account: &EntityAddr, amount: u64) -> Result<(), CallError> { - // TODO: the variable name is called target_account, but - // logic would call it with misc addresses. need to confer w/ michal - log!("transfer entity_addr {:?}", target_account); - let bytes = match borsh::to_vec(&(target_account, amount)) { +pub fn transfer(target: &EntityAddr, amount: u64) -> Result<(), CallError> { + log!("transfer entity_addr {:?}", target); + let bytes = match borsh::to_vec(&(target, amount)) { Ok(bytes) => bytes, Err(_err) => return Err(CallError::CalleeTrapped), }; diff --git a/smart_contracts/vm2/sdk/src/casper/native.rs b/smart_contracts/vm2/sdk/src/casper/native.rs deleted file mode 100644 index 7e7d366c6d..0000000000 --- a/smart_contracts/vm2/sdk/src/casper/native.rs +++ /dev/null @@ -1,1050 +0,0 @@ -use std::{ - cell::RefCell, - collections::{BTreeMap, BTreeSet, VecDeque}, - convert::Infallible, - fmt, - panic::{self, UnwindSafe}, - ptr::{self, NonNull}, - slice, - sync::{Arc, RwLock}, -}; - -use crate::linkme::distributed_slice; -use bytes::Bytes; -use casper_executor_wasm_common::{ - error::{ - CALLEE_ROLLED_BACK, CALLEE_SUCCEEDED, CALLEE_TRAPPED, HOST_ERROR_INTERNAL, - HOST_ERROR_NOT_FOUND, HOST_ERROR_SUCCESS, - }, - flags::ReturnFlags, -}; -#[cfg(not(target_arch = "wasm32"))] -use rand::Rng; - -use super::Entity; -use crate::types::Address; - -#[repr(C)] -pub struct Param { - pub name_ptr: *const u8, - pub name_len: usize, -} - -/// The kind of export that is being registered. -/// -/// This is used to identify the type of export and its name. -/// -/// Depending on the location of given function it may be registered as a: -/// -/// * `SmartContract` (if it's part of a `impl Contract` block), -/// * `TraitImpl` (if it's part of a `impl Trait for Contract` block), -/// * `Function` (if it's a standalone function). -/// -/// This is used to dispatch exports under native code i.e. you want to write a test that calls -/// "foobar" regardless of location. -#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)] -pub enum EntryPointKind { - /// Smart contract. - /// - /// This is used to identify the smart contract and its name. - /// - /// The `struct_name` is the name of the smart contract that is being registered. - /// The `name` is the name of the function that is being registered. - SmartContract { - struct_name: &'static str, - name: &'static str, - }, - /// Trait implementation. - /// - /// This is used to identify the trait implementation and its name. - /// - /// The `trait_name` is the name of the trait that is being implemented. - /// The `impl_name` is the name of the implementation. - /// The `name` is the name of the function that is being implemented. - TraitImpl { - trait_name: &'static str, - impl_name: &'static str, - name: &'static str, - }, - /// Function export. - /// - /// This is used to identify the function export and its name. - /// - /// The `name` is the name of the function that is being exported. - Function { name: &'static str }, -} - -impl EntryPointKind { - pub fn name(&self) -> &'static str { - match self { - EntryPointKind::SmartContract { name, .. } - | EntryPointKind::TraitImpl { name, .. } - | EntryPointKind::Function { name } => name, - } - } -} - -/// Export is a structure that contains information about the exported function. -/// -/// This is used to register the export and its name and physical location in the smart contract -/// source code. -pub struct EntryPoint { - /// The kind of entry point that is being registered. - pub kind: EntryPointKind, - pub fptr: fn() -> (), - pub module_path: &'static str, - pub file: &'static str, - pub line: u32, -} - -#[distributed_slice] -#[linkme(crate = crate::linkme)] -pub static ENTRY_POINTS: [EntryPoint]; - -impl fmt::Debug for EntryPoint { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let Self { - kind, - fptr: _, - module_path, - file, - line, - } = self; - - f.debug_struct("Export") - .field("kind", kind) - .field("fptr", &"") - .field("module_path", module_path) - .field("file", file) - .field("line", line) - .finish() - } -} - -/// Invokes an export by its name. -/// -/// This function is used to invoke an export by its name regardless of its location in the smart -/// contract. -pub fn invoke_export_by_name(name: &str) { - let all_entry_points = ENTRY_POINTS.iter().collect::>(); - - let exports_by_name: Vec<_> = all_entry_points - .iter() - .filter(|export| export.kind.name() == name) - .collect(); - - if exports_by_name.len() != 1 { - panic!( - "Expected exactly one export {} found, but got {:?} ({:?})", - name, exports_by_name, all_entry_points - ); - } - - let result = dispatch_export_call(exports_by_name[0].fptr); - - match result { - Ok(()) => {} - Err(trap) => { - match trap { - NativeTrap::Panic(panic_payload) => { - // Re-raise the panic so it can be caught by test's #[should_panic] - std::panic::resume_unwind(panic_payload); - } - other_trap => { - // For non-panic traps, set them in LAST_TRAP - LAST_TRAP.with(|last_trap| { - last_trap.borrow_mut().replace(other_trap); - }); - } - } - } - } -} - -#[derive(Debug)] -pub enum NativeTrap { - Return(ReturnFlags, Bytes), - Panic(Box), -} - -pub type Container = BTreeMap>; - -#[derive(Clone, Debug)] -#[allow(dead_code)] -pub struct NativeParam(pub(crate) String); - -impl From<&Param> for NativeParam { - fn from(val: &Param) -> Self { - let name = - String::from_utf8_lossy(unsafe { slice::from_raw_parts(val.name_ptr, val.name_len) }) - .into_owned(); - NativeParam(name) - } -} - -#[derive(Clone, Debug)] -pub struct Environment { - pub db: Arc>, - contracts: Arc>>, - // input_data: Arc>>, - input_data: Option, - caller: Entity, - callee: Entity, -} - -impl Default for Environment { - fn default() -> Self { - Self { - db: Default::default(), - contracts: Default::default(), - input_data: Default::default(), - caller: DEFAULT_ADDRESS, - callee: DEFAULT_ADDRESS, - } - } -} - -pub const DEFAULT_ADDRESS: Entity = Entity::Account([42; 32]); - -impl Environment { - #[must_use] - pub fn new(db: Container, caller: Entity) -> Self { - Self { - db: Arc::new(RwLock::new(db)), - contracts: Default::default(), - input_data: Default::default(), - caller, - callee: caller, - } - } - - #[must_use] - pub fn with_caller(&self, caller: Entity) -> Self { - let mut env = self.clone(); - env.caller = caller; - env - } - - #[must_use] - pub fn smart_contract(&self, callee: Entity) -> Self { - let mut env = self.clone(); - env.caller = self.callee; - env.callee = callee; - env - } - - #[must_use] - pub fn session(&self, callee: Entity) -> Self { - let mut env = self.clone(); - env.caller = callee; - env.callee = callee; - env - } - - #[must_use] - pub fn with_callee(&self, callee: Entity) -> Self { - let mut env = self.clone(); - env.callee = callee; - env - } - - #[must_use] - pub fn with_input_data(&self, input_data: Vec) -> Self { - let mut env = self.clone(); - env.input_data = Some(Bytes::from(input_data)); - env - } -} - -impl Environment { - fn key_prefix(&self, key: &[u8]) -> Vec { - let entity = self.callee; - - let mut bytes = Vec::new(); - bytes.extend(entity.tag().to_le_bytes()); - bytes.extend(entity.address()); - bytes.extend(key); - - bytes - } - - fn casper_read( - &self, - key_space: u64, - key_ptr: *const u8, - key_size: usize, - info: *mut casper_contract_sdk_sys::ReadInfo, - alloc: extern "C" fn(usize, *mut core::ffi::c_void) -> *mut u8, - alloc_ctx: *const core::ffi::c_void, - ) -> Result { - let key_bytes = unsafe { slice::from_raw_parts(key_ptr, key_size) }; - let key_bytes = self.key_prefix(key_bytes); - - let Ok(db) = self.db.read() else { - return Ok(HOST_ERROR_INTERNAL); - }; - - let value = match db.get(&key_space) { - Some(values) => values.get(key_bytes.as_slice()).cloned(), - None => return Ok(HOST_ERROR_NOT_FOUND), - }; - match value { - Some(tagged_value) => { - let ptr = NonNull::new(alloc(tagged_value.len(), alloc_ctx as _)); - - if let Some(ptr) = ptr { - unsafe { - (*info).data_ptr = ptr.as_ptr(); - (*info).data_size = tagged_value.len(); - } - - unsafe { - ptr::copy_nonoverlapping( - tagged_value.as_ptr(), - ptr.as_ptr(), - tagged_value.len(), - ); - } - } - - Ok(HOST_ERROR_SUCCESS) - } - None => Ok(HOST_ERROR_NOT_FOUND), - } - } - - fn casper_write( - &self, - key_space: u64, - key_ptr: *const u8, - key_size: usize, - value_ptr: *const u8, - value_size: usize, - ) -> Result { - assert!(!key_ptr.is_null()); - assert!(!value_ptr.is_null()); - // let key_bytes = unsafe { slice::from_raw_parts(key_ptr, key_size) }; - let key_bytes = unsafe { slice::from_raw_parts(key_ptr, key_size) }.to_owned(); - let key_bytes = self.key_prefix(&key_bytes); - - let value_bytes = unsafe { slice::from_raw_parts(value_ptr, value_size) }; - - let mut db = self.db.write().unwrap(); - db.entry(key_space).or_default().insert( - Bytes::from(key_bytes.to_vec()), - Bytes::from(value_bytes.to_vec()), - ); - Ok(HOST_ERROR_SUCCESS) - } - - fn casper_remove( - &self, - key_space: u64, - key_ptr: *const u8, - key_size: usize, - ) -> Result { - assert!(!key_ptr.is_null()); - let key_bytes = unsafe { slice::from_raw_parts(key_ptr, key_size) }; - let key_bytes = self.key_prefix(key_bytes); - - let mut db = self.db.write().unwrap(); - if let Some(values) = db.get_mut(&key_space) { - values.remove(key_bytes.as_slice()); - Ok(HOST_ERROR_SUCCESS) - } else { - Ok(HOST_ERROR_NOT_FOUND) - } - } - - fn casper_print(&self, msg_ptr: *const u8, msg_size: usize) -> Result<(), NativeTrap> { - let msg_bytes = unsafe { slice::from_raw_parts(msg_ptr, msg_size) }; - let msg = std::str::from_utf8(msg_bytes).expect("Valid UTF-8 string"); - println!("💻 {msg}"); - Ok(()) - } - - fn casper_return( - &self, - flags: u32, - data_ptr: *const u8, - data_len: usize, - ) -> Result { - let maybe_flags = ReturnFlags::from_bits(flags); - let return_flags = match maybe_flags { - Some(flags) => flags, - None => { - return Err(NativeTrap::Panic(Box::new(format!( - "Attempted to pass return flags which are not supported, raw flags: {}", - flags - )))) - } - }; - let data = if data_ptr.is_null() { - Bytes::new() - } else { - Bytes::copy_from_slice(unsafe { slice::from_raw_parts(data_ptr, data_len) }) - }; - Err(NativeTrap::Return(return_flags, data)) - } - - fn casper_copy_input( - &self, - alloc: extern "C" fn(usize, *mut core::ffi::c_void) -> *mut u8, - alloc_ctx: *const core::ffi::c_void, - ) -> Result<*mut u8, NativeTrap> { - let input_data = self.input_data.clone(); - let input_data = input_data.as_ref().cloned().unwrap_or_default(); - let ptr = NonNull::new(alloc(input_data.len(), alloc_ctx as _)); - - match ptr { - Some(ptr) => { - if !input_data.is_empty() { - unsafe { - ptr::copy_nonoverlapping( - input_data.as_ptr(), - ptr.as_ptr(), - input_data.len(), - ); - } - } - Ok(unsafe { ptr.as_ptr().add(input_data.len()) }) - } - None => Ok(ptr::null_mut()), - } - } - - #[allow(clippy::too_many_arguments)] - fn casper_create( - &self, - code_ptr: *const u8, - code_size: usize, - transferred_value: u64, - constructor_ptr: *const u8, - constructor_size: usize, - input_ptr: *const u8, - input_size: usize, - seed_ptr: *const u8, - seed_size: usize, - result_ptr: *mut casper_contract_sdk_sys::CreateResult, - ) -> Result { - // let manifest = - // NonNull::new(manifest_ptr as *mut casper_contract_sdk_sys::Manifest).expect("Manifest - // instance"); - let code = if code_ptr.is_null() { - None - } else { - Some(unsafe { slice::from_raw_parts(code_ptr, code_size) }) - }; - - if code.is_some() { - panic!("Supplying code is not supported yet in native mode"); - } - - let constructor = if constructor_ptr.is_null() { - None - } else { - Some(unsafe { slice::from_raw_parts(constructor_ptr, constructor_size) }) - }; - - let input_data = if input_ptr.is_null() { - None - } else { - Some(unsafe { slice::from_raw_parts(input_ptr, input_size) }) - }; - - let _seed = if seed_ptr.is_null() { - None - } else { - Some(unsafe { slice::from_raw_parts(seed_ptr, seed_size) }) - }; - - assert_eq!( - transferred_value, 0, - "Creating new contracts with transferred value is not supported in native mode" - ); - - let mut rng = rand::thread_rng(); - let contract_address = rng.gen(); - let package_address = rng.gen(); - - let mut result = NonNull::new(result_ptr).expect("Valid pointer"); - unsafe { - result.as_mut().contract_address = package_address; - } - - let mut contracts = self.contracts.write().unwrap(); - contracts.insert(contract_address); - - if let Some(entry_point) = constructor { - let entry_point = ENTRY_POINTS - .iter() - .find(|export| export.kind.name().as_bytes() == entry_point) - .expect("Entry point exists"); - - let mut stub = with_current_environment(|stub| stub); - stub.input_data = input_data.map(Bytes::copy_from_slice); - - stub.caller = stub.callee; - stub.callee = Entity::Contract(package_address); - - // stub.callee - // Call constructor, expect a trap - let result = dispatch_with(stub, || { - // TODO: Handle panic inside constructor - (entry_point.fptr)(); - }); - - match result { - Ok(()) => {} - Err(NativeTrap::Return(flags, bytes)) => { - if flags.contains(ReturnFlags::ROLLBACK) { - todo!("Constructor returned with a revert flag"); - } - assert!(bytes.is_empty(), "When returning from the constructor it is expected that no bytes are passed in a return function"); - } - Err(NativeTrap::Panic(_panic)) => { - todo!(); - } - } - } - - Ok(HOST_ERROR_SUCCESS) - } - - #[allow(clippy::too_many_arguments)] - fn casper_call( - &self, - address_ptr: *const u8, - address_size: usize, - transferred_value: u64, - entry_point_ptr: *const u8, - entry_point_size: usize, - input_ptr: *const u8, - input_size: usize, - alloc: extern "C" fn(usize, *mut core::ffi::c_void) -> *mut u8, /* For capturing output - * data */ - alloc_ctx: *const core::ffi::c_void, - ) -> Result { - let address = unsafe { slice::from_raw_parts(address_ptr, address_size) }; - let input_data = unsafe { slice::from_raw_parts(input_ptr, input_size) }; - let entry_point = { - let entry_point_ptr = NonNull::new(entry_point_ptr.cast_mut()).expect("Valid pointer"); - let entry_point = - unsafe { slice::from_raw_parts(entry_point_ptr.as_ptr(), entry_point_size) }; - let entry_point = std::str::from_utf8(entry_point).expect("Valid UTF-8 string"); - entry_point.to_string() - }; - - assert_eq!( - transferred_value, 0, - "Transferred value is not supported in native mode" - ); - - let export = ENTRY_POINTS - .iter() - .find(|export| - matches!(export.kind, EntryPointKind::SmartContract { name, .. } | EntryPointKind::TraitImpl { name, .. } - if name == entry_point) - ) - .expect("Existing entry point"); - - let mut new_stub = with_current_environment(|stub| stub.clone()); - new_stub.input_data = Some(Bytes::copy_from_slice(input_data)); - new_stub.caller = new_stub.callee; - new_stub.callee = Entity::Contract(address.try_into().expect("Size to match")); - - let ret = dispatch_with(new_stub, || { - // We need to convert any panic inside the entry point into a native trap. This probably - // should be done in a more configurable way. - dispatch_export_call(|| { - (export.fptr)(); - }) - }); - - let unfolded = match ret { - Ok(Ok(())) => Ok(()), - Ok(Err(error)) | Err(error) => Err(error), - }; - - match unfolded { - Ok(()) => Ok(CALLEE_SUCCEEDED), - Err(NativeTrap::Return(flags, bytes)) => { - let ptr = NonNull::new(alloc(bytes.len(), alloc_ctx.cast_mut())); - if let Some(output_ptr) = ptr { - unsafe { - ptr::copy_nonoverlapping(bytes.as_ptr(), output_ptr.as_ptr(), bytes.len()); - } - } - - if flags.contains(ReturnFlags::ROLLBACK) { - Ok(CALLEE_ROLLED_BACK) - } else { - Ok(CALLEE_SUCCEEDED) - } - } - Err(NativeTrap::Panic(panic)) => { - eprintln!("Panic {panic:?}"); - Ok(CALLEE_TRAPPED) - } - } - } - - #[doc = r"Obtain data from the blockchain environemnt of current wasm invocation. - -Example paths: - -* `env_read([CASPER_CALLER], 1, nullptr, &caller_addr)` -> read caller's address into - `caller_addr` memory. -* `env_read([CASPER_CHAIN, BLOCK_HASH, 0], 3, nullptr, &block_hash)` -> read hash of the - current block into `block_hash` memory. -* `env_read([CASPER_CHAIN, BLOCK_HASH, 5], 3, nullptr, &block_hash)` -> read hash of the 5th - block from the current one into `block_hash` memory. -* `env_read([CASPER_AUTHORIZED_KEYS], 1, nullptr, &authorized_keys)` -> read list of - authorized keys into `authorized_keys` memory."] - fn casper_env_read( - &self, - _env_path: *const u64, - _env_path_size: usize, - _alloc: Option *mut u8>, - _alloc_ctx: *const core::ffi::c_void, - ) -> Result<*mut u8, NativeTrap> { - todo!() - } - - fn casper_env_info(&self, info_ptr: *const u8, info_size: u32) -> Result { - assert_eq!( - info_size as usize, - size_of::() - ); - let mut env_info = NonNull::new(info_ptr as *mut u8) - .expect("Valid ptr") - .cast::(); - let env_info = unsafe { env_info.as_mut() }; - *env_info = casper_contract_sdk_sys::EnvInfo { - block_time: 0, - transferred_value: 0, - caller_addr: *self.caller.address(), - caller_kind: self.caller.tag(), - callee_addr: *self.callee.address(), - callee_kind: self.callee.tag(), - protocol_version_major: 2, - protocol_version_minor: 1, - protocol_version_patch: 0, - parent_block_hash: [0xAB; 32], - block_height: 1, - }; - Ok(HOST_ERROR_SUCCESS) - } -} - -thread_local! { - pub(crate) static LAST_TRAP: RefCell> = const { RefCell::new(None) }; - static ENV_STACK: RefCell> = RefCell::new(VecDeque::from_iter([ - // Stack of environments has a default element so unit tests do not require extra effort. - // Environment::default() - ])); -} - -pub fn with_current_environment(f: impl FnOnce(Environment) -> T) -> T { - ENV_STACK.with(|stack| { - let stub = { - let borrowed = stack.borrow(); - let front = borrowed.front().expect("Stub exists").clone(); - front - }; - f(stub) - }) -} - -pub fn current_environment() -> Environment { - with_current_environment(|env| env) -} - -fn handle_ret_with(value: Result, ret: impl FnOnce() -> T) -> T { - match value { - Ok(result) => { - LAST_TRAP.with(|last_trap| last_trap.borrow_mut().take()); - result - } - Err(trap) => { - let result = ret(); - LAST_TRAP.with(|last_trap| last_trap.borrow_mut().replace(trap)); - result - } - } -} - -fn dispatch_export_call(func: F) -> Result<(), NativeTrap> -where - F: FnOnce() + Send + UnwindSafe, -{ - let call_result = panic::catch_unwind(|| { - func(); - }); - match call_result { - Ok(()) => { - let last_trap = LAST_TRAP.with(|last_trap| last_trap.borrow_mut().take()); - match last_trap { - Some(last_trap) => Err(last_trap), - None => Ok(()), - } - } - Err(error) => Err(NativeTrap::Panic(error)), - } -} - -fn handle_ret(value: Result) -> T { - handle_ret_with(value, || T::default()) -} - -/// Dispatches a function with a default environment. -pub fn dispatch(f: impl FnOnce() -> T) -> Result { - dispatch_with(Environment::default(), f) -} - -/// Dispatches a function with a given environment. -pub fn dispatch_with(stub: Environment, f: impl FnOnce() -> T) -> Result { - ENV_STACK.with(|stack| { - let mut borrowed = stack.borrow_mut(); - borrowed.push_front(stub); - }); - - // Clear previous trap (if present) - LAST_TRAP.with(|last_trap| last_trap.borrow_mut().take()); - - // Call a function - let result = f(); - - // Check if a trap was set and return it if so (otherwise return the result). - let last_trap = LAST_TRAP.with(|last_trap| last_trap.borrow_mut().take()); - - let result = if let Some(trap) = last_trap { - Err(trap) - } else { - Ok(result) - }; - - // Pop the stub from the stack - ENV_STACK.with(|stack| { - let mut borrowed = stack.borrow_mut(); - borrowed.pop_front(); - }); - - result -} - -mod symbols { - // TODO: Figure out how to use for_each_host_function macro here and deal with never type in - // casper_return - #[no_mangle] - /// Read value from a storage available for caller's entity address. - pub extern "C" fn casper_read( - key_space: u64, - key_ptr: *const u8, - key_size: usize, - info: *mut ::casper_contract_sdk_sys::ReadInfo, - alloc: extern "C" fn(usize, *mut core::ffi::c_void) -> *mut u8, - alloc_ctx: *const core::ffi::c_void, - ) -> u32 { - let _name = "casper_read"; - let _args = (&key_space, &key_ptr, &key_size, &info, &alloc, &alloc_ctx); - let _call_result = with_current_environment(|stub| { - stub.casper_read(key_space, key_ptr, key_size, info, alloc, alloc_ctx) - }); - crate::casper::native::handle_ret(_call_result) - } - - #[no_mangle] - pub extern "C" fn casper_write( - key_space: u64, - key_ptr: *const u8, - key_size: usize, - value_ptr: *const u8, - value_size: usize, - ) -> u32 { - let _name = "casper_write"; - let _args = (&key_space, &key_ptr, &key_size, &value_ptr, &value_size); - let _call_result = with_current_environment(|stub| { - stub.casper_write(key_space, key_ptr, key_size, value_ptr, value_size) - }); - crate::casper::native::handle_ret(_call_result) - } - - #[no_mangle] - pub extern "C" fn casper_remove(key_space: u64, key_ptr: *const u8, key_size: usize) -> u32 { - let _name = "casper_remove"; - let _args = (&key_space, &key_ptr, &key_size); - let _call_result = - with_current_environment(|stub| stub.casper_remove(key_space, key_ptr, key_size)); - crate::casper::native::handle_ret(_call_result) - } - - #[no_mangle] - pub extern "C" fn casper_print(msg_ptr: *const u8, msg_size: usize) { - let _name = "casper_print"; - let _args = (&msg_ptr, &msg_size); - let _call_result = with_current_environment(|stub| stub.casper_print(msg_ptr, msg_size)); - crate::casper::native::handle_ret(_call_result); - } - - use casper_executor_wasm_common::error::HOST_ERROR_SUCCESS; - - use crate::casper::native::LAST_TRAP; - - #[no_mangle] - pub extern "C" fn casper_return(flags: u32, data_ptr: *const u8, data_len: usize) { - let _name = "casper_return"; - let _args = (&flags, &data_ptr, &data_len); - let _call_result = - with_current_environment(|stub| stub.casper_return(flags, data_ptr, data_len)); - let err = _call_result.unwrap_err(); // SAFE - LAST_TRAP.with(|last_trap| last_trap.borrow_mut().replace(err)); - } - - #[no_mangle] - pub extern "C" fn casper_copy_input( - alloc: extern "C" fn(usize, *mut core::ffi::c_void) -> *mut u8, - alloc_ctx: *const core::ffi::c_void, - ) -> *mut u8 { - let _name = "casper_copy_input"; - let _args = (&alloc, &alloc_ctx); - let _call_result = - with_current_environment(|stub| stub.casper_copy_input(alloc, alloc_ctx)); - crate::casper::native::handle_ret_with(_call_result, ptr::null_mut) - } - - #[no_mangle] - pub extern "C" fn casper_create( - code_ptr: *const u8, - code_size: usize, - transferred_value: u64, - constructor_ptr: *const u8, - constructor_size: usize, - input_ptr: *const u8, - input_size: usize, - seed_ptr: *const u8, - seed_size: usize, - result_ptr: *mut casper_contract_sdk_sys::CreateResult, - ) -> u32 { - let _call_result = with_current_environment(|stub| { - stub.casper_create( - code_ptr, - code_size, - transferred_value, - constructor_ptr, - constructor_size, - input_ptr, - input_size, - seed_ptr, - seed_size, - result_ptr, - ) - }); - crate::casper::native::handle_ret(_call_result) - } - - #[no_mangle] - pub extern "C" fn casper_ffi( - _system_contract_opt: u32, - _input_ptr: *const u8, - _input_size: usize, - _alloc: extern "C" fn(usize, *mut core::ffi::c_void) -> *mut u8, - _alloc_ctx: *const core::ffi::c_void, - ) -> u32 { - todo!() - } - - #[no_mangle] - pub extern "C" fn casper_call( - address_ptr: *const u8, - address_size: usize, - transferred_value: u64, - entry_point_ptr: *const u8, - entry_point_size: usize, - input_ptr: *const u8, - input_size: usize, - alloc: extern "C" fn(usize, *mut core::ffi::c_void) -> *mut u8, /* For capturing output - * data */ - alloc_ctx: *const core::ffi::c_void, - ) -> u32 { - let _call_result = with_current_environment(|stub| { - stub.casper_call( - address_ptr, - address_size, - transferred_value, - entry_point_ptr, - entry_point_size, - input_ptr, - input_size, - alloc, - alloc_ctx, - ) - }); - crate::casper::native::handle_ret(_call_result) - } - - #[no_mangle] - pub extern "C" fn casper_upgrade( - _code_ptr: *const u8, - _code_size: usize, - _entry_point_ptr: *const u8, - _entry_point_size: usize, - _input_ptr: *const u8, - _input_size: usize, - ) -> u32 { - todo!() - } - - use core::slice; - use std::ptr; - - use super::with_current_environment; - - #[no_mangle] - pub extern "C" fn casper_env_read( - env_path: *const u64, - env_path_size: usize, - alloc: Option *mut u8>, - alloc_ctx: *const core::ffi::c_void, - ) -> *mut u8 { - let _name = "casper_env_read"; - let _args = (&env_path, &env_path_size, &alloc, &alloc_ctx); - let _call_result = with_current_environment(|stub| { - stub.casper_env_read(env_path, env_path_size, alloc, alloc_ctx) - }); - crate::casper::native::handle_ret_with(_call_result, ptr::null_mut) - } - #[no_mangle] - pub extern "C" fn casper_env_balance( - _entity_kind: u32, - _entity_addr_ptr: *const u8, - _entity_addr_len: usize, - ) -> u64 { - todo!() - } - #[no_mangle] - pub extern "C" fn casper_emit( - topic_ptr: *const u8, - topic_size: usize, - data_ptr: *const u8, - data_size: usize, - ) -> u32 { - let topic = unsafe { slice::from_raw_parts(topic_ptr, topic_size) }; - let data = unsafe { slice::from_raw_parts(data_ptr, data_size) }; - let topic = std::str::from_utf8(topic).expect("Valid UTF-8 string"); - println!("Emitting event with topic: {topic:?} and data: {data:?}"); - HOST_ERROR_SUCCESS - } - - #[no_mangle] - pub extern "C" fn casper_env_info(info_ptr: *const u8, info_size: u32) -> u32 { - let ret = with_current_environment(|env| env.casper_env_info(info_ptr, info_size)); - crate::casper::native::handle_ret(ret) - } - - #[no_mangle] - pub extern "C" fn casper_generic_hash( - _in_ptr: *const u8, - _in_size: u32, - _out_ptr: *const u8, - _algorithm: u32, - ) -> u32 { - todo!() - } - - #[no_mangle] - pub fn casper_recover_secp256k1( - _message_ptr: *const u8, - _message_size: usize, - _signature_ptr: *const u8, - _signature_size: usize, - _public_key_ptr: *const u8, - _recovery_id: u32, - ) -> u32 { - todo!() - } -} - -#[cfg(test)] -mod tests { - use super::*; - - /*#TODO fix native implementation - #[test] - fn foo() { - dispatch(|| { - let _ = casper::print("Hello"); - casper::write(Keyspace::Context(b"test"), b"value 1").unwrap(); - - let change_context_1 = - with_current_environment(|stub| stub.smart_contract(Entity::Contract([1; 32]))); - - dispatch_with(change_context_1, || { - casper::write(Keyspace::Context(b"test"), b"value 2").unwrap(); - casper::write(Keyspace::State, b"state").unwrap(); - }) - .unwrap(); - - let change_context_1 = - with_current_environment(|stub| stub.smart_contract(Entity::Contract([1; 32]))); - dispatch_with(change_context_1, || { - assert_eq!( - casper::read_into_vec(Keyspace::Context(b"test")), - Ok(Some(b"value 2".to_vec())) - ); - assert_eq!( - casper::read_into_vec(Keyspace::State), - Ok(Some(b"state".to_vec())) - ); - }) - .unwrap(); - - assert_eq!(casper::get_caller(), DEFAULT_ADDRESS); - assert_eq!( - casper::read_into_vec(Keyspace::Context(b"test")), - Ok(Some(b"value 1".to_vec())) - ); - }) - .unwrap(); - } - */ - #[test] - fn test() { - dispatch_with(Environment::default(), || { - let msg = "Hello"; - let () = with_current_environment(|stub| stub.casper_print(msg.as_ptr(), msg.len())) - .expect("Ok"); - }) - .unwrap(); - } - - #[test] - fn test_returns() { - dispatch_with(Environment::default(), || { - let _ = with_current_environment(|stub| stub.casper_return(0, ptr::null(), 0)); - }) - .unwrap(); - } - - #[test] - fn test_returns_unsupported_flags() { - let all_flags = ReturnFlags::all().bits(); - let faulty_flags = all_flags << 1; - if all_flags == faulty_flags { - unreachable!("Shifting ReturnFlags::all by 1 byte yields ReturnFlags::all and cannot be used to construct faulty_flags, replace with different value.") - } - - let ret = dispatch_with(Environment::default(), || { - with_current_environment(|stub| stub.casper_return(faulty_flags, ptr::null(), 0)) - }); - assert!(ret.is_ok()); - let inner_result = ret.ok().unwrap(); - assert!(inner_result.is_err()); - let inner_err = inner_result.err().unwrap(); - let expected_err = NativeTrap::Panic(Box::new(format!( - "Attempted to pass return flags which are not supported, raw flags: {}", - 1 - ))); - assert_eq!(format!("{inner_err:?}"), format!("{expected_err:?}")) - } -} diff --git a/smart_contracts/vm2/sdk/src/lib.rs b/smart_contracts/vm2/sdk/src/lib.rs index 26c423599f..bf2bf1703a 100644 --- a/smart_contracts/vm2/sdk/src/lib.rs +++ b/smart_contracts/vm2/sdk/src/lib.rs @@ -45,7 +45,10 @@ cfg_if::cfg_if! { } else { pub fn set_panic_hook() { - // TODO: What to do? + crate::casper::ret( + casper_executor_wasm_common::flags::ReturnFlags::REVERT, + Some(s.as_bytes()), + ); } } } diff --git a/smart_contracts/vm2/sdk/src/types.rs b/smart_contracts/vm2/sdk/src/types.rs index 13375d39ad..0c64cf1c72 100644 --- a/smart_contracts/vm2/sdk/src/types.rs +++ b/smart_contracts/vm2/sdk/src/types.rs @@ -2,8 +2,9 @@ use core::marker::PhantomData; use casper_executor_wasm_common::{ error::{ - CALLEE_API_ERROR, CALLEE_GAS_DEPLETED, CALLEE_INPUT_INVALID, CALLEE_NOT_CALLABLE, - CALLEE_ROLLED_BACK, CALLEE_TRAPPED, + CALLEE_API_ERROR, CALLEE_CODE_NOT_FOUND, CALLEE_ENTITY_NOT_FOUND, CALLEE_GAS_DEPLETED, + CALLEE_INPUT_INVALID, CALLEE_LOCKED_PACKAGE, CALLEE_NOT_CALLABLE, + CALLEE_NO_ACTIVE_CONTRACT, CALLEE_ROLLED_BACK, CALLEE_TRAPPED, }, keyspace::Keyspace, }; @@ -245,6 +246,10 @@ impl TryFrom for CallError { CALLEE_NOT_CALLABLE => Ok(Self::NotCallable), CALLEE_INPUT_INVALID => Ok(Self::InputInvalid), CALLEE_API_ERROR => Ok(Self::Api), + CALLEE_NO_ACTIVE_CONTRACT => Ok(Self::NoActiveContract), + CALLEE_CODE_NOT_FOUND => Ok(Self::CodeNotFound), + CALLEE_ENTITY_NOT_FOUND => Ok(Self::EntityNotFound), + CALLEE_LOCKED_PACKAGE => Ok(Self::LockedPackage), _ => Err(()), } } From a22f380e1f79dd8b6927208bf8eb00c805c4a19a Mon Sep 17 00:00:00 2001 From: igor-casper Date: Tue, 18 Nov 2025 14:35:16 +0100 Subject: [PATCH 2/7] remove native module --- .../vm2/sdk/src/collections/iterable_map.rs | 545 ------------------ .../vm2/sdk/src/collections/iterable_set.rs | 172 +----- .../vm2/sdk/src/collections/vector.rs | 287 +-------- smart_contracts/vm2/sdk/src/compat.rs | 36 +- smart_contracts/vm2/sdk/src/lib.rs | 4 - 5 files changed, 3 insertions(+), 1041 deletions(-) diff --git a/smart_contracts/vm2/sdk/src/collections/iterable_map.rs b/smart_contracts/vm2/sdk/src/collections/iterable_map.rs index 856dfc4d96..23df37bc51 100644 --- a/smart_contracts/vm2/sdk/src/collections/iterable_map.rs +++ b/smart_contracts/vm2/sdk/src/collections/iterable_map.rs @@ -447,548 +447,3 @@ where } } } - -#[cfg(all(not(target_arch = "wasm32"), feature = "std"))] -#[cfg(test)] -mod tests { - /*#TODO fix native implementation - use super::*; - use crate::casper::native::dispatch; - - const TEST_MAP_PREFIX: &str = "test_map"; - - #[test] - fn insert_and_get() { - dispatch(|| { - let mut map = IterableMap::::new(TEST_MAP_PREFIX); - assert_eq!(map.len(), 0); - - assert_eq!(map.get(&1), None); - - map.insert(1, "a".to_string()); - assert_eq!(map.len(), 1); - - assert_eq!(map.get(&1), Some("a".to_string())); - - map.insert(2, "b".to_string()); - assert_eq!(map.len(), 2); - - assert_eq!(map.get(&2), Some("b".to_string())); - }) - .unwrap(); - } - - #[test] - fn overwrite_existing_key() { - dispatch(|| { - let mut map = IterableMap::::new(TEST_MAP_PREFIX); - - assert_eq!(map.insert(1, "a".to_string()), None); - assert_eq!(map.insert(1, "b".to_string()), Some("a".to_string())); - assert_eq!(map.get(&1), Some("b".to_string())); - }) - .unwrap(); - } - - #[test] - fn remove_tail_entry() { - dispatch(|| { - let mut map = IterableMap::::new(TEST_MAP_PREFIX); - assert_eq!(map.len(), 0); - map.insert(1, "a".to_string()); - assert_eq!(map.len(), 1); - map.insert(2, "b".to_string()); - assert_eq!(map.len(), 2); - assert_eq!(map.remove(&2), Some("b".to_string())); - assert_eq!(map.len(), 1); - assert_eq!(map.get(&2), None); - assert_eq!(map.get(&1), Some("a".to_string())); - }) - .unwrap(); - } - - #[test] - fn remove_middle_entry() { - dispatch(|| { - let mut map = IterableMap::::new(TEST_MAP_PREFIX); - assert_eq!(map.len(), 0); - - map.insert(1, "a".to_string()); - assert_eq!(map.len(), 1); - - map.insert(2, "b".to_string()); - assert_eq!(map.len(), 2); - - map.insert(3, "c".to_string()); - assert_eq!(map.len(), 3); - - assert_eq!(map.remove(&2), Some("b".to_string())); - assert_eq!(map.len(), 2); - - assert_eq!(map.get(&2), None); - assert_eq!(map.get(&1), Some("a".to_string())); - assert_eq!(map.get(&3), Some("c".to_string())); - - assert_eq!(map.len(), 2); - }) - .unwrap(); - } - - #[test] - fn remove_nonexistent_key_does_nothing() { - dispatch(|| { - let mut map = IterableMap::::new(TEST_MAP_PREFIX); - - map.insert(1, "a".to_string()); - - assert_eq!(map.remove(&999), None); - assert_eq!(map.get(&1), Some("a".to_string())); - }) - .unwrap(); - } - - #[test] - fn iterates_all_entries_in_reverse_insertion_order() { - dispatch(|| { - let mut map = IterableMap::::new(TEST_MAP_PREFIX); - - map.insert(1, "a".to_string()); - map.insert(2, "b".to_string()); - map.insert(3, "c".to_string()); - - let values: Vec<_> = map.values().collect(); - assert_eq!( - values, - vec!["c".to_string(), "b".to_string(), "a".to_string(),] - ); - }) - .unwrap(); - } - - #[test] - fn iteration_skips_deleted_entries() { - dispatch(|| { - let mut map = IterableMap::::new(TEST_MAP_PREFIX); - - map.insert(1, "a".to_string()); - map.insert(2, "b".to_string()); - map.insert(3, "c".to_string()); - - map.remove(&2); - - let values: Vec<_> = map.values().collect(); - assert_eq!(values, vec!["c".to_string(), "a".to_string(),]); - }) - .unwrap(); - } - - #[test] - fn empty_map_behaves_sanely() { - dispatch(|| { - let mut map = IterableMap::::new(TEST_MAP_PREFIX); - - assert_eq!(map.get(&1), None); - assert_eq!(map.remove(&1), None); - assert_eq!(map.iter().count(), 0); - }) - .unwrap(); - } - - #[test] - fn separate_maps_do_not_conflict() { - dispatch(|| { - let mut map1 = IterableMap::::new("map1"); - let mut map2 = IterableMap::::new("map2"); - - map1.insert(1, "a".to_string()); - map2.insert(1, "b".to_string()); - - assert_eq!(map1.get(&1), Some("a".to_string())); - assert_eq!(map2.get(&1), Some("b".to_string())); - }) - .unwrap(); - } - - #[test] - fn insert_same_value_under_different_keys() { - dispatch(|| { - let mut map = IterableMap::::new(TEST_MAP_PREFIX); - - map.insert(1, "shared".to_string()); - map.insert(2, "shared".to_string()); - - assert_eq!(map.get(&1), Some("shared".to_string())); - assert_eq!(map.get(&2), Some("shared".to_string())); - }) - .unwrap(); - } - - #[test] - fn clear_removes_all_entries() { - dispatch(|| { - let mut map = IterableMap::::new(TEST_MAP_PREFIX); - map.insert(1, "a".to_string()); - map.insert(2, "b".to_string()); - map.clear(); - assert!(map.is_empty()); - assert_eq!(map.iter().count(), 0); - }) - .unwrap(); - } - - #[test] - fn keys_returns_reverse_insertion_order() { - dispatch(|| { - let mut map = IterableMap::::new(TEST_MAP_PREFIX); - map.insert(1, "a".to_string()); - map.insert(2, "b".to_string()); - let hashes: Vec<_> = map.keys().collect(); - assert_eq!(hashes, vec![2, 1]); - }) - .unwrap(); - } - - #[test] - fn values_returns_values_in_reverse_insertion_order() { - dispatch(|| { - let mut map = IterableMap::::new(TEST_MAP_PREFIX); - map.insert(1, "a".to_string()); - map.insert(2, "b".to_string()); - let values: Vec<_> = map.values().collect(); - assert_eq!(values, vec!["b".to_string(), "a".to_string()]); - }) - .unwrap(); - } - - #[test] - fn contains_key_returns_correctly() { - dispatch(|| { - let mut map = IterableMap::::new(TEST_MAP_PREFIX); - assert!(!map.contains_key(&1)); - map.insert(1, "a".to_string()); - assert!(map.contains_key(&1)); - map.remove(&1); - assert!(!map.contains_key(&1)); - }) - .unwrap(); - } - - #[test] - fn multiple_removals_and_insertions() { - dispatch(|| { - let mut map = IterableMap::::new(TEST_MAP_PREFIX); - map.insert(1, "a".to_string()); - map.insert(2, "b".to_string()); - map.insert(3, "c".to_string()); - map.remove(&2); - assert_eq!(map.get(&2), None); - assert_eq!(map.get(&1), Some("a".to_string())); - assert_eq!(map.get(&3), Some("c".to_string())); - - map.insert(4, "d".to_string()); - let values: Vec<_> = map.values().collect(); - assert_eq!(values, vec!["d", "c", "a"]); - }) - .unwrap(); - } - - #[test] - fn struct_as_key() { - #[derive(BorshSerialize, BorshDeserialize, Debug, Clone, PartialEq, Eq)] - struct TestKey { - id: u64, - name: String, - } - - impl IterableMapHash for TestKey {} - - dispatch(|| { - let key1 = TestKey { - id: 1, - name: "Key1".to_string(), - }; - let key2 = TestKey { - id: 2, - name: "Key2".to_string(), - }; - let mut map = IterableMap::::new(TEST_MAP_PREFIX); - - map.insert(key1.clone(), "a".to_string()); - map.insert(key2.clone(), "b".to_string()); - - assert_eq!(map.get(&key1), Some("a".to_string())); - assert_eq!(map.get(&key2), Some("b".to_string())); - }) - .unwrap(); - } - - #[test] - fn remove_middle_of_long_chain() { - dispatch(|| { - let mut map = IterableMap::::new(TEST_MAP_PREFIX); - map.insert(1, "a".to_string()); - map.insert(2, "b".to_string()); - map.insert(3, "c".to_string()); - map.insert(4, "d".to_string()); - map.insert(5, "e".to_string()); - - // The order is 5,4,3,2,1 - map.remove(&3); // Remove the middle entry - - let values: Vec<_> = map.values().collect(); - assert_eq!(values, vec!["e", "d", "b", "a"]); - - // Check that entry 4's previous is now 2's hash - let ptr4 = map.create_root_ptr_from_key(&4u64); - let prefix = map.create_prefix_from_ptr(&ptr4); - let entry = map.get_entry(Keyspace::Context(&prefix)).unwrap(); - assert_eq!(entry.previous, Some(map.create_root_ptr_from_key(&2u64))); - }) - .unwrap(); - } - - #[test] - fn insert_after_remove_updates_head() { - dispatch(|| { - let mut map = IterableMap::::new(TEST_MAP_PREFIX); - map.insert(1, "a".to_string()); - map.insert(2, "b".to_string()); - map.remove(&2); - map.insert(3, "c".to_string()); - - let values: Vec<_> = map.values().collect(); - assert_eq!(values, vec!["c", "a"]); - }) - .unwrap(); - } - - #[test] - fn reinsert_removed_key() { - dispatch(|| { - let mut map = IterableMap::::new(TEST_MAP_PREFIX); - map.insert(1, "a".to_string()); - map.remove(&1); - map.insert(1, "b".to_string()); - - assert_eq!(map.get(&1), Some("b".to_string())); - assert_eq!(map.iter().next().unwrap().1, "b".to_string()); - }) - .unwrap(); - } - - #[test] - fn iteration_reflects_modifications() { - dispatch(|| { - let mut map = IterableMap::::new(TEST_MAP_PREFIX); - map.insert(1, "a".to_string()); - map.insert(2, "b".to_string()); - let mut iter = map.iter(); - assert_eq!(iter.next().unwrap().1, "b".to_string()); - - map.remove(&2); - map.insert(3, "c".to_string()); - let values: Vec<_> = map.values().collect(); - assert_eq!(values, vec!["c", "a"]); - }) - .unwrap(); - } - - #[test] - fn unit_struct_as_key() { - #[derive(BorshSerialize, BorshDeserialize, PartialEq)] - struct UnitKey; - - impl IterableMapHash for UnitKey {} - - dispatch(|| { - let mut map = IterableMap::::new(TEST_MAP_PREFIX); - map.insert(UnitKey, "value".to_string()); - assert_eq!(map.get(&UnitKey), Some("value".to_string())); - }) - .unwrap(); - } - - #[derive(Debug, Clone, PartialEq, Eq, BorshSerialize, BorshDeserialize)] - struct CollidingKey(u64, u64); - - impl IterableMapHash for CollidingKey { - fn compute_hash(&self) -> u64 { - let mut bytes = Vec::new(); - // Only serialize first field for hash computation - self.0.serialize(&mut bytes).unwrap(); - fnv1a_hash_64(&bytes, None) - } - } - - #[test] - fn basic_collision_handling() { - dispatch(|| { - let mut map = IterableMap::::new(TEST_MAP_PREFIX); - - // Both keys will have same hash but different actual keys - let k1 = CollidingKey(42, 1); - let k2 = CollidingKey(42, 2); - - map.insert(k1.clone(), "first".to_string()); - map.insert(k2.clone(), "second".to_string()); - - assert_eq!(map.get(&k1), Some("first".to_string())); - assert_eq!(map.get(&k2), Some("second".to_string())); - }) - .unwrap(); - } - - #[test] - fn tombstone_handling() { - dispatch(|| { - let mut map = IterableMap::::new(TEST_MAP_PREFIX); - - let k1 = CollidingKey(42, 1); - let k2 = CollidingKey(42, 2); - let k3 = CollidingKey(42, 3); - - map.insert(k1.clone(), "first".to_string()); - map.insert(k2.clone(), "second".to_string()); - map.insert(k3.clone(), "third".to_string()); - - // Remove middle entry - assert_eq!(map.remove(&k2), Some("second".to_string())); - - // Verify tombstone state - let (_, entry) = map.get_writable_slot(&k2); - assert!(entry.unwrap().value.is_none()); - - // Verify chain integrity - let values: Vec<_> = map.values().collect(); - assert_eq!(values, vec!["third", "first"]); - }) - .unwrap(); - } - - #[test] - fn tombstone_reuse() { - dispatch(|| { - let mut map = IterableMap::::new(TEST_MAP_PREFIX); - - let k1 = CollidingKey(42, 1); - let k2 = CollidingKey(42, 2); - - map.insert(k1.clone(), "first".to_string()); - map.insert(k2.clone(), "second".to_string()); - - // Removing k1 while k2 exists guarantees k1 turns into - // a tombstone - map.remove(&k1); - - // Reinsert into tombstone slot - map.insert(k1.clone(), "reused".to_string()); - - assert_eq!(map.get(&k1), Some("reused".to_string())); - assert_eq!(map.get(&k2), Some("second".to_string())); - }) - .unwrap(); - } - - #[test] - fn full_deletion_handling() { - dispatch(|| { - let mut map = IterableMap::::new(TEST_MAP_PREFIX); - - let k1 = CollidingKey(42, 1); - map.insert(k1.clone(), "lonely".to_string()); - - assert_eq!(map.remove(&k1), Some("lonely".to_string())); - - // Verify complete removal - let (_, entry) = map.get_writable_slot(&k1); - assert!(entry.is_none()); - }) - .unwrap(); - } - - #[test] - fn collision_chain_iteration() { - dispatch(|| { - let mut map = IterableMap::::new(TEST_MAP_PREFIX); - - let keys = [ - CollidingKey(42, 1), - CollidingKey(42, 2), - CollidingKey(42, 3), - ]; - - for (i, k) in keys.iter().enumerate() { - map.insert(k.clone(), format!("value-{}", i)); - } - - // Remove middle entry - map.remove(&keys[1]); - - let values: Vec<_> = map.values().collect(); - assert_eq!(values, vec!["value-2", "value-0"]); - }) - .unwrap(); - } - - #[test] - fn complex_collision_chain() { - dispatch(|| { - let mut map = IterableMap::::new(TEST_MAP_PREFIX); - - // Create 5 colliding keys - let keys: Vec<_> = (0..5).map(|i| CollidingKey(42, i)).collect(); - - // Insert all - for k in &keys { - map.insert(k.clone(), format!("{}", k.1)); - } - - // Remove even indexes - for k in keys.iter().step_by(2) { - map.remove(k); - } - - // Insert new values - map.insert(keys[0].clone(), "reinserted".to_string()); - map.insert(CollidingKey(42, 5), "new".to_string()); - - // Verify final state - let expected = vec![ - ("new".to_string(), 5), - ("reinserted".to_string(), 0), - ("3".to_string(), 3), - ("1".to_string(), 1), - ]; - - let results: Vec<_> = map.iter().map(|(k, v)| (v, k.1)).collect(); - - assert_eq!(results, expected); - }) - .unwrap(); - } - - #[test] - fn cross_bucket_reference() { - dispatch(|| { - let mut map = IterableMap::::new(TEST_MAP_PREFIX); - - // Create keys with different hashes but chained references - let k1 = CollidingKey(1, 0); - let k2 = CollidingKey(2, 0); - let k3 = CollidingKey(1, 1); // Collides with k1 - - map.insert(k1.clone(), "first".to_string()); - map.insert(k2.clone(), "second".to_string()); - map.insert(k3.clone(), "third".to_string()); - - // Remove k2 which is referenced by k3 - map.remove(&k2); - - // Verify iteration skips removed entry - let values: Vec<_> = map.values().collect(); - assert_eq!(values, vec!["third", "first"]); - }) - .unwrap(); - } - */ -} diff --git a/smart_contracts/vm2/sdk/src/collections/iterable_set.rs b/smart_contracts/vm2/sdk/src/collections/iterable_set.rs index a5faea43ec..6fd6d79098 100644 --- a/smart_contracts/vm2/sdk/src/collections/iterable_set.rs +++ b/smart_contracts/vm2/sdk/src/collections/iterable_set.rs @@ -47,174 +47,4 @@ impl IterableSet pub fn clear(&mut self) { self.map.clear(); } -} - -#[cfg(all(not(target_arch = "wasm32"), feature = "std"))] -#[cfg(test)] -mod tests { - /*#TODO fix native implementation - use super::*; - use crate::casper::native::dispatch; - use borsh::{BorshDeserialize, BorshSerialize}; - - #[test] - fn basic_insert_contains() { - dispatch(|| { - let mut set = IterableSet::new("test_set"); - assert!(!set.contains(&1)); - - set.insert(1); - assert!(set.contains(&1)); - - set.insert(2); - assert!(set.contains(&2)); - }) - .unwrap(); - } - - #[test] - fn remove_elements() { - dispatch(|| { - let mut set = IterableSet::new("test_set"); - set.insert(1); - set.insert(2); - - set.remove(&1); - assert!(!set.contains(&1)); - assert!(set.contains(&2)); - - set.remove(&2); - assert!(set.is_empty()); - }) - .unwrap(); - } - - #[test] - fn iterator_order_and_contents() { - dispatch(|| { - let mut set = IterableSet::new("test_set"); - set.insert(1); - set.insert(2); - set.insert(3); - - let mut items: Vec<_> = set.iter().collect(); - items.sort(); - assert_eq!(items, vec![1, 2, 3]); - }) - .unwrap(); - } - - #[test] - fn clear_functionality() { - dispatch(|| { - let mut set = IterableSet::new("test_set"); - set.insert(1); - set.insert(2); - - assert!(!set.is_empty()); - set.clear(); - assert!(set.is_empty()); - assert_eq!(set.iter().count(), 0); - }) - .unwrap(); - } - - #[test] - fn multiple_sets_independence() { - dispatch(|| { - let mut set1 = IterableSet::new("set1"); - let mut set2 = IterableSet::new("set2"); - - set1.insert(1); - set2.insert(1); - - assert!(set1.contains(&1)); - assert!(set2.contains(&1)); - - set1.remove(&1); - assert!(!set1.contains(&1)); - assert!(set2.contains(&1)); - }) - .unwrap(); - } - - #[derive(BorshSerialize, BorshDeserialize, Clone, Debug, PartialEq)] - struct TestStruct { - field1: u64, - field2: String, - } - - impl IterableMapHash for TestStruct {} - - #[test] - fn struct_values() { - dispatch(|| { - let val1 = TestStruct { - field1: 1, - field2: "a".to_string(), - }; - let val2 = TestStruct { - field1: 2, - field2: "b".to_string(), - }; - - let mut set = IterableSet::new("test_set"); - set.insert(val1.clone()); - set.insert(val2.clone()); - - assert!(set.contains(&val1)); - assert!(set.contains(&val2)); - - let mut collected: Vec<_> = set.iter().collect(); - collected.sort_by(|a, b| a.field1.cmp(&b.field1)); - assert_eq!(collected, vec![val1, val2]); - }) - .unwrap(); - } - - #[test] - fn duplicate_insertions() { - dispatch(|| { - let mut set = IterableSet::new("test_set"); - set.insert(1); - set.insert(1); // Should be no-op - - assert_eq!(set.iter().count(), 1); - set.remove(&1); - assert!(set.is_empty()); - }) - .unwrap(); - } - - #[test] - fn empty_set_behavior() { - dispatch(|| { - let set = IterableSet::::new("test_set"); - assert!(set.is_empty()); - assert_eq!(set.iter().count(), 0); - - let mut set = set; - set.remove(&999); // Shouldn't panic - assert!(set.is_empty()); - }) - .unwrap(); - } - - #[test] - fn complex_operations_sequence() { - dispatch(|| { - let mut set = IterableSet::new("test_set"); - set.insert(1); - set.insert(2); - set.remove(&1); - set.insert(3); - set.clear(); - set.insert(4); - - let items: Vec<_> = set.iter().collect(); - assert_eq!(items, vec![4]); - }) - .unwrap(); - } - */ -} +} \ No newline at end of file diff --git a/smart_contracts/vm2/sdk/src/collections/vector.rs b/smart_contracts/vm2/sdk/src/collections/vector.rs index 960f4862cc..45f126ce58 100644 --- a/smart_contracts/vm2/sdk/src/collections/vector.rs +++ b/smart_contracts/vm2/sdk/src/collections/vector.rs @@ -283,289 +283,4 @@ fn compute_prefix_bytes_for_index(prefix: &str, index: u64) -> Vec { let mut prefix_bytes = prefix.as_bytes().to_owned(); prefix_bytes.extend(&index.to_le_bytes()); prefix_bytes -} - -#[cfg(all(test, feature = "std"))] -pub(crate) mod tests { - /*#TODO fix native implementation - use core::ptr::NonNull; - - use self::casper::native::dispatch; - - use super::*; - - const TEST_VEC_PREFIX: &str = "test_vector"; - type VecU64 = Vector; - - fn get_vec_elements_from_storage(prefix: &str) -> Vec { - let mut values = Vec::new(); - for idx in 0..64 { - let prefix = compute_prefix_bytes_for_index(prefix, idx); - let mut value: [u8; 8] = [0; 8]; - let result = casper::read(Keyspace::Context(&prefix), |size| { - assert_eq!(size, 8); - NonNull::new(value.as_mut_ptr()) - }) - .unwrap(); - - if result.is_some() { - values.push(u64::from_le_bytes(value)); - } - } - values - } - - #[test] - fn should_not_panic_with_empty_vec() { - dispatch(|| { - let mut vec = VecU64::new(TEST_VEC_PREFIX); - assert_eq!(vec.len(), 0); - assert_eq!(vec.remove(0), None); - vec.retain(|_| false); - let _ = vec.binary_search(&123); - assert_eq!( - get_vec_elements_from_storage(TEST_VEC_PREFIX), - Vec::::new() - ); - }) - .unwrap(); - } - - #[test] - fn should_retain() { - dispatch(|| { - let mut vec = VecU64::new(TEST_VEC_PREFIX); - - vec.push(1); - vec.push(2); - vec.push(3); - vec.push(4); - vec.push(5); - - vec.retain(|v| *v % 2 == 0); - - let vec: Vec<_> = vec.iter().collect(); - assert_eq!(vec, vec![2, 4]); - - assert_eq!(get_vec_elements_from_storage(TEST_VEC_PREFIX), vec![2, 4]); - }) - .unwrap(); - } - - #[test] - fn test_vec() { - dispatch(|| { - let mut vec = VecU64::new(TEST_VEC_PREFIX); - - assert!(vec.get(0).is_none()); - vec.push(111); - assert_eq!(vec.get(0), Some(111)); - vec.push(222); - assert_eq!(vec.get(1), Some(222)); - - vec.insert(0, 42); - vec.insert(0, 41); - vec.insert(1, 43); - vec.insert(5, 333); - vec.insert(5, 334); - assert_eq!(vec.remove(5), Some(334)); - assert_eq!(vec.remove(55), None); - - let mut iter = vec.iter(); - assert_eq!(iter.next(), Some(41)); - assert_eq!(iter.next(), Some(43)); - assert_eq!(iter.next(), Some(42)); - assert_eq!(iter.next(), Some(111)); - assert_eq!(iter.next(), Some(222)); - assert_eq!(iter.next(), Some(333)); - assert_eq!(iter.next(), None); - - { - let ser = borsh::to_vec(&vec).unwrap(); - let deser: Vector = borsh::from_slice(&ser).unwrap(); - let mut iter = deser.iter(); - assert_eq!(iter.next(), Some(41)); - assert_eq!(iter.next(), Some(43)); - assert_eq!(iter.next(), Some(42)); - assert_eq!(iter.next(), Some(111)); - assert_eq!(iter.next(), Some(222)); - assert_eq!(iter.next(), Some(333)); - assert_eq!(iter.next(), None); - } - - assert_eq!( - get_vec_elements_from_storage(TEST_VEC_PREFIX), - vec![41, 43, 42, 111, 222, 333] - ); - - let vec2 = VecU64::new("test1"); - assert_eq!(vec2.get(0), None); - - assert_eq!(get_vec_elements_from_storage("test1"), Vec::::new()); - }) - .unwrap(); - } - - #[test] - fn test_pop() { - dispatch(|| { - let mut vec = VecU64::new(TEST_VEC_PREFIX); - assert_eq!(vec.pop(), None); - vec.push(1); - vec.push(2); - assert_eq!(vec.pop(), Some(2)); - assert_eq!(vec.len(), 1); - assert_eq!(vec.pop(), Some(1)); - assert!(vec.is_empty()); - - assert_eq!( - get_vec_elements_from_storage(TEST_VEC_PREFIX), - Vec::::new() - ); - }) - .unwrap(); - } - - #[test] - fn test_contains() { - dispatch(|| { - let mut vec = VecU64::new(TEST_VEC_PREFIX); - vec.push(1); - vec.push(2); - assert!(vec.contains(&1)); - assert!(vec.contains(&2)); - assert!(!vec.contains(&3)); - vec.remove(0); - assert!(!vec.contains(&1)); - assert_eq!(get_vec_elements_from_storage(TEST_VEC_PREFIX), vec![2]); - }) - .unwrap(); - } - - #[test] - fn test_clear() { - dispatch(|| { - let mut vec = VecU64::new(TEST_VEC_PREFIX); - vec.push(1); - vec.push(2); - vec.clear(); - assert_eq!(vec.len(), 0); - assert!(vec.is_empty()); - assert_eq!(vec.get(0), None); - vec.push(3); - assert_eq!(vec.get(0), Some(3)); - - assert_eq!(get_vec_elements_from_storage(TEST_VEC_PREFIX), vec![3]); - }) - .unwrap(); - } - - #[test] - fn test_binary_search() { - dispatch(|| { - let mut vec = VecU64::new(TEST_VEC_PREFIX); - vec.push(1); - vec.push(2); - vec.push(3); - vec.push(4); - vec.push(5); - assert_eq!(vec.binary_search(&3), Ok(2)); - assert_eq!(vec.binary_search(&0), Err(0)); - assert_eq!(vec.binary_search(&6), Err(5)); - }) - .unwrap(); - } - - #[test] - fn test_swap_remove() { - dispatch(|| { - let mut vec = VecU64::new(TEST_VEC_PREFIX); - vec.push(1); - vec.push(2); - vec.push(3); - vec.push(4); - assert_eq!(vec.swap_remove(1), Some(2)); - assert_eq!(vec.iter().collect::>(), vec![1, 4, 3]); - assert_eq!(vec.swap_remove(2), Some(3)); - assert_eq!(vec.iter().collect::>(), vec![1, 4]); - - assert_eq!(get_vec_elements_from_storage(TEST_VEC_PREFIX), vec![1, 4]); - }) - .unwrap(); - } - - #[test] - fn test_insert_at_len() { - dispatch(|| { - let mut vec = VecU64::new(TEST_VEC_PREFIX); - vec.push(1); - vec.insert(1, 2); - assert_eq!(vec.iter().collect::>(), vec![1, 2]); - assert_eq!(get_vec_elements_from_storage(TEST_VEC_PREFIX), vec![1, 2]); - }) - .unwrap(); - } - - #[test] - fn test_struct_elements() { - #[derive(BorshSerialize, BorshDeserialize, PartialEq, Debug)] - struct TestStruct { - field: u64, - } - - dispatch(|| { - let mut vec = Vector::new(TEST_VEC_PREFIX); - vec.push(TestStruct { field: 1 }); - vec.push(TestStruct { field: 2 }); - assert_eq!(vec.get(1), Some(TestStruct { field: 2 })); - }) - .unwrap(); - } - - #[test] - fn test_multiple_operations() { - dispatch(|| { - let mut vec = VecU64::new(TEST_VEC_PREFIX); - assert!(vec.is_empty()); - vec.push(1); - vec.insert(0, 2); - vec.push(3); - assert_eq!(vec.iter().collect::>(), vec![2, 1, 3]); - assert_eq!(vec.swap_remove(0), Some(2)); - assert_eq!(vec.iter().collect::>(), vec![3, 1]); - assert_eq!(vec.pop(), Some(1)); - assert_eq!(vec.get(0), Some(3)); - vec.clear(); - assert!(vec.is_empty()); - - assert_eq!( - get_vec_elements_from_storage(TEST_VEC_PREFIX), - Vec::::new() - ); - }) - .unwrap(); - } - - #[test] - fn test_remove_invalid_index() { - dispatch(|| { - let mut vec = VecU64::new(TEST_VEC_PREFIX); - vec.push(1); - assert_eq!(vec.remove(1), None); - assert_eq!(vec.remove(0), Some(1)); - assert_eq!(vec.remove(0), None); - }) - .unwrap(); - } - - #[test] - #[should_panic(expected = "index out of bounds")] - fn test_insert_out_of_bounds() { - dispatch(|| { - let mut vec = VecU64::new(TEST_VEC_PREFIX); - vec.insert(1, 1); - }) - .unwrap(); - } - */ -} +} \ No newline at end of file diff --git a/smart_contracts/vm2/sdk/src/compat.rs b/smart_contracts/vm2/sdk/src/compat.rs index 6423d59650..d23dbbab9c 100644 --- a/smart_contracts/vm2/sdk/src/compat.rs +++ b/smart_contracts/vm2/sdk/src/compat.rs @@ -1,36 +1,2 @@ pub mod runtime; -pub mod types; - -#[cfg(all(not(target_arch = "wasm32"), feature = "std"))] -#[cfg(test)] -mod tests { - /*#TODO fix native implementation - use crate::{ - casper::native::{dispatch_with, Environment}, - compat::types::RuntimeArgs, - }; - - use super::*; - use crate::compat::types::U512; - - #[test] - fn test_compat() { - let mut runtime_args = RuntimeArgs::new(); - runtime_args.insert("amount", U512::from(1000u64)).unwrap(); - runtime_args.insert("hello", String::from("world")).unwrap(); - runtime_args - .insert("complex type", vec![1u64, 2u64, 3u64]) - .unwrap(); - - let arg_bytes = borsh::to_vec(&runtime_args).unwrap(); - - let env = Environment::default().with_input_data(arg_bytes); - - dispatch_with(env, || { - let amount: U512 = runtime::get_named_arg("amount"); - assert_eq!(amount, U512::from(1000u64)); - }) - .expect("Dispatch failed"); - } - */ -} +pub mod types; \ No newline at end of file diff --git a/smart_contracts/vm2/sdk/src/lib.rs b/smart_contracts/vm2/sdk/src/lib.rs index bf2bf1703a..bcab6c057f 100644 --- a/smart_contracts/vm2/sdk/src/lib.rs +++ b/smart_contracts/vm2/sdk/src/lib.rs @@ -45,10 +45,6 @@ cfg_if::cfg_if! { } else { pub fn set_panic_hook() { - crate::casper::ret( - casper_executor_wasm_common::flags::ReturnFlags::REVERT, - Some(s.as_bytes()), - ); } } } From fe73b300cb2aa82d66ec0de76c28f0a4d2c18655 Mon Sep 17 00:00:00 2001 From: igor-casper Date: Tue, 18 Nov 2025 14:46:35 +0100 Subject: [PATCH 3/7] lint, fmt --- smart_contracts/vm2/sdk/src/collections/iterable_set.rs | 2 +- smart_contracts/vm2/sdk/src/collections/vector.rs | 2 +- smart_contracts/vm2/sdk/src/compat.rs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/smart_contracts/vm2/sdk/src/collections/iterable_set.rs b/smart_contracts/vm2/sdk/src/collections/iterable_set.rs index a7c64c6c93..a205f437cb 100644 --- a/smart_contracts/vm2/sdk/src/collections/iterable_set.rs +++ b/smart_contracts/vm2/sdk/src/collections/iterable_set.rs @@ -47,4 +47,4 @@ impl IterableSet pub fn clear(&mut self) { self.map.clear(); } -} \ No newline at end of file +} diff --git a/smart_contracts/vm2/sdk/src/collections/vector.rs b/smart_contracts/vm2/sdk/src/collections/vector.rs index 78361fe0a8..50c2acb39b 100644 --- a/smart_contracts/vm2/sdk/src/collections/vector.rs +++ b/smart_contracts/vm2/sdk/src/collections/vector.rs @@ -342,4 +342,4 @@ fn compute_prefix_bytes_for_index(prefix: &str, index: u64) -> Vec { let mut prefix_bytes = prefix.as_bytes().to_owned(); prefix_bytes.extend(&index.to_le_bytes()); prefix_bytes -} \ No newline at end of file +} diff --git a/smart_contracts/vm2/sdk/src/compat.rs b/smart_contracts/vm2/sdk/src/compat.rs index d23dbbab9c..5732f354c0 100644 --- a/smart_contracts/vm2/sdk/src/compat.rs +++ b/smart_contracts/vm2/sdk/src/compat.rs @@ -1,2 +1,2 @@ pub mod runtime; -pub mod types; \ No newline at end of file +pub mod types; From 5ed9206a88b8cb50df71b63455073784bf94d1ca Mon Sep 17 00:00:00 2001 From: igor-casper Date: Tue, 18 Nov 2025 16:14:46 +0100 Subject: [PATCH 4/7] restore symbols --- smart_contracts/vm2/sdk/src/casper.rs | 2 + smart_contracts/vm2/sdk/src/casper/native.rs | 348 +++++++++++++++++++ 2 files changed, 350 insertions(+) create mode 100644 smart_contracts/vm2/sdk/src/casper/native.rs diff --git a/smart_contracts/vm2/sdk/src/casper.rs b/smart_contracts/vm2/sdk/src/casper.rs index 9b06297416..3961ce31df 100644 --- a/smart_contracts/vm2/sdk/src/casper.rs +++ b/smart_contracts/vm2/sdk/src/casper.rs @@ -1,4 +1,6 @@ pub mod altbn128; +#[cfg(all(not(target_arch = "wasm32"), feature = "std"))] +pub mod native; #[cfg(all(not(target_arch = "wasm32"), feature = "std"))] use crate::abi::{ABITypeInfo, CasperABI, EnumVariant}; diff --git a/smart_contracts/vm2/sdk/src/casper/native.rs b/smart_contracts/vm2/sdk/src/casper/native.rs new file mode 100644 index 0000000000..491c4b518d --- /dev/null +++ b/smart_contracts/vm2/sdk/src/casper/native.rs @@ -0,0 +1,348 @@ +use std::{ + cell::RefCell, + collections::{BTreeMap, BTreeSet, VecDeque}, + convert::Infallible, + fmt, + panic::{self, UnwindSafe}, + ptr::{self, NonNull}, + slice, + sync::{Arc, RwLock}, +}; + +use crate::linkme::distributed_slice; +use bytes::Bytes; +use casper_executor_wasm_common::{ + error::{ + CALLEE_ROLLED_BACK, CALLEE_SUCCEEDED, CALLEE_TRAPPED, HOST_ERROR_INTERNAL, + HOST_ERROR_NOT_FOUND, HOST_ERROR_SUCCESS, + }, + flags::ReturnFlags, +}; +#[cfg(not(target_arch = "wasm32"))] +use rand::Rng; + +use super::Entity; +use crate::types::Address; + +#[repr(C)] +pub struct Param { + pub name_ptr: *const u8, + pub name_len: usize, +} + +/// The kind of export that is being registered. +/// +/// This is used to identify the type of export and its name. +/// +/// Depending on the location of given function it may be registered as a: +/// +/// * `SmartContract` (if it's part of a `impl Contract` block), +/// * `TraitImpl` (if it's part of a `impl Trait for Contract` block), +/// * `Function` (if it's a standalone function). +/// +/// This is used to dispatch exports under native code i.e. you want to write a test that calls +/// "foobar" regardless of location. +#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)] +pub enum EntryPointKind { + /// Smart contract. + /// + /// This is used to identify the smart contract and its name. + /// + /// The `struct_name` is the name of the smart contract that is being registered. + /// The `name` is the name of the function that is being registered. + SmartContract { + struct_name: &'static str, + name: &'static str, + }, + /// Trait implementation. + /// + /// This is used to identify the trait implementation and its name. + /// + /// The `trait_name` is the name of the trait that is being implemented. + /// The `impl_name` is the name of the implementation. + /// The `name` is the name of the function that is being implemented. + TraitImpl { + trait_name: &'static str, + impl_name: &'static str, + name: &'static str, + }, + /// Function export. + /// + /// This is used to identify the function export and its name. + /// + /// The `name` is the name of the function that is being exported. + Function { name: &'static str }, +} + +impl EntryPointKind { + pub fn name(&self) -> &'static str { + match self { + EntryPointKind::SmartContract { name, .. } + | EntryPointKind::TraitImpl { name, .. } + | EntryPointKind::Function { name } => name, + } + } +} + +/// Export is a structure that contains information about the exported function. +/// +/// This is used to register the export and its name and physical location in the smart contract +/// source code. +pub struct EntryPoint { + /// The kind of entry point that is being registered. + pub kind: EntryPointKind, + pub fptr: fn() -> (), + pub module_path: &'static str, + pub file: &'static str, + pub line: u32, +} + +#[distributed_slice] +#[linkme(crate = crate::linkme)] +pub static ENTRY_POINTS: [EntryPoint]; + +impl fmt::Debug for EntryPoint { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let Self { + kind, + fptr: _, + module_path, + file, + line, + } = self; + + f.debug_struct("Export") + .field("kind", kind) + .field("fptr", &"") + .field("module_path", module_path) + .field("file", file) + .field("line", line) + .finish() + } +} + +/// Invokes an export by its name. +/// +/// This function is used to invoke an export by its name regardless of its location in the smart +/// contract. +pub fn invoke_export_by_name(name: &str) { + let all_entry_points = ENTRY_POINTS.iter().collect::>(); + + let exports_by_name: Vec<_> = all_entry_points + .iter() + .filter(|export| export.kind.name() == name) + .collect(); + + if exports_by_name.len() != 1 { + panic!( + "Expected exactly one export {} found, but got {:?} ({:?})", + name, exports_by_name, all_entry_points + ); + } + + let result = dispatch_export_call(exports_by_name[0].fptr); + + match result { + Ok(()) => {} + Err(trap) => { + match trap { + NativeTrap::Panic(panic_payload) => { + // Re-raise the panic so it can be caught by test's #[should_panic] + std::panic::resume_unwind(panic_payload); + } + other_trap => { + // For non-panic traps, set them in LAST_TRAP + LAST_TRAP.with(|last_trap| { + last_trap.borrow_mut().replace(other_trap); + }); + } + } + } + } +} + +#[derive(Debug)] +pub enum NativeTrap { + Return(ReturnFlags, Bytes), + Panic(Box), +} + +pub type Container = BTreeMap>; + +#[derive(Clone, Debug)] +#[allow(dead_code)] +pub struct NativeParam(pub(crate) String); + +impl From<&Param> for NativeParam { + fn from(val: &Param) -> Self { + let name = + String::from_utf8_lossy(unsafe { slice::from_raw_parts(val.name_ptr, val.name_len) }) + .into_owned(); + NativeParam(name) + } +} + +#[derive(Clone, Debug)] +pub struct Environment { + pub db: Arc>, + contracts: Arc>>, + // input_data: Arc>>, + input_data: Option, + caller: Entity, + callee: Entity, +} + +impl Default for Environment { + fn default() -> Self { + Self { + db: Default::default(), + contracts: Default::default(), + input_data: Default::default(), + caller: DEFAULT_ADDRESS, + callee: DEFAULT_ADDRESS, + } + } +} + +pub const DEFAULT_ADDRESS: Entity = Entity::Account([42; 32]); + +impl Environment { + #[must_use] + pub fn new(db: Container, caller: Entity) -> Self { + Self { + db: Arc::new(RwLock::new(db)), + contracts: Default::default(), + input_data: Default::default(), + caller, + callee: caller, + } + } + + #[must_use] + pub fn with_caller(&self, caller: Entity) -> Self { + let mut env = self.clone(); + env.caller = caller; + env + } + + #[must_use] + pub fn smart_contract(&self, callee: Entity) -> Self { + let mut env = self.clone(); + env.caller = self.callee; + env.callee = callee; + env + } + + #[must_use] + pub fn session(&self, callee: Entity) -> Self { + let mut env = self.clone(); + env.caller = callee; + env.callee = callee; + env + } + + #[must_use] + pub fn with_callee(&self, callee: Entity) -> Self { + let mut env = self.clone(); + env.callee = callee; + env + } + + #[must_use] + pub fn with_input_data(&self, input_data: Vec) -> Self { + let mut env = self.clone(); + env.input_data = Some(Bytes::from(input_data)); + env + } +} + +thread_local! { + pub(crate) static LAST_TRAP: RefCell> = const { RefCell::new(None) }; + static ENV_STACK: RefCell> = RefCell::new(VecDeque::from_iter([ + // Stack of environments has a default element so unit tests do not require extra effort. + // Environment::default() + ])); +} + +pub fn with_current_environment(f: impl FnOnce(Environment) -> T) -> T { + ENV_STACK.with(|stack| { + let stub = { + let borrowed = stack.borrow(); + let front = borrowed.front().expect("Stub exists").clone(); + front + }; + f(stub) + }) +} + +pub fn current_environment() -> Environment { + with_current_environment(|env| env) +} + +fn dispatch_export_call(func: F) -> Result<(), NativeTrap> +where + F: FnOnce() + Send + UnwindSafe, +{ + let call_result = panic::catch_unwind(|| { + func(); + }); + match call_result { + Ok(()) => { + let last_trap = LAST_TRAP.with(|last_trap| last_trap.borrow_mut().take()); + match last_trap { + Some(last_trap) => Err(last_trap), + None => Ok(()), + } + } + Err(error) => Err(NativeTrap::Panic(error)), + } +} + +/// Dispatches a function with a default environment. +pub fn dispatch(f: impl FnOnce() -> T) -> Result { + dispatch_with(Environment::default(), f) +} + +/// Dispatches a function with a given environment. +pub fn dispatch_with(stub: Environment, f: impl FnOnce() -> T) -> Result { + ENV_STACK.with(|stack| { + let mut borrowed = stack.borrow_mut(); + borrowed.push_front(stub); + }); + + // Clear previous trap (if present) + LAST_TRAP.with(|last_trap| last_trap.borrow_mut().take()); + + // Call a function + let result = f(); + + // Check if a trap was set and return it if so (otherwise return the result). + let last_trap = LAST_TRAP.with(|last_trap| last_trap.borrow_mut().take()); + + let result = if let Some(trap) = last_trap { + Err(trap) + } else { + Ok(result) + }; + + // Pop the stub from the stack + ENV_STACK.with(|stack| { + let mut borrowed = stack.borrow_mut(); + borrowed.pop_front(); + }); + + result +} + +mod symbols { + #[no_mangle] + pub extern "C" fn casper_ffi( + _system_contract_opt: u32, + _input_ptr: *const u8, + _input_size: usize, + _alloc: extern "C" fn(usize, *mut core::ffi::c_void) -> *mut u8, + _alloc_ctx: *const core::ffi::c_void, + ) -> u32 { + todo!() + } +} \ No newline at end of file From 92c72a5ed94779693188d8af0758189166311ac0 Mon Sep 17 00:00:00 2001 From: igor-casper Date: Tue, 18 Nov 2025 16:22:24 +0100 Subject: [PATCH 5/7] lint, fmt --- smart_contracts/vm2/sdk/src/casper/native.rs | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/smart_contracts/vm2/sdk/src/casper/native.rs b/smart_contracts/vm2/sdk/src/casper/native.rs index 491c4b518d..569df09aec 100644 --- a/smart_contracts/vm2/sdk/src/casper/native.rs +++ b/smart_contracts/vm2/sdk/src/casper/native.rs @@ -1,10 +1,8 @@ use std::{ cell::RefCell, - collections::{BTreeMap, BTreeSet, VecDeque}, - convert::Infallible, + collections::{BTreeMap, VecDeque}, fmt, panic::{self, UnwindSafe}, - ptr::{self, NonNull}, slice, sync::{Arc, RwLock}, }; @@ -12,17 +10,10 @@ use std::{ use crate::linkme::distributed_slice; use bytes::Bytes; use casper_executor_wasm_common::{ - error::{ - CALLEE_ROLLED_BACK, CALLEE_SUCCEEDED, CALLEE_TRAPPED, HOST_ERROR_INTERNAL, - HOST_ERROR_NOT_FOUND, HOST_ERROR_SUCCESS, - }, flags::ReturnFlags, }; -#[cfg(not(target_arch = "wasm32"))] -use rand::Rng; use super::Entity; -use crate::types::Address; #[repr(C)] pub struct Param { @@ -185,8 +176,6 @@ impl From<&Param> for NativeParam { #[derive(Clone, Debug)] pub struct Environment { pub db: Arc>, - contracts: Arc>>, - // input_data: Arc>>, input_data: Option, caller: Entity, callee: Entity, @@ -196,7 +185,6 @@ impl Default for Environment { fn default() -> Self { Self { db: Default::default(), - contracts: Default::default(), input_data: Default::default(), caller: DEFAULT_ADDRESS, callee: DEFAULT_ADDRESS, @@ -211,7 +199,6 @@ impl Environment { pub fn new(db: Container, caller: Entity) -> Self { Self { db: Arc::new(RwLock::new(db)), - contracts: Default::default(), input_data: Default::default(), caller, callee: caller, From 6248e693e17d09fb90a32ebc8b531d1601553c9d Mon Sep 17 00:00:00 2001 From: igor-casper Date: Tue, 18 Nov 2025 16:27:32 +0100 Subject: [PATCH 6/7] fmt --- smart_contracts/vm2/sdk/src/casper/native.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/smart_contracts/vm2/sdk/src/casper/native.rs b/smart_contracts/vm2/sdk/src/casper/native.rs index 569df09aec..3fece80a80 100644 --- a/smart_contracts/vm2/sdk/src/casper/native.rs +++ b/smart_contracts/vm2/sdk/src/casper/native.rs @@ -9,9 +9,7 @@ use std::{ use crate::linkme::distributed_slice; use bytes::Bytes; -use casper_executor_wasm_common::{ - flags::ReturnFlags, -}; +use casper_executor_wasm_common::flags::ReturnFlags; use super::Entity; @@ -332,4 +330,4 @@ mod symbols { ) -> u32 { todo!() } -} \ No newline at end of file +} From a4cfdfe7eec1ff57b80b037c7bb2b3ecca21a5f9 Mon Sep 17 00:00:00 2001 From: igor-casper Date: Fri, 5 Dec 2025 16:45:23 +0100 Subject: [PATCH 7/7] merge fixup --- smart_contracts/vm2/sdk/src/types.rs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/smart_contracts/vm2/sdk/src/types.rs b/smart_contracts/vm2/sdk/src/types.rs index 5fd3c92429..6b261adbf9 100644 --- a/smart_contracts/vm2/sdk/src/types.rs +++ b/smart_contracts/vm2/sdk/src/types.rs @@ -3,10 +3,8 @@ use core::marker::PhantomData; use casper_contract_macros::TypeUid; use casper_executor_wasm_common::{ error::{ - CALLEE_API_ERROR, CALLEE_CODE_NOT_FOUND, CALLEE_ENTITY_NOT_FOUND, CALLEE_GAS_DEPLETED, - CALLEE_INPUT_INVALID, CALLEE_LOCKED_PACKAGE, CALLEE_NOT_CALLABLE, - CALLEE_NO_ACTIVE_CONTRACT, CALLEE_ROLLED_BACK, CALLEE_TRAPPED, - CALLEE_GAS_DEPLETED, CALLEE_INPUT_INVALID, CALLEE_NOT_CALLABLE, CALLEE_REVERT_ERROR, + CALLEE_CODE_NOT_FOUND, CALLEE_ENTITY_NOT_FOUND, CALLEE_GAS_DEPLETED, CALLEE_INPUT_INVALID, + CALLEE_LOCKED_PACKAGE, CALLEE_NOT_CALLABLE, CALLEE_NO_ACTIVE_CONTRACT, CALLEE_REVERT_ERROR, CALLEE_ROLLED_BACK, CALLEE_TRAPPED, }, keyspace::Keyspace, @@ -232,7 +230,6 @@ impl TryFrom for CallError { CALLEE_GAS_DEPLETED => Ok(Self::CalleeGasDepleted), CALLEE_NOT_CALLABLE => Ok(Self::NotCallable), CALLEE_INPUT_INVALID => Ok(Self::InputInvalid), - CALLEE_API_ERROR => Ok(Self::CalleeReverted), CALLEE_NO_ACTIVE_CONTRACT => Ok(Self::NoActiveContract), CALLEE_CODE_NOT_FOUND => Ok(Self::CodeNotFound), CALLEE_ENTITY_NOT_FOUND => Ok(Self::EntityNotFound),