diff --git a/Cargo.lock b/Cargo.lock
index 65016298195..c2cf4a8c71a 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -3826,6 +3826,7 @@ dependencies = [
"thiserror",
"tokio",
"vara-runtime",
+ "wasmi 0.30.0 (registry+https://github.com/rust-lang/crates.io-index)",
"which",
"whoami",
]
diff --git a/gcli/Cargo.toml b/gcli/Cargo.toml
index ba5b8806ba1..46d21f695c0 100644
--- a/gcli/Cargo.toml
+++ b/gcli/Cargo.toml
@@ -46,6 +46,8 @@ reqwest = { workspace = true, default-features = false, features = [ "json", "ru
etc.workspace = true
sp-io = { workspace = true, features = [ "std" ] }
sp-core = { workspace = true, features = [ "std" ] }
+# TODO: use wasmi from workspace (#3214)
+wasmi = { version = "0.30.0", features = ["std"] }
[dev-dependencies]
rand.workspace = true
diff --git a/gcli/src/meta/executor.rs b/gcli/src/meta/executor.rs
new file mode 100644
index 00000000000..be76fd537c4
--- /dev/null
+++ b/gcli/src/meta/executor.rs
@@ -0,0 +1,312 @@
+// This file is part of Gear.
+//
+// Copyright (C) 2021-2023 Gear Technologies Inc.
+// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+
+//! The WASM executor in this module is just for parsing the state types
+//! of gear programs, some of the host functions are missing logics that
+//! is because they are for the on-chain environment data.
+
+use anyhow::{anyhow, Result};
+use wasmi::{AsContextMut, Engine, Extern, Linker, Memory, MemoryType, Module, Store};
+
+const PAGE_STORAGE_PREFIX: [u8; 32] = *b"gcligcligcligcligcligcligcligcli";
+
+/// HostState for the WASM executor
+#[derive(Default)]
+pub struct HostState {
+ /// Message buffer in host state.
+ pub msg: Vec,
+}
+
+/// Call `metadata` method in the WASM code.
+pub fn call_metadata(wasm: &[u8]) -> Result> {
+ execute(wasm, "metadata")
+}
+
+/// Executes the WASM code.
+fn execute(wasm: &[u8], method: &str) -> Result> {
+ assert!(gear_lazy_pages_interface::try_to_enable_lazy_pages(
+ PAGE_STORAGE_PREFIX
+ ));
+
+ let engine = Engine::default();
+ let module = Module::new(&engine, wasm).unwrap();
+
+ let mut store = Store::new(&engine, HostState::default());
+ let mut linker = >::new(&engine);
+ let memory = Memory::new(
+ store.as_context_mut(),
+ MemoryType::new(256, None).map_err(|_| anyhow!("failed to create memory type"))?,
+ )
+ .map_err(|_| anyhow!("failed to create memory"))?;
+
+ // Execution environment
+ //
+ // TODO: refactor this after #3416.
+ {
+ let mut env = env::Env {
+ linker: &mut linker,
+ store: &mut store,
+ memory,
+ };
+
+ for import in module.imports() {
+ env.define(import.module(), import.name())?;
+ }
+ }
+
+ let instance = linker
+ .instantiate(&mut store, &module)
+ .unwrap()
+ .start(&mut store)?;
+
+ let metadata = instance
+ .get_export(&store, method)
+ .and_then(Extern::into_func)
+ .ok_or_else(|| anyhow!("could not find function \"{}\"", method))?
+ .typed::<(), ()>(&mut store)?;
+
+ metadata.call(&mut store, ())?;
+ Ok(store.data().msg.clone())
+}
+
+mod env {
+ use super::HostState;
+ use anyhow::{anyhow, Result};
+ use wasmi::{
+ core::{Pages, Trap, TrapCode},
+ AsContext, AsContextMut, Caller, Extern, Func, Linker, Memory, Store,
+ };
+
+ /// Environment for the wasm execution.
+ pub struct Env<'e> {
+ pub linker: &'e mut Linker,
+ pub store: &'e mut Store,
+ pub memory: Memory,
+ }
+
+ macro_rules! func {
+ ($store:tt) => {
+ func!($store,)
+ };
+ ($store:tt, $($ty:tt),* ) => {
+ Extern::Func(Func::wrap(
+ $store,
+ move |_caller: Caller<'_, HostState>, $(_: $ty),*| { Ok(()) },
+ ))
+ };
+ (@result $store:tt, $($ty:tt),* ) => {
+ Extern::Func(Func::wrap(
+ $store,
+ move |_caller: Caller<'_, HostState>, $(_: $ty),*| { 0 },
+ ))
+ };
+ }
+
+ impl Env<'_> {
+ /// Define function in the environment.
+ pub fn define(&mut self, module: &str, name: &str) -> Result<()> {
+ if module != "env" {
+ return Err(anyhow!("module \"{}\" not found", module));
+ }
+
+ let memory = self.memory;
+ let store = &mut self.store;
+
+ let external = match name {
+ "memory" => Extern::Memory(memory),
+ "alloc" => alloc(self.store, memory),
+ "gr_oom_panic" => gr_oom_panic(store),
+ "gr_read" => gr_read(store, memory),
+ "gr_reply" => gr_reply(store, memory),
+ "gr_panic" => gr_panic(store, memory),
+ "gr_size" => gr_size(store, memory),
+ // methods may be used by programs but not required by metadata.
+ "free" => func!(@result store, i32),
+ "gr_block_height" => func!(store, u32),
+ "gr_block_timestamp" => func!(store, u32),
+ "gr_create_program_wgas" => func!(store, i32, i32, u32, i32, u32, u64, u32, i32),
+ "gr_create_program" => func!(store, i32, i32, u32, i32, u32, u64, i32),
+ "gr_debug" => func!(store, i32, u32),
+ "gr_exit" => func!(store, i32),
+ "gr_gas_available" => func!(store, i32),
+ "gr_leave" => func!(store),
+ "gr_message_id" => func!(store, i32),
+ "gr_out_of_gas" => func!(store),
+ "gr_pay_program_rent" => func!(store, i32, i32),
+ "gr_program_id" => func!(store, i32),
+ "gr_random" => func!(store, i32, i32),
+ "gr_reply_code" => func!(store, i32),
+ "gr_reply_commit" => func!(store, i32, i32),
+ "gr_reply_deposit" => func!(store, i32, u64, i32),
+ "gr_reply_input" => func!(store, u32, u32, i32, i32),
+ "gr_reply_push" => func!(store, i32, u32, i32),
+ "gr_reply_push_input" => func!(store, u32, u32, i32),
+ "gr_reply_push_input_wgas" => func!(store, u32, u32, u64, i32, i32),
+ "gr_reply_to" => func!(store, i32),
+ "gr_reply_wgas" => func!(store, i32, u32, u64, i32, i32),
+ "gr_reservation_reply" => func!(store, i32, i32, u32, i32),
+ "gr_reservation_send_commit" => func!(store, u32, i32, u32, i32),
+ "gr_reservation_send" => func!(store, i32, i32, u32, u32, i32),
+ "gr_reserve_gas" => func!(store, u64, u32, i32),
+ "gr_send" => func!(store, i32, i32, u32, u32, i32),
+ "gr_send_commit" => func!(store, u32, i32, u32, i32),
+ "gr_send_commit_wgas" => func!(store, u32, i32, u64, u32, i32),
+ "gr_send_init" => func!(store, i32),
+ "gr_send_input" => func!(store, i32, u32, u32, u32, i32),
+ "gr_send_input_wgas" => func!(store, i32, u32, u32, u64, u32, i32),
+ "gr_send_push" => func!(store, u32, i32, u32, i32),
+ "gr_send_push_input" => func!(store, u32, u32, u32, i32),
+ "gr_send_wgas" => func!(store, i32, i32, u32, u64, u32, i32),
+ "gr_signal_code" => func!(store, i32),
+ "gr_signal_from" => func!(store, i32),
+ "gr_source" => func!(store, i32),
+ "gr_system_reserve_gas" => func!(store, u64, i32),
+ "gr_unreserve_gas" => func!(store, i32, i32),
+ "gr_value" => func!(store, i32),
+ "gr_wait" => func!(store, u32),
+ "gr_wait_for" => func!(store, u32),
+ "gr_wait_up_to" => func!(store, u32),
+ "gr_wake" => func!(store, i32, u32, i32),
+ _ => return Err(anyhow!("export \"{}\" not found in env", name,)),
+ };
+
+ self.linker.define(module, name, external)?;
+
+ Ok(())
+ }
+ }
+
+ fn alloc(store: &mut Store, memory: Memory) -> Extern {
+ Extern::Func(Func::wrap(
+ store,
+ move |mut caller: Caller<'_, HostState>, pages: u32| {
+ memory
+ .clone()
+ .grow(
+ caller.as_context_mut(),
+ Pages::new(pages).unwrap_or_default(),
+ )
+ .map_or_else(
+ |err| {
+ log::error!("{err:?}");
+ u32::MAX as i32
+ },
+ |pages| pages.to_bytes().unwrap_or_default() as i32,
+ )
+ },
+ ))
+ }
+
+ fn gr_read(ctx: &mut Store, memory: Memory) -> Extern {
+ Extern::Func(Func::wrap(
+ ctx,
+ move |mut caller: Caller<'_, HostState>, at: u32, len: i32, buff: i32, err: i32| {
+ let (at, len, buff, err) = (at as _, len as usize, buff as _, err as _);
+
+ let msg = &caller.data().msg;
+ let payload = if at + len <= msg.len() {
+ msg[at..(at + len)].to_vec()
+ } else {
+ return Err(Trap::new(TrapCode::MemoryOutOfBounds.trap_message()));
+ };
+
+ let len: u32 = memory
+ .clone()
+ .write(caller.as_context_mut(), buff, &payload)
+ .map_err(|e| log::error!("{:?}", e))
+ .is_err()
+ .into();
+
+ memory
+ .clone()
+ .write(caller.as_context_mut(), err, &len.to_le_bytes())
+ .map_err(|e| {
+ log::error!("{:?}", e);
+ Trap::new(TrapCode::MemoryOutOfBounds.trap_message())
+ })?;
+
+ Ok(())
+ },
+ ))
+ }
+
+ fn gr_reply(ctx: &mut Store, memory: Memory) -> Extern {
+ Extern::Func(Func::wrap(
+ ctx,
+ move |mut caller: Caller<'_, HostState>, ptr: u32, len: i32, _value: i32, _err: i32| {
+ let mut result = vec![0; len as usize];
+
+ memory
+ .read(caller.as_context(), ptr as usize, &mut result)
+ .map_err(|e| {
+ log::error!("{:?}", e);
+ Trap::new(TrapCode::MemoryOutOfBounds.trap_message())
+ })?;
+ caller.data_mut().msg = result;
+
+ Ok(())
+ },
+ ))
+ }
+
+ fn gr_size(ctx: &mut Store, memory: Memory) -> Extern {
+ Extern::Func(Func::wrap(
+ ctx,
+ move |mut caller: Caller<'_, HostState>, size_ptr: u32| {
+ let size = caller.data().msg.len() as u32;
+
+ memory
+ .clone()
+ .write(
+ caller.as_context_mut(),
+ size_ptr as usize,
+ &size.to_le_bytes(),
+ )
+ .map_err(|e| {
+ log::error!("{:?}", e);
+ Trap::new(TrapCode::MemoryOutOfBounds.trap_message())
+ })?;
+
+ Ok(())
+ },
+ ))
+ }
+
+ fn gr_panic(ctx: &mut Store, memory: Memory) -> Extern {
+ Extern::Func(Func::wrap(
+ ctx,
+ move |caller: Caller<'_, HostState>, ptr: u32, len: i32| {
+ let mut buff = Vec::with_capacity(len as usize);
+ memory.read(caller, ptr as usize, &mut buff).map_err(|e| {
+ log::error!("{e:?}");
+ Trap::new(TrapCode::MemoryOutOfBounds.trap_message())
+ })?;
+
+ log::error!("Panic: {}", String::from_utf8_lossy(&buff));
+ Ok(())
+ },
+ ))
+ }
+
+ fn gr_oom_panic(ctx: impl AsContextMut) -> Extern {
+ Extern::Func(Func::wrap(ctx, || {
+ log::error!("OOM panic occurred");
+ Ok(())
+ }))
+ }
+}
diff --git a/gcli/src/meta/mod.rs b/gcli/src/meta/mod.rs
index 4028cccb4a1..1a34dd2af0d 100644
--- a/gcli/src/meta/mod.rs
+++ b/gcli/src/meta/mod.rs
@@ -17,13 +17,13 @@
// along with this program. If not, see .
//! Program metadata parser
+mod executor;
mod registry;
#[cfg(test)]
mod tests;
use crate::result::{Error, Result};
-use core_processor::configs::BlockInfo;
-use gear_core::code::{Code, CodeAndId, InstrumentedCode, InstrumentedCodeAndId, TryNewCodeConfig};
+use gear_core::code::{Code, CodeAndId, InstrumentedCodeAndId, TryNewCodeConfig};
use gmeta::{MetadataRepr, MetawasmData, TypesRepr};
use registry::LocalRegistry as _;
use scale_info::{scale::Decode, PortableRegistry};
@@ -73,8 +73,6 @@ pub enum Meta {
}
impl Meta {
- const PAGE_STORAGE_PREFIX: [u8; 32] = *b"gcligcligcligcligcligcligcligcli";
-
fn format_metadata(meta: &MetadataRepr, fmt: &mut fmt::Formatter) -> fmt::Result {
let registry =
PortableRegistry::decode(&mut meta.registry.as_ref()).map_err(|_| fmt::Error)?;
@@ -107,26 +105,6 @@ impl Meta {
display.finish()
}
- /// Execute meta method.
- fn execute(wasm: InstrumentedCode, method: &str) -> Result> {
- assert!(gear_lazy_pages_interface::try_to_enable_lazy_pages(
- Self::PAGE_STORAGE_PREFIX
- ));
-
- sp_io::TestExternalities::default().execute_with(|| {
- core_processor::informational::execute_for_reply::(
- method.into(),
- wasm,
- None,
- None,
- Default::default(),
- u64::MAX,
- BlockInfo::default(),
- )
- .map_err(Error::WasmExecution)
- })
- }
-
/// Decode metawasm from wasm binary.
pub fn decode_wasm(wasm: &[u8]) -> Result {
let code = Code::try_new_mock_const_or_no_rules(
@@ -135,10 +113,9 @@ impl Meta {
TryNewCodeConfig::new_no_exports_check(),
)?;
let (code, _) = InstrumentedCodeAndId::from(CodeAndId::new(code)).into_parts();
+ let result = executor::call_metadata(code.code())?;
- Ok(Self::Wasm(MetawasmData::decode(
- &mut Self::execute(code, "metadata")?.as_ref(),
- )?))
+ Ok(Self::Wasm(MetawasmData::decode(&mut result.as_ref())?))
}
/// Decode metadata from hex bytes.
diff --git a/gcli/src/result.rs b/gcli/src/result.rs
index 8b6e2546509..621cd7e41ef 100644
--- a/gcli/src/result.rs
+++ b/gcli/src/result.rs
@@ -65,6 +65,8 @@ pub enum Error {
InvalidWasm,
#[error("Wasm execution error {0}")]
WasmExecution(String),
+ #[error("Wasmi execution error {0}")]
+ Wasmi(wasmi::Error),
#[error(transparent)]
Etc(#[from] etc::Error),
#[error("Metadata parsing error {0:?}")]
diff --git a/gcli/tests/cmd/program.rs b/gcli/tests/cmd/program.rs
index 46c83bbfb22..d1e1594afa1 100644
--- a/gcli/tests/cmd/program.rs
+++ b/gcli/tests/cmd/program.rs
@@ -97,9 +97,10 @@ Metadata {
#[test]
fn test_command_program_metadata_works() -> Result<()> {
+ let node = common::dev()?;
let meta = env::wasm_bin("demo_new_meta.meta.txt");
let args = Args::new("program").action("meta").meta(meta);
- let result = common::gcli(Vec::::from(args)).expect("run gcli failed");
+ let result = node.run(args)?;
let stdout = result.stdout.convert();
assert_eq!(
@@ -113,13 +114,14 @@ fn test_command_program_metadata_works() -> Result<()> {
#[test]
fn test_command_program_metadata_derive_works() -> Result<()> {
let meta = env::wasm_bin("demo_new_meta.meta.txt");
+ let node = common::dev()?;
let args = Args::new("program")
.action("meta")
.meta(meta)
.flag("--derive")
.derive("Person");
- let result = common::gcli(Vec::::from(args)).expect("run gcli failed");
+ let result = node.run(args)?;
let stdout = result.stdout.convert();
let expected = "Person { surname: String, name: String }";
@@ -153,9 +155,10 @@ Exports {
#[test]
fn test_command_program_metawasm_works() -> Result<()> {
+ let node = common::dev()?;
let meta = env::wasm_bin("demo_meta_state_v1.meta.wasm");
let args = Args::new("program").action("meta").meta(meta);
- let result = common::gcli(Vec::::from(args)).expect("run gcli failed");
+ let result = node.run(args)?;
let stdout = result.stdout.convert();
assert_eq!(
@@ -168,6 +171,7 @@ fn test_command_program_metawasm_works() -> Result<()> {
#[test]
fn test_command_program_metawasm_derive_works() -> Result<()> {
+ let node = common::dev()?;
let meta = env::wasm_bin("demo_meta_state_v1.meta.wasm");
let args = Args::new("program")
.action("meta")
@@ -175,7 +179,7 @@ fn test_command_program_metawasm_derive_works() -> Result<()> {
.flag("--derive")
.derive("Person");
- let result = common::gcli(Vec::::from(args)).expect("run gcli failed");
+ let result = node.run(args)?;
let stdout = result.stdout.convert();
let expected = "Person { surname: String, name: String }";
diff --git a/gcli/tests/cmd/send.rs b/gcli/tests/cmd/send.rs
index a1933576f03..83d8fb2020c 100644
--- a/gcli/tests/cmd/send.rs
+++ b/gcli/tests/cmd/send.rs
@@ -36,6 +36,7 @@ async fn test_command_send_works() -> Result<()> {
// Send message to messager
let dest = hex::encode(mailbox[0].0.source.0);
let _ = node.run(Args::new("send").destination(dest).gas_limit("2000000000"))?;
+
let mailbox = signer
.api()
.mailbox(Some(common::alice_account_id()), 10)
diff --git a/gcli/tests/common/mod.rs b/gcli/tests/common/mod.rs
index dbbafc92d1c..78494bb3d1b 100644
--- a/gcli/tests/common/mod.rs
+++ b/gcli/tests/common/mod.rs
@@ -20,7 +20,7 @@
pub use self::{
args::Args,
result::{Error, Result},
- traits::NodeExec,
+ traits::{Convert, NodeExec},
};
use gear_core::ids::{CodeId, ProgramId};
use gsdk::{
@@ -60,13 +60,14 @@ impl NodeExec for Node {
/// Run binary `gcli`
pub fn gcli(args: impl IntoIterator- ) -> Result