From 9fe09b177126ed2eb2b7567740cbe535b55e82d6 Mon Sep 17 00:00:00 2001 From: kp2pml30 Date: Tue, 14 Apr 2026 21:39:06 +0900 Subject: [PATCH 1/9] feat: add basic storage structs --- executor/crates/sdk-rs/Cargo.lock | 80 +++++ executor/crates/sdk-rs/Cargo.toml | 8 +- .../sdk-rs/examples/storage_dyn_array.rs | 62 ++++ executor/crates/sdk-rs/src/lib.rs | 3 + executor/crates/sdk-rs/src/storage/array.rs | 171 +++++++++ executor/crates/sdk-rs/src/storage/core.rs | 324 ++++++++++++++++++ executor/crates/sdk-rs/src/storage/mod.rs | 7 + .../storage/storage_dyn_array_rust/prepare.py | 34 ++ .../storage_dyn_array.wasm | Bin 0 -> 220701 bytes .../storage_dyn_array_rust.0.hash | 1 + .../storage_dyn_array_rust.0.stdout | 8 + .../storage_dyn_array_rust.jsonnet | 11 + 12 files changed, 708 insertions(+), 1 deletion(-) create mode 100644 executor/crates/sdk-rs/examples/storage_dyn_array.rs create mode 100644 executor/crates/sdk-rs/src/storage/array.rs create mode 100644 executor/crates/sdk-rs/src/storage/core.rs create mode 100644 executor/crates/sdk-rs/src/storage/mod.rs create mode 100644 tests/cases/stable/storage/storage_dyn_array_rust/prepare.py create mode 100644 tests/cases/stable/storage/storage_dyn_array_rust/storage_dyn_array.wasm create mode 100644 tests/cases/stable/storage/storage_dyn_array_rust/storage_dyn_array_rust.0.hash create mode 100644 tests/cases/stable/storage/storage_dyn_array_rust/storage_dyn_array_rust.0.stdout create mode 100644 tests/cases/stable/storage/storage_dyn_array_rust/storage_dyn_array_rust.jsonnet diff --git a/executor/crates/sdk-rs/Cargo.lock b/executor/crates/sdk-rs/Cargo.lock index 2c966ed4..c667cbc5 100644 --- a/executor/crates/sdk-rs/Cargo.lock +++ b/executor/crates/sdk-rs/Cargo.lock @@ -41,6 +41,15 @@ dependencies = [ "wyz", ] +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + [[package]] name = "bumpalo" version = "3.19.1" @@ -124,12 +133,41 @@ version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + [[package]] name = "crunchy" version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" +[[package]] +name = "crypto-common" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + [[package]] name = "equivalent" version = "1.0.2" @@ -160,6 +198,16 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + [[package]] name = "genlayer_calldata" version = "0.0.1" @@ -186,6 +234,7 @@ dependencies = [ "primitive-types", "serde", "serde_bytes", + "sha3", ] [[package]] @@ -284,6 +333,15 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "keccak" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb26cec98cce3a3d96cbb7bced3c4b16e3d13f27ec56dbd62cbc8f39cfb9d653" +dependencies = [ + "cpufeatures", +] + [[package]] name = "libc" version = "0.2.180" @@ -501,6 +559,16 @@ dependencies = [ "syn", ] +[[package]] +name = "sha3" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60" +dependencies = [ + "digest", + "keccak", +] + [[package]] name = "shlex" version = "1.3.0" @@ -560,6 +628,12 @@ dependencies = [ "winnow", ] +[[package]] +name = "typenum" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" + [[package]] name = "uint" version = "0.10.0" @@ -584,6 +658,12 @@ version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + [[package]] name = "wasi" version = "0.11.1+wasi-snapshot-preview1" diff --git a/executor/crates/sdk-rs/Cargo.toml b/executor/crates/sdk-rs/Cargo.toml index 9ebdf486..65b03900 100644 --- a/executor/crates/sdk-rs/Cargo.toml +++ b/executor/crates/sdk-rs/Cargo.toml @@ -19,9 +19,14 @@ exclude = [ name = "fetch_webpage" path = "examples/fetch_webpage.rs" +[[example]] +name = "storage_dyn_array" +path = "examples/storage_dyn_array.rs" + [features] -default = ["wasi"] +default = ["wasi", "storage"] wasi = [] +storage = ["dep:sha3", "wasi"] arbitrary = ["dep:arbitrary", "genlayer_calldata/arbitrary"] [dependencies] @@ -34,6 +39,7 @@ serde_bytes = { version = "0.11.19"} bytes = {version = "1.11.0", features = ["serde"] } chrono = { version = "0.4", features = ["serde"] } genlayer_calldata = { path = "../calldata" } +sha3 = { version = "0.10", optional = true } [dev-dependencies] primitive-types = { version = "0.13", features = ["impl-serde"] } diff --git a/executor/crates/sdk-rs/examples/storage_dyn_array.rs b/executor/crates/sdk-rs/examples/storage_dyn_array.rs new file mode 100644 index 00000000..b792ff7d --- /dev/null +++ b/executor/crates/sdk-rs/examples/storage_dyn_array.rs @@ -0,0 +1,62 @@ +//! Example contract demonstrating DynArray storage. +//! +//! On deploy (is_init=true), populates a DynArray and prints its contents. + +use genlayer_sdk::abi::entry::MessageData; +use genlayer_sdk::abi::entry::contract_def::Contract; +use genlayer_sdk::calldata::Value; +use genlayer_sdk::storage::{DynArray, Slot, StorageType}; + +const ROOT_SLOT: Slot = Slot([0u8; 32]); + +#[derive(Default)] +pub struct StorageDynArrayExample; + +impl Contract for StorageDynArrayExample { + fn handle_main(&mut self, _message: MessageData, _data: bytes::Bytes) -> Result { + let arr = >::handle_at(ROOT_SLOT, 0); + + // populate + arr.append_slot().set(10); + arr.append_slot().set(20); + arr.append_slot().set(30); + + // iterate and print + let len = arr.len(); + println!("len={len}"); + for i in 0..len { + let val = arr.index(i).get(); + println!("[{i}]={val}"); + } + + // pop last, print again + arr.pop(); + let len = arr.len(); + println!("after pop len={len}"); + for i in 0..len { + let val = arr.index(i).get(); + println!("[{i}]={val}"); + } + + Ok(Value::Null) + } + + fn handle_sandbox( + &mut self, + _message: MessageData, + _data: bytes::Bytes, + ) -> Result, String> { + unimplemented!() + } + + fn handle_consensus_stage( + &mut self, + _message: MessageData, + _data: bytes::Bytes, + _stage_data: genlayer_sdk::abi::entry::contract_def::ConsensusStageData, + ) -> Result { + unimplemented!() + } +} + +genlayer_sdk::contract_main!(StorageDynArrayExample); diff --git a/executor/crates/sdk-rs/src/lib.rs b/executor/crates/sdk-rs/src/lib.rs index 9de0ba13..ac89b1cc 100644 --- a/executor/crates/sdk-rs/src/lib.rs +++ b/executor/crates/sdk-rs/src/lib.rs @@ -1,2 +1,5 @@ pub mod abi; pub use genlayer_calldata as calldata; + +#[cfg(feature = "storage")] +pub mod storage; diff --git a/executor/crates/sdk-rs/src/storage/array.rs b/executor/crates/sdk-rs/src/storage/array.rs new file mode 100644 index 00000000..19712ae7 --- /dev/null +++ b/executor/crates/sdk-rs/src/storage/array.rs @@ -0,0 +1,171 @@ +//! Fixed-size arrays, dynamically-sized arrays, and variable-length arrays. + +use super::core::{Slot, StorageType}; + +// ===== Fixed-size array ===== +// Layout: N elements sequentially at offset + i * element_size. + +pub struct StorageArray { + pub slot: Slot, + pub offset: u32, + _marker: core::marker::PhantomData, +} + +impl Clone for StorageArray { + fn clone(&self) -> Self { + *self + } +} +impl Copy for StorageArray {} + +impl StorageArray { + pub const fn len(&self) -> usize { + N + } + + pub fn index(&self, idx: usize) -> T::Handle { + assert!(idx < N, "index {idx} out of bounds for array of length {N}"); + T::handle_at(self.slot, self.offset + idx as u32 * T::SIZE) + } +} + +impl StorageType for [T; N] { + const SIZE: u32 = T::SIZE * N as u32; + type Handle = StorageArray; + fn handle_at(slot: Slot, offset: u32) -> Self::Handle { + StorageArray { + slot, + offset, + _marker: core::marker::PhantomData, + } + } +} + +// ===== Dynamically-sized array ===== +// Layout: [u32 length] at (slot, offset), elements at slot.indirect(offset). + +/// Marker type for a dynamically-sized storage array. +pub struct DynArray(core::marker::PhantomData); + +pub struct StorageDynArray { + pub slot: Slot, + pub offset: u32, + _marker: core::marker::PhantomData, +} + +impl Clone for StorageDynArray { + fn clone(&self) -> Self { + *self + } +} +impl Copy for StorageDynArray {} + +impl StorageDynArray { + pub fn len(&self) -> u32 { + u32::handle_at(self.slot, self.offset).get() + } + + pub fn is_empty(&self) -> bool { + self.len() == 0 + } + + pub fn set_len(&self, len: u32) { + u32::handle_at(self.slot, self.offset).set(len); + } + + pub fn index(&self, idx: u32) -> T::Handle { + assert!(idx < self.len(), "index out of bounds"); + T::handle_at(self.slot.indirect(self.offset), idx * T::SIZE) + } + + /// Grow by one and return a handle to the new (uninitialized) element. + pub fn append_slot(&self) -> T::Handle { + let len = self.len(); + self.set_len(len + 1); + T::handle_at(self.slot.indirect(self.offset), len * T::SIZE) + } + + pub fn pop(&self) { + let len = self.len(); + assert!(len > 0, "can't pop from empty array"); + self.set_len(len - 1); + } + + pub fn clear(&self) { + self.set_len(0); + } +} + +impl StorageType for DynArray { + const SIZE: u32 = 4; + type Handle = StorageDynArray; + fn handle_at(slot: Slot, offset: u32) -> Self::Handle { + StorageDynArray { + slot, + offset, + _marker: core::marker::PhantomData, + } + } +} + +// ===== VLA (Variable Length Array) ===== +// Layout: [u32 length] then elements inline in the same slot (no indirection). + +/// Marker type for a variable-length array with inline storage. +pub struct VLA(core::marker::PhantomData); + +pub struct StorageVLA { + pub slot: Slot, + pub offset: u32, + _marker: core::marker::PhantomData, +} + +impl Clone for StorageVLA { + fn clone(&self) -> Self { + *self + } +} +impl Copy for StorageVLA {} + +impl StorageVLA { + pub fn len(&self) -> u32 { + u32::handle_at(self.slot, self.offset).get() + } + + pub fn is_empty(&self) -> bool { + self.len() == 0 + } + + pub fn set_len(&self, len: u32) { + u32::handle_at(self.slot, self.offset).set(len); + } + + pub fn index(&self, idx: u32) -> T::Handle { + assert!(idx < self.len(), "index out of bounds"); + T::handle_at(self.slot, self.offset + 4 + idx * T::SIZE) + } + + /// Grow by one and return a handle to the new (uninitialized) element. + pub fn append_slot(&self) -> T::Handle { + let len = self.len(); + self.set_len(len + 1); + T::handle_at(self.slot, self.offset + 4 + len * T::SIZE) + } + + pub fn truncate(&self, to: u32) { + assert!(to <= self.len(), "truncate target exceeds current length"); + self.set_len(to); + } +} + +impl StorageType for VLA { + const SIZE: u32 = u32::MAX; + type Handle = StorageVLA; + fn handle_at(slot: Slot, offset: u32) -> Self::Handle { + StorageVLA { + slot, + offset, + _marker: core::marker::PhantomData, + } + } +} diff --git a/executor/crates/sdk-rs/src/storage/core.rs b/executor/crates/sdk-rs/src/storage/core.rs new file mode 100644 index 00000000..f6eb052b --- /dev/null +++ b/executor/crates/sdk-rs/src/storage/core.rs @@ -0,0 +1,324 @@ +//! Core storage primitives: Slot, StorageType trait, and built-in scalar types. + +use crate::abi::wasi; + +// ===== Slot ===== + +/// A 32-byte addressed region of contract storage. +#[derive(Clone, Copy, PartialEq, Eq)] +pub struct Slot(pub [u8; 32]); + +impl Slot { + pub fn read(&self, offset: u32, buf: &mut [u8]) { + wasi::storage_read(&self.0, offset, buf).expect("storage read failed"); + } + + pub fn write(&self, offset: u32, buf: &[u8]) { + wasi::storage_write(&self.0, offset as i32, buf).expect("storage write failed"); + } + + /// Derive a child slot via `sha3_256(slot_id || offset_le_bytes)`. + pub fn indirect(&self, offset: u32) -> Slot { + use sha3::{Digest, Sha3_256}; + let mut hasher = Sha3_256::new(); + hasher.update(self.0); + hasher.update(offset.to_le_bytes()); + Slot(hasher.finalize().into()) + } +} + +// ===== StorageType trait ===== + +/// Maps a logical value type to its storage handle. +/// +/// `SIZE` is the number of bytes occupied in the parent slot. +/// `Handle` is the pointer-like struct used to read/write the value. +pub trait StorageType { + const SIZE: u32; + type Handle; + fn handle_at(slot: Slot, offset: u32) -> Self::Handle; +} + +// ===== Integer types ===== + +macro_rules! impl_int_storage { + ($($rust_ty:ty => $handle_name:ident),* $(,)?) => { $( + #[derive(Clone, Copy)] + pub struct $handle_name { + pub slot: Slot, + pub offset: u32, + } + + impl $handle_name { + pub fn get(&self) -> $rust_ty { + let mut buf = [0u8; core::mem::size_of::<$rust_ty>()]; + self.slot.read(self.offset, &mut buf); + <$rust_ty>::from_le_bytes(buf) + } + + pub fn set(&self, val: $rust_ty) { + self.slot.write(self.offset, &val.to_le_bytes()); + } + } + + impl StorageType for $rust_ty { + const SIZE: u32 = core::mem::size_of::<$rust_ty>() as u32; + type Handle = $handle_name; + fn handle_at(slot: Slot, offset: u32) -> Self::Handle { + $handle_name { slot, offset } + } + } + )* }; +} + +impl_int_storage! { + u8 => StorageU8, + u16 => StorageU16, + u32 => StorageU32, + u64 => StorageU64, + u128 => StorageU128, + i8 => StorageI8, + i16 => StorageI16, + i32 => StorageI32, + i64 => StorageI64, + i128 => StorageI128, +} + +// ===== Float types ===== + +macro_rules! impl_float_storage { + ($($rust_ty:ty => $handle_name:ident),* $(,)?) => { $( + #[derive(Clone, Copy)] + pub struct $handle_name { + pub slot: Slot, + pub offset: u32, + } + + impl $handle_name { + pub fn get(&self) -> $rust_ty { + let mut buf = [0u8; core::mem::size_of::<$rust_ty>()]; + self.slot.read(self.offset, &mut buf); + <$rust_ty>::from_le_bytes(buf) + } + + pub fn set(&self, val: $rust_ty) { + self.slot.write(self.offset, &val.to_le_bytes()); + } + } + + impl StorageType for $rust_ty { + const SIZE: u32 = core::mem::size_of::<$rust_ty>() as u32; + type Handle = $handle_name; + fn handle_at(slot: Slot, offset: u32) -> Self::Handle { + $handle_name { slot, offset } + } + } + )* }; +} + +impl_float_storage! { + f32 => StorageF32, + f64 => StorageF64, +} + +// ===== Bool ===== + +#[derive(Clone, Copy)] +pub struct StorageBool { + pub slot: Slot, + pub offset: u32, +} + +impl StorageBool { + pub fn get(&self) -> bool { + let mut buf = [0u8; 1]; + self.slot.read(self.offset, &mut buf); + buf[0] != 0 + } + + pub fn set(&self, val: bool) { + self.slot.write(self.offset, &[val as u8]); + } +} + +impl StorageType for bool { + const SIZE: u32 = 1; + type Handle = StorageBool; + fn handle_at(slot: Slot, offset: u32) -> Self::Handle { + StorageBool { slot, offset } + } +} + +// ===== String ===== +// Layout: [u32 length] at (slot, offset), UTF-8 data at slot.indirect(offset). + +#[derive(Clone, Copy)] +pub struct StorageStr { + pub slot: Slot, + pub offset: u32, +} + +impl StorageStr { + pub fn len(&self) -> u32 { + u32::handle_at(self.slot, self.offset).get() + } + + pub fn is_empty(&self) -> bool { + self.len() == 0 + } + + pub fn set_len(&self, len: u32) { + u32::handle_at(self.slot, self.offset).set(len); + } + + pub fn load(&self) -> String { + let len = self.len() as usize; + if len == 0 { + return String::new(); + } + let indirect = self.slot.indirect(self.offset); + let mut buf = vec![0u8; len]; + indirect.read(0, &mut buf); + String::from_utf8(buf).expect("invalid utf-8 in storage string") + } + + pub fn store(&self, val: &str) { + let bytes = val.as_bytes(); + self.set_len(bytes.len() as u32); + if !bytes.is_empty() { + self.slot.indirect(self.offset).write(0, bytes); + } + } + + pub fn slice(&self, start: u32, end: u32) -> String { + assert!(start <= end && end <= self.len()); + let len = (end - start) as usize; + if len == 0 { + return String::new(); + } + let indirect = self.slot.indirect(self.offset); + let mut buf = vec![0u8; len]; + indirect.read(start, &mut buf); + String::from_utf8(buf).expect("invalid utf-8 in storage string slice") + } + + pub fn index(&self, idx: u32) -> u8 { + assert!(idx < self.len(), "index out of bounds"); + let mut buf = [0u8; 1]; + self.slot.indirect(self.offset).read(idx, &mut buf); + buf[0] + } +} + +impl StorageType for String { + const SIZE: u32 = 4; + type Handle = StorageStr; + fn handle_at(slot: Slot, offset: u32) -> Self::Handle { + StorageStr { slot, offset } + } +} + +// ===== Bytes ===== +// Layout: [u32 length] at (slot, offset), raw data at slot.indirect(offset). + +#[derive(Clone, Copy)] +pub struct StorageBytes { + pub slot: Slot, + pub offset: u32, +} + +impl StorageBytes { + pub fn len(&self) -> u32 { + u32::handle_at(self.slot, self.offset).get() + } + + pub fn is_empty(&self) -> bool { + self.len() == 0 + } + + pub fn set_len(&self, len: u32) { + u32::handle_at(self.slot, self.offset).set(len); + } + + pub fn load(&self) -> Vec { + let len = self.len() as usize; + if len == 0 { + return Vec::new(); + } + let indirect = self.slot.indirect(self.offset); + let mut buf = vec![0u8; len]; + indirect.read(0, &mut buf); + buf + } + + pub fn store(&self, val: &[u8]) { + self.set_len(val.len() as u32); + if !val.is_empty() { + self.slot.indirect(self.offset).write(0, val); + } + } + + pub fn slice(&self, start: u32, end: u32) -> Vec { + assert!(start <= end && end <= self.len()); + let len = (end - start) as usize; + if len == 0 { + return Vec::new(); + } + let indirect = self.slot.indirect(self.offset); + let mut buf = vec![0u8; len]; + indirect.read(start, &mut buf); + buf + } + + pub fn index(&self, idx: u32) -> u8 { + assert!(idx < self.len(), "index out of bounds"); + let mut buf = [0u8; 1]; + self.slot.indirect(self.offset).read(idx, &mut buf); + buf[0] + } +} + +impl StorageType for Vec { + const SIZE: u32 = 4; + type Handle = StorageBytes; + fn handle_at(slot: Slot, offset: u32) -> Self::Handle { + StorageBytes { slot, offset } + } +} + +// ===== Indirection ===== +// Data lives at a derived slot. Occupies 1 byte in the parent to prevent collisions. + +/// Marker type for indirected storage. +pub struct Indirection(core::marker::PhantomData); + +pub struct StorageIndirection { + pub slot: Slot, + pub offset: u32, + _marker: core::marker::PhantomData, +} + +impl Clone for StorageIndirection { + fn clone(&self) -> Self { + *self + } +} +impl Copy for StorageIndirection {} + +impl StorageIndirection { + pub fn get(&self) -> T::Handle { + T::handle_at(self.slot.indirect(self.offset), 0) + } +} + +impl StorageType for Indirection { + const SIZE: u32 = 1; + type Handle = StorageIndirection; + fn handle_at(slot: Slot, offset: u32) -> Self::Handle { + StorageIndirection { + slot, + offset, + _marker: core::marker::PhantomData, + } + } +} diff --git a/executor/crates/sdk-rs/src/storage/mod.rs b/executor/crates/sdk-rs/src/storage/mod.rs new file mode 100644 index 00000000..38bf15a7 --- /dev/null +++ b/executor/crates/sdk-rs/src/storage/mod.rs @@ -0,0 +1,7 @@ +//! Storage types for GenLayer smart contracts. + +pub mod array; +pub mod core; + +pub use self::array::*; +pub use self::core::*; diff --git a/tests/cases/stable/storage/storage_dyn_array_rust/prepare.py b/tests/cases/stable/storage/storage_dyn_array_rust/prepare.py new file mode 100644 index 00000000..cc74fc52 --- /dev/null +++ b/tests/cases/stable/storage/storage_dyn_array_rust/prepare.py @@ -0,0 +1,34 @@ +import subprocess +import json +from pathlib import Path + +root = Path(__file__).parent +repo_root = root.parents[4] + +build_info = json.loads(repo_root.joinpath('build', 'info.json').read_text()) +target_dir = Path(build_info['rust_target_dir']) + +subprocess.run( + [ + 'cargo', + 'build', + '--example', + 'storage_dyn_array', + '--target', + 'wasm32-wasip1', + '--release', + '--target-dir', + str(target_dir), + ], + cwd=repo_root / 'executor' / 'crates' / 'sdk-rs', + check=True, +) + +src = target_dir / 'wasm32-wasip1' / 'release' / 'examples' / 'storage_dyn_array.wasm' +dst = root / 'storage_dyn_array.wasm' +wat = root / 'storage_dyn_array.wat' + +# Round-trip through wabt to normalize to MVP format +subprocess.run(['wasm2wat', str(src), '-o', str(wat)], check=True) +subprocess.run(['wat2wasm', str(wat), '-o', str(dst)], check=True) +wat.unlink() diff --git a/tests/cases/stable/storage/storage_dyn_array_rust/storage_dyn_array.wasm b/tests/cases/stable/storage/storage_dyn_array_rust/storage_dyn_array.wasm new file mode 100644 index 0000000000000000000000000000000000000000..7288d5c73e49cd960c6cffc481534d930765016a GIT binary patch literal 220701 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