From 3b54070868f00ff8a9f7f0be59f183d21ebab713 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Fri, 11 Jul 2025 09:46:35 +0100 Subject: [PATCH 1/7] Fix wasm host-in-guest alloc by injecting, exporting and using fn --- crates/vm/src/wasm/memory.rs | 41 +++++++-- crates/vm/src/wasm/run.rs | 174 +++++++++++++++++++++++++++++++++-- 2 files changed, 202 insertions(+), 13 deletions(-) diff --git a/crates/vm/src/wasm/memory.rs b/crates/vm/src/wasm/memory.rs index 480ff03773b..330a7301229 100644 --- a/crates/vm/src/wasm/memory.rs +++ b/crates/vm/src/wasm/memory.rs @@ -46,6 +46,8 @@ pub enum Error { Arith(#[from] arith::Error), #[error("{0}")] TryFromInt(#[from] std::num::TryFromIntError), + #[error("Failed to allocate memory: {0}")] + GuestAlloc(wasmer::RuntimeError), } /// Result of a function that may fail @@ -107,15 +109,16 @@ pub struct TxCallInput { /// Write transaction inputs into wasm memory pub fn write_tx_inputs( + instance: &wasmer::Instance, store: &mut impl wasmer::AsStoreMut, memory: &wasmer::Memory, tx_data: &BatchedTxRef<'_>, ) -> Result { - let tx_data_ptr = 0; let tx_data_bytes = tx_data.serialize_to_vec(); - let tx_data_len = tx_data_bytes.len() as _; + let tx_data_len = tx_data_bytes.len() as u64; + let tx_data_ptr = wasm_alloc(instance, store, tx_data_len)?; - write_memory_bytes(store, memory, tx_data_ptr, tx_data_bytes)?; + write_memory_bytes(store, memory, tx_data_ptr, &tx_data_bytes)?; Ok(TxCallInput { tx_data_ptr, @@ -146,6 +149,7 @@ pub struct VpCallInput { /// Write validity predicate inputs into wasm memory pub fn write_vp_inputs( + instance: &wasmer::Instance, store: &mut impl wasmer::AsStoreMut, memory: &wasmer::Memory, VpInput { @@ -155,20 +159,16 @@ pub fn write_vp_inputs( verifiers, }: VpInput<'_>, ) -> Result { - let addr_ptr = 0_u64; let addr_bytes = addr.serialize_to_vec(); let addr_len = addr_bytes.len() as _; let data_bytes = data.serialize_to_vec(); - let data_ptr = checked!(addr_ptr + addr_len)?; let data_len = data_bytes.len() as _; let keys_changed_bytes = keys_changed.serialize_to_vec(); - let keys_changed_ptr = checked!(data_ptr + data_len)?; let keys_changed_len = keys_changed_bytes.len() as _; let verifiers_bytes = verifiers.serialize_to_vec(); - let verifiers_ptr = checked!(keys_changed_ptr + keys_changed_len)?; let verifiers_len = verifiers_bytes.len() as _; let bytes = [ @@ -178,6 +178,13 @@ pub fn write_vp_inputs( &verifiers_bytes[..], ] .concat(); + let bytes_len = bytes.len() as u64; + + let addr_ptr = wasm_alloc(instance, store, bytes_len)?; + let data_ptr = checked!(addr_ptr + addr_len)?; + let keys_changed_ptr = checked!(data_ptr + data_len)?; + let verifiers_ptr = checked!(keys_changed_ptr + keys_changed_len)?; + write_memory_bytes(store, memory, addr_ptr, bytes)?; Ok(VpCallInput { @@ -192,6 +199,26 @@ pub fn write_vp_inputs( }) } +/// Allocated inside the wasm instance using the injected function and return a +/// ptr to it +fn wasm_alloc( + instance: &wasmer::Instance, + store: &mut impl wasmer::AsStoreMut, + len: u64, +) -> Result { + let alloc_fn = instance + .exports + .get_function(super::run::ALLOC_FN_NAME) + .unwrap() + .typed::(&*store) + .unwrap(); + + let len: i32 = len.try_into().map_err(Error::TryFromInt)?; + let ptr = alloc_fn.call(&mut *store, len).map_err(Error::GuestAlloc)?; + let ptr: u64 = ptr.try_into().map_err(Error::TryFromInt)?; + Ok(ptr) +} + /// Check that the given offset and length fits into the memory bounds. If not, /// it will try to grow the memory. fn check_bounds( diff --git a/crates/vm/src/wasm/run.rs b/crates/vm/src/wasm/run.rs index a94fbb3a509..a70b8a1f6ea 100644 --- a/crates/vm/src/wasm/run.rs +++ b/crates/vm/src/wasm/run.rs @@ -37,6 +37,9 @@ use crate::{ validate_untrusted_wasm, }; +/// Allocator function name injected into a expored from wasm +pub const ALLOC_FN_NAME: &str = "_injected_alloc"; + const TX_ENTRYPOINT: &str = "_apply_tx"; const VP_ENTRYPOINT: &str = "_validate_tx"; const WASM_STACK_LIMIT: u32 = u16::MAX as u32; @@ -59,6 +62,8 @@ pub enum Error { MemoryError(memory::Error), #[error("Unable to inject stack limiter")] StackLimiterInjection, + #[error("Unable to inject alloc")] + AllocInjection, #[error("Wasm deserialization error: {0}")] DeserializationError(elements::Error), #[error("Wasm serialization error: {0}")] @@ -240,15 +245,18 @@ where env.memory.init_from(guest_memory); - // Write the inputs in the memory exported from the wasm - // module let memory::TxCallInput { tx_data_ptr, tx_data_len, } = { let mut store = store.borrow_mut(); - memory::write_tx_inputs(&mut *store, guest_memory, &batched_tx) - .map_err(Error::MemoryError)? + memory::write_tx_inputs( + &instance, + &mut *store, + guest_memory, + &batched_tx, + ) + .map_err(Error::MemoryError)? }; // Get the module's entrypoint to be called @@ -437,7 +445,7 @@ where verifiers_len, } = { let mut store = store.borrow_mut(); - memory::write_vp_inputs(&mut *store, guest_memory, input) + memory::write_vp_inputs(&instance, &mut *store, guest_memory, input) .map_err(Error::MemoryError)? }; @@ -694,9 +702,105 @@ pub fn prepare_wasm_code>(code: T) -> Result> { let module = wasm_instrument::inject_stack_limiter(module, WASM_STACK_LIMIT) .map_err(|_original_module| Error::StackLimiterInjection)?; + + let module = inject_alloc(module)?; + elements::serialize(module).map_err(Error::SerializationError) } +/// Inject and export allocation function that can be used to grow memory from +/// the host side +fn inject_alloc(module: elements::Module) -> Result { + use elements::{BlockType, Instruction, Instructions, ValueType}; + use parity_wasm::builder; + + // Index of the function to be injected (note that the result of + // `builder.push_function` is wrong as it ignores improted fns) + let fn_ix: u32 = module + .functions_space() + .try_into() + .map_err(|_| Error::AllocInjection)?; + let mut builder = builder::from_module(module); + + const WASM_PAGE_SIZE_LOG2: i32 = 16; + const WASM_PAGE_SIZE: i32 = 1 << WASM_PAGE_SIZE_LOG2; + + // Alloc fn in WAT: + // + // (func (;0;) (type 0) (param i32) (result i32) + // block ;; label = @1 + // local.get 0 + // i32.const -65536 + // i32.le_u + // if ;; label = @2 + // local.get 0 + // i32.const 65535 + // i32.add + // i32.const 16 + // i32.shr_u + // memory.grow + // local.tee 0 + // i32.const -1 + // i32.ne + // br_if 1 (;@1;) + // end + // unreachable + // end + // local.get 0 + // i32.const 16 + // i32.shl) + + let instructions = { + use Instruction::*; + vec![ + Block(BlockType::NoResult), + GetLocal(0), + I32Const(-WASM_PAGE_SIZE), + I32LeU, + If(BlockType::NoResult), + GetLocal(0), + I32Const(WASM_PAGE_SIZE - 1), + I32Add, + I32Const(WASM_PAGE_SIZE_LOG2), + I32ShrU, + GrowMemory(0), + TeeLocal(0), + I32Const(-1), + I32Ne, + BrIf(1), + End, + Unreachable, + End, + GetLocal(0), + I32Const(WASM_PAGE_SIZE_LOG2), + I32Shl, + End, + ] + }; + + builder.push_function( + builder::function() + .signature() + .with_param(ValueType::I32) + .with_result(ValueType::I32) + .build() + .body() + .with_instructions(Instructions::new(instructions)) + .build() + .build(), + ); + + builder.push_export( + builder::export() + .field(ALLOC_FN_NAME) + .internal() + .func(fn_ix) + .build(), + ); + + Ok(builder.build()) +} + // Fetch or compile a WASM code from the cache or storage. Account for the // loading and code compilation gas costs. fn fetch_or_compile( @@ -1017,7 +1121,7 @@ mod tests { use namada_core::borsh::BorshSerializeExt; use namada_state::StorageWrite; use namada_state::testing::TestState; - use namada_test_utils::TestWasms; + use namada_test_utils::{TestWasms, tx_data}; use namada_token::DenominatedAmount; use namada_tx::data::eval_vp::EvalVp; use namada_tx::data::{Fee, TxType}; @@ -2178,6 +2282,64 @@ mod tests { ) } + /// Test that a tx which is larger than the initial WASM memory size gets + /// executed without issues (the injected alloc fn should be invoked from + /// host) + #[test] + fn test_tx_alloc() { + let mut state = TestState::default(); + let gas_meter = RefCell::new(TxGasMeter::new(TX_GAS_LIMIT, GAS_SCALE)); + let tx_index = TxIndex::default(); + + let tx_write = TestWasms::TxWriteStorageKey.read_bytes(); + // store the wasm code + let code_hash = Hash::sha256(&tx_write); + let key = Key::wasm_code(&code_hash); + let len_key = Key::wasm_code_len(&code_hash); + let code_len = (tx_write.len() as u64).serialize_to_vec(); + let _ = state + .write_log_mut() + .write(&key, tx_write.serialize_to_vec()) + .unwrap(); + let _ = state.write_log_mut().write(&len_key, code_len).unwrap(); + + // Using `2^21` (2 MiB) for the input should be above the initial memory + // size and require allocation + let len = 2_usize.pow(21); + let value: Vec = vec![6_u8; len]; + let key_raw = "key"; + let key = Key::parse(key_raw).unwrap(); + let tx_data = tx_data::TxWriteData { + key: key.clone(), + value: value.clone(), + } + .serialize_to_vec(); + + let (mut vp_cache, _) = + wasm::compilation_cache::common::testing::vp_cache(); + let (mut tx_cache, _) = + wasm::compilation_cache::common::testing::tx_cache(); + + let mut outer_tx = Tx::from_type(TxType::Raw); + outer_tx.set_code(Code::new(tx_write, None)); + outer_tx.set_data(Data::new(tx_data)); + let batched_tx = outer_tx.batch_ref_first_tx().unwrap(); + tx( + &mut state, + &gas_meter, + None, + &tx_index, + batched_tx.tx, + batched_tx.cmt, + &mut vp_cache, + &mut tx_cache, + ) + .unwrap(); + + let written_value = state.read_bytes(&key).unwrap().unwrap(); + assert_eq!(value, written_value); + } + fn loop_in_tx_wasm(loops: u32) -> Result> { // A transaction with a recursive loop. // The boilerplate code is generated from tx_template.wasm using From a2bb2ad899fdf8feca071644946f14d23ed3a37d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Fri, 11 Jul 2025 09:48:07 +0100 Subject: [PATCH 2/7] increase the wasm memory limits --- crates/vm/src/wasm/memory.rs | 6 +-- crates/vm/src/wasm/run.rs | 86 ++++++++++++++---------------------- 2 files changed, 36 insertions(+), 56 deletions(-) diff --git a/crates/vm/src/wasm/memory.rs b/crates/vm/src/wasm/memory.rs index 330a7301229..9c164d63c1d 100644 --- a/crates/vm/src/wasm/memory.rs +++ b/crates/vm/src/wasm/memory.rs @@ -67,11 +67,11 @@ impl From for namada_state::Error { /// Initial pages in tx memory pub const TX_MEMORY_INIT_PAGES: u32 = 100; // 6.4 MiB /// Mamixmum pages in tx memory -pub const TX_MEMORY_MAX_PAGES: u32 = 200; // 12.8 MiB +pub const TX_MEMORY_MAX_PAGES: u32 = 400; // 25.6 MiB /// Initial pages in VP memory -pub const VP_MEMORY_INIT_PAGES: u32 = 100; // 6.4 MiB +pub const VP_MEMORY_INIT_PAGES: u32 = TX_MEMORY_INIT_PAGES; // 6.4 MiB /// Mamixmum pages in VP memory -pub const VP_MEMORY_MAX_PAGES: u32 = 200; // 12.8 MiB +pub const VP_MEMORY_MAX_PAGES: u32 = TX_MEMORY_MAX_PAGES; // 25.6 MiB /// Prepare memory for instantiating a transaction module pub fn prepare_tx_memory( diff --git a/crates/vm/src/wasm/run.rs b/crates/vm/src/wasm/run.rs index a70b8a1f6ea..90eb0d03ebb 100644 --- a/crates/vm/src/wasm/run.rs +++ b/crates/vm/src/wasm/run.rs @@ -1284,12 +1284,12 @@ mod tests { let _ = state.write_log_mut().write(&key, tx_code.clone()).unwrap(); let _ = state.write_log_mut().write(&len_key, code_len).unwrap(); - // Assuming 200 pages, 12.8 MiB limit - assert_eq!(memory::TX_MEMORY_MAX_PAGES, 200); + // Assuming 400 pages, 25.6 MiB limit + assert_eq!(memory::TX_MEMORY_MAX_PAGES, 400); - // Allocating `2^23` (8 MiB) should be below the memory limit and + // Allocating `2^24` (16 MiB) should be below the memory limit and // shouldn't fail - let tx_data = 2_usize.pow(23).serialize_to_vec(); + let tx_data = 2_usize.pow(24).serialize_to_vec(); let (mut vp_cache, _) = wasm::compilation_cache::common::testing::vp_cache(); let (mut tx_cache, _) = @@ -1310,9 +1310,9 @@ mod tests { ); assert!(result.is_ok(), "Expected success, got {:?}", result); - // Allocating `2^24` (16 MiB) should be above the memory limit and + // Allocating `2^25` (32 MiB) should be above the memory limit and // should fail - let tx_data = 2_usize.pow(24).serialize_to_vec(); + let tx_data = 2_usize.pow(25).serialize_to_vec(); let mut outer_tx = Tx::from_type(TxType::Raw); outer_tx.set_code(Code::new(tx_code, None)); outer_tx.set_data(Data::new(tx_data)); @@ -1365,12 +1365,12 @@ mod tests { state.write(&key, vp_memory_limit).unwrap(); state.write(&len_key, code_len).unwrap(); - // Assuming 200 pages, 12.8 MiB limit - assert_eq!(memory::VP_MEMORY_MAX_PAGES, 200); + // Assuming 400 pages, 25.6 MiB limit + assert_eq!(memory::VP_MEMORY_MAX_PAGES, 400); - // Allocating `2^23` (8 MiB) should be below the memory limit and + // Allocating `2^24` (16 MiB) should be below the memory limit and // shouldn't fail - let input = 2_usize.pow(23).serialize_to_vec(); + let input = 2_usize.pow(24).serialize_to_vec(); let mut tx = Tx::new(state.in_mem().chain_id.clone(), None); tx.add_code(vec![], None).add_serialized_data(input); @@ -1402,9 +1402,9 @@ mod tests { .is_ok() ); - // Allocating `2^24` (16 MiB) should be above the memory limit and + // Allocating `2^25` (32 MiB) should be above the memory limit and // should fail - let input = 2_usize.pow(24).serialize_to_vec(); + let input = 2_usize.pow(25).serialize_to_vec(); let mut tx = Tx::new(state.in_mem().chain_id.clone(), None); tx.add_code(vec![], None).add_data(input); @@ -1458,12 +1458,12 @@ mod tests { state.write(&key, vp_code).unwrap(); state.write(&len_key, code_len).unwrap(); - // Assuming 200 pages, 12.8 MiB limit - assert_eq!(memory::VP_MEMORY_MAX_PAGES, 200); + // Assuming 400 pages, 25.6 MiB limit + assert_eq!(memory::VP_MEMORY_MAX_PAGES, 400); - // Allocating `2^23` (8 MiB) should be below the memory limit and + // Allocating `2^24` (16 MiB) should be below the memory limit and // shouldn't fail - let tx_data = 2_usize.pow(23).serialize_to_vec(); + let tx_data = 2_usize.pow(24).serialize_to_vec(); let mut outer_tx = Tx::from_type(TxType::Raw); outer_tx.header.chain_id = state.in_mem().chain_id.clone(); outer_tx.set_data(Data::new(tx_data)); @@ -1483,9 +1483,9 @@ mod tests { ); assert!(result.is_ok(), "Expected success, got {:?}", result); - // Allocating `2^24` (16 MiB) should be above the memory limit and + // Allocating `2^25` (32 MiB) should be above the memory limit and // should fail - let tx_data = 2_usize.pow(24).serialize_to_vec(); + let tx_data = 2_usize.pow(25).serialize_to_vec(); let mut outer_tx = Tx::from_type(TxType::Raw); outer_tx.header.chain_id = state.in_mem().chain_id.clone(); outer_tx.set_data(Data::new(tx_data)); @@ -1525,12 +1525,12 @@ mod tests { .unwrap(); let _ = state.write_log_mut().write(&len_key, code_len).unwrap(); - // Assuming 200 pages, 12.8 MiB limit - assert_eq!(memory::TX_MEMORY_MAX_PAGES, 200); + // Assuming 400 pages, 25.6 MiB limit + assert_eq!(memory::TX_MEMORY_MAX_PAGES, 400); - // Allocating `2^24` (16 MiB) for the input should be above the memory + // Allocating `2^25` (32 MiB) for the input should be above the memory // limit and should fail - let len = 2_usize.pow(24); + let len = 2_usize.pow(25); let tx_data: Vec = vec![6_u8; len]; let (mut vp_cache, _) = wasm::compilation_cache::common::testing::vp_cache(); @@ -1553,17 +1553,8 @@ mod tests { // Depending on platform, we get a different error from the running out // of memory match result { - // Dylib engine error (used anywhere except mac) - Err(Error::MemoryError(memory::Error::Grow( - wasmer::MemoryError::CouldNotGrow { .. }, - ))) => {} - Err(error) => { - let trap_code = get_trap_code(&error); - // Universal engine error (currently used on mac) - assert_eq!( - trap_code, - Either::Left(wasmer_vm::TrapCode::HeapAccessOutOfBounds) - ); + Err(Error::MemoryError(memory::Error::GuestAlloc(_))) => { + // as expected } _ => panic!("Expected to run out of memory, got {:?}", result), } @@ -1591,12 +1582,12 @@ mod tests { state.write(&key, vp_code).unwrap(); state.write(&len_key, code_len).unwrap(); - // Assuming 200 pages, 12.8 MiB limit - assert_eq!(memory::VP_MEMORY_MAX_PAGES, 200); + // Assuming 400 pages, 25.6 MiB limit + assert_eq!(memory::VP_MEMORY_MAX_PAGES, 400); - // Allocating `2^24` (16 MiB) for the input should be above the memory + // Allocating `2^25` (32 MiB) for the input should be above the memory // limit and should fail - let len = 2_usize.pow(24); + let len = 2_usize.pow(25); let tx_data: Vec = vec![6_u8; len]; let mut outer_tx = Tx::from_type(TxType::Raw); outer_tx.header.chain_id = state.in_mem().chain_id.clone(); @@ -1618,20 +1609,9 @@ mod tests { // Depending on platform, we get a different error from the running out // of memory match result { - // Dylib engine error (used anywhere except mac) - Err(Error::MemoryError(memory::Error::Grow( - wasmer::MemoryError::CouldNotGrow { .. }, - ))) => { + Err(Error::MemoryError(memory::Error::GuestAlloc(_))) => { // as expected } - Err(error) => { - let trap_code = get_trap_code(&error); - // Universal engine error (currently used on mac) - assert_eq!( - trap_code, - Either::Left(wasmer_vm::TrapCode::HeapAccessOutOfBounds) - ); - } _ => panic!("Expected to run out of memory, got {:?}", result), } } @@ -1657,10 +1637,10 @@ mod tests { .unwrap(); let _ = state.write_log_mut().write(&len_key, code_len).unwrap(); - // Allocating `2^24` (16 MiB) for a value in storage that the tx + // Allocating `2^25` (32 MiB) for a value in storage that the tx // attempts to read should be above the memory limit and should // fail - let len = 2_usize.pow(24); + let len = 2_usize.pow(25); let value: Vec = vec![6_u8; len]; let key_raw = "key"; let key = Key::parse(key_raw).unwrap(); @@ -1715,10 +1695,10 @@ mod tests { state.write(&key, vp_read_key).unwrap(); state.write(&len_key, code_len).unwrap(); - // Allocating `2^24` (16 MiB) for a value in storage that the tx + // Allocating `2^25` (32 MiB) for a value in storage that the tx // attempts to read should be above the memory limit and should // fail - let len = 2_usize.pow(24); + let len = 2_usize.pow(25); let value: Vec = vec![6_u8; len]; let key_raw = "key"; let key = Key::parse(key_raw).unwrap(); From 8358b45f073ae597e1ca8dd93c05fbb042bd9187 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Fri, 11 Jul 2025 14:04:51 +0100 Subject: [PATCH 3/7] avoid growing memory if the initial data fits current mem size --- crates/vm/src/wasm/run.rs | 67 ++++++++++++++++++++++++++------------- 1 file changed, 45 insertions(+), 22 deletions(-) diff --git a/crates/vm/src/wasm/run.rs b/crates/vm/src/wasm/run.rs index 90eb0d03ebb..4412c3f3ac4 100644 --- a/crates/vm/src/wasm/run.rs +++ b/crates/vm/src/wasm/run.rs @@ -708,10 +708,13 @@ pub fn prepare_wasm_code>(code: T) -> Result> { elements::serialize(module).map_err(Error::SerializationError) } -/// Inject and export allocation function that can be used to grow memory from -/// the host side +/// Inject and export allocation function that can be used to grow the initial +/// memory from the host side. +/// +/// IMPORTANT: This must not be used for non-initial memory allocations as it +/// assumes writing at location 0. fn inject_alloc(module: elements::Module) -> Result { - use elements::{BlockType, Instruction, Instructions, ValueType}; + use elements::{BlockType, Instruction, Instructions, Local, ValueType}; use parity_wasm::builder; // Index of the function to be injected (note that the result of @@ -722,17 +725,21 @@ fn inject_alloc(module: elements::Module) -> Result { .map_err(|_| Error::AllocInjection)?; let mut builder = builder::from_module(module); - const WASM_PAGE_SIZE_LOG2: i32 = 16; - const WASM_PAGE_SIZE: i32 = 1 << WASM_PAGE_SIZE_LOG2; - // Alloc fn in WAT: // - // (func (;0;) (type 0) (param i32) (result i32) + // (func (;0;) (type 0) (param i32) + // (local i32) // block ;; label = @1 // local.get 0 - // i32.const -65536 - // i32.le_u - // if ;; label = @2 + // memory.size + // i32.const 16 + // i32.shl + // i32.gt_u + // if (result i32) ;; label = @2 + // local.get 0 + // i32.const -65536 + // i32.gt_u + // br_if 1 (;@1;) // local.get 0 // i32.const 65535 // i32.add @@ -741,39 +748,54 @@ fn inject_alloc(module: elements::Module) -> Result { // memory.grow // local.tee 0 // i32.const -1 - // i32.ne + // i32.eq // br_if 1 (;@1;) + // local.get 0 + // i32.const 16 + // i32.shl + // else + // i32.const 0 // end - // unreachable + // return // end - // local.get 0 - // i32.const 16 - // i32.shl) + // unreachable) + const MEMORY_IX: u8 = 0; + const WASM_PAGE_SIZE_LOG2: i32 = 16; + const WASM_PAGE_SIZE: i32 = 1 << WASM_PAGE_SIZE_LOG2; let instructions = { use Instruction::*; vec![ Block(BlockType::NoResult), GetLocal(0), + CurrentMemory(MEMORY_IX), + I32Const(WASM_PAGE_SIZE_LOG2), + I32Shl, + I32GtU, + If(BlockType::Value(ValueType::I32)), + GetLocal(0), I32Const(-WASM_PAGE_SIZE), - I32LeU, - If(BlockType::NoResult), + I32GtU, + BrIf(1), GetLocal(0), I32Const(WASM_PAGE_SIZE - 1), I32Add, I32Const(WASM_PAGE_SIZE_LOG2), I32ShrU, - GrowMemory(0), + GrowMemory(MEMORY_IX), TeeLocal(0), I32Const(-1), - I32Ne, + I32Eq, BrIf(1), - End, - Unreachable, - End, GetLocal(0), I32Const(WASM_PAGE_SIZE_LOG2), I32Shl, + Else, + I32Const(0), + End, + Return, + End, + Unreachable, End, ] }; @@ -786,6 +808,7 @@ fn inject_alloc(module: elements::Module) -> Result { .build() .body() .with_instructions(Instructions::new(instructions)) + .with_locals([Local::new(1, ValueType::I32)]) .build() .build(), ); From 8893ccf9bb30ed103a438c8334ecefc266eec75e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Fri, 11 Jul 2025 14:10:26 +0100 Subject: [PATCH 4/7] changelog: add #4729 --- .changelog/unreleased/bug-fixes/4729-fix-wasm-alloc.md | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .changelog/unreleased/bug-fixes/4729-fix-wasm-alloc.md diff --git a/.changelog/unreleased/bug-fixes/4729-fix-wasm-alloc.md b/.changelog/unreleased/bug-fixes/4729-fix-wasm-alloc.md new file mode 100644 index 00000000000..0c5baa6288b --- /dev/null +++ b/.changelog/unreleased/bug-fixes/4729-fix-wasm-alloc.md @@ -0,0 +1,2 @@ +- Allow to execute WASM with a tx payload that doesn't fit into the initial WASM + memory. ([\#4729](https://github.com/anoma/namada/pull/4729)) \ No newline at end of file From 7f4f0bb35a1cd0116f23c0a0ed020bb6e3511c5f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Fri, 8 Aug 2025 17:44:29 +0100 Subject: [PATCH 5/7] remove the condition to not allocate when init data fits init memory --- crates/vm/src/wasm/run.rs | 51 ++++++++++++--------------------------- 1 file changed, 16 insertions(+), 35 deletions(-) diff --git a/crates/vm/src/wasm/run.rs b/crates/vm/src/wasm/run.rs index 4412c3f3ac4..396913b440c 100644 --- a/crates/vm/src/wasm/run.rs +++ b/crates/vm/src/wasm/run.rs @@ -727,19 +727,12 @@ fn inject_alloc(module: elements::Module) -> Result { // Alloc fn in WAT: // - // (func (;0;) (type 0) (param i32) - // (local i32) + // (func (;0;) (type 0) (param i32) (result i32) // block ;; label = @1 // local.get 0 - // memory.size - // i32.const 16 - // i32.shl - // i32.gt_u - // if (result i32) ;; label = @2 - // local.get 0 - // i32.const -65536 - // i32.gt_u - // br_if 1 (;@1;) + // i32.const -65536 + // i32.le_u + // if ;; label = @2 // local.get 0 // i32.const 65535 // i32.add @@ -748,17 +741,14 @@ fn inject_alloc(module: elements::Module) -> Result { // memory.grow // local.tee 0 // i32.const -1 - // i32.eq + // i32.ne // br_if 1 (;@1;) - // local.get 0 - // i32.const 16 - // i32.shl - // else - // i32.const 0 // end - // return + // unreachable // end - // unreachable) + // local.get 0 + // i32.const 16 + // i32.shl) const MEMORY_IX: u8 = 0; const WASM_PAGE_SIZE_LOG2: i32 = 16; @@ -767,16 +757,10 @@ fn inject_alloc(module: elements::Module) -> Result { use Instruction::*; vec![ Block(BlockType::NoResult), - GetLocal(0), - CurrentMemory(MEMORY_IX), - I32Const(WASM_PAGE_SIZE_LOG2), - I32Shl, - I32GtU, - If(BlockType::Value(ValueType::I32)), - GetLocal(0), + GetLocal(1), I32Const(-WASM_PAGE_SIZE), - I32GtU, - BrIf(1), + I32LeU, + If(BlockType::NoResult), GetLocal(0), I32Const(WASM_PAGE_SIZE - 1), I32Add, @@ -785,17 +769,14 @@ fn inject_alloc(module: elements::Module) -> Result { GrowMemory(MEMORY_IX), TeeLocal(0), I32Const(-1), - I32Eq, + I32Ne, BrIf(1), + End, + Unreachable, + End, GetLocal(0), I32Const(WASM_PAGE_SIZE_LOG2), I32Shl, - Else, - I32Const(0), - End, - Return, - End, - Unreachable, End, ] }; From 7d003b54977898332c233c7abfc82da31d177f87 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Fri, 8 Aug 2025 17:56:32 +0100 Subject: [PATCH 6/7] host_env: don't debug log bytes --- crates/vm/src/host_env.rs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/crates/vm/src/host_env.rs b/crates/vm/src/host_env.rs index 6a09231527f..2280e209ef9 100644 --- a/crates/vm/src/host_env.rs +++ b/crates/vm/src/host_env.rs @@ -915,7 +915,7 @@ where .map_err(|e| TxRuntimeError::MemoryError(Box::new(e)))?; consume_tx_gas::(env, gas)?; - tracing::debug!("tx_update {}, {:?}", key, value); + tracing::debug!("tx_update {}", key); let key = Key::parse(key)?; if key.is_validity_predicate().is_some() { @@ -955,7 +955,7 @@ where .map_err(|e| TxRuntimeError::MemoryError(Box::new(e)))?; consume_tx_gas::(env, gas)?; - tracing::debug!("tx_write_temp {}, {:?}", key, value); + tracing::debug!("tx_write_temp {}", key); let key = Key::parse(key)?; @@ -1144,10 +1144,9 @@ where let state = env.state(); let value = vp_host_fns::read_pre(gas_meter, &state, &key)?; tracing::debug!( - "vp_read_pre addr {}, key {}, value {:?}", + "vp_read_pre addr {}, key {}", unsafe { env.ctx.address.get() }, key, - value, ); Ok(match value { Some(value) => { From b3334fd4a93a7a91ade773d38dcee44a5bed5b28 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Mon, 11 Aug 2025 15:49:59 +0100 Subject: [PATCH 7/7] changelog: #4786 --- .changelog/unreleased/bug-fixes/4786-wasm-init-always-alloc.md | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .changelog/unreleased/bug-fixes/4786-wasm-init-always-alloc.md diff --git a/.changelog/unreleased/bug-fixes/4786-wasm-init-always-alloc.md b/.changelog/unreleased/bug-fixes/4786-wasm-init-always-alloc.md new file mode 100644 index 00000000000..bac45ea7be9 --- /dev/null +++ b/.changelog/unreleased/bug-fixes/4786-wasm-init-always-alloc.md @@ -0,0 +1,2 @@ +- Always allocate for the initial data to allow to execute larger WASMs + ([\#4786](https://github.com/anoma/namada/pull/4786)) \ No newline at end of file