diff --git a/Cargo.lock b/Cargo.lock index 22bdae51378..53af44bb54b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4070,7 +4070,6 @@ dependencies = [ "blake2-rfc", "derive_more", "gear-core", - "gear-core-backend-codegen", "gear-core-errors", "gear-lazy-pages-common", "gear-sandbox", @@ -4081,15 +4080,6 @@ dependencies = [ "parity-scale-codec", ] -[[package]] -name = "gear-core-backend-codegen" -version = "1.0.2" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.31", -] - [[package]] name = "gear-core-errors" version = "1.0.2" diff --git a/Cargo.toml b/Cargo.toml index 7a6f1da38bc..77dda8cb2c1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,7 +16,6 @@ members = [ "common/codegen", "core", "core-backend", - "core-backend/codegen", "core-processor", "core-errors", "examples/async", @@ -196,7 +195,6 @@ gsys = { path = "gsys" } gtest = { path = "gtest" } gmeta = { path = "gmeta" } gear-authorship = { path = "node/authorship" } -gear-core-backend-codegen = { path = "core-backend/codegen" } gear-core-backend = { path = "core-backend", default-features = false } gear-call-gen = { path = "utils/call-gen" } gear-common = { path = "common", default-features = false } diff --git a/core-backend/Cargo.toml b/core-backend/Cargo.toml index 0a1de42cad0..7343023cbb6 100644 --- a/core-backend/Cargo.toml +++ b/core-backend/Cargo.toml @@ -11,7 +11,6 @@ repository.workspace = true [dependencies] gear-core.workspace = true gear-core-errors = { workspace = true, features = ["codec"] } -gear-core-backend-codegen.workspace = true gear-lazy-pages-common.workspace = true gsys = { workspace = true } diff --git a/core-backend/codegen/Cargo.toml b/core-backend/codegen/Cargo.toml deleted file mode 100644 index 8f7bb632556..00000000000 --- a/core-backend/codegen/Cargo.toml +++ /dev/null @@ -1,19 +0,0 @@ -[package] -name = "gear-core-backend-codegen" -description = "Code generation library for gear-core-backend" -keywords = ["gear", "wasm", "codegen"] -categories = ["wasm"] -version.workspace = true -authors.workspace = true -edition.workspace = true -license.workspace = true -homepage.workspace = true -repository.workspace = true - -[lib] -proc-macro = true - -[dependencies] -syn = { workspace = true, features = ["full", "fold"] } -quote.workspace = true -proc-macro2.workspace = true diff --git a/core-backend/codegen/src/host.rs b/core-backend/codegen/src/host.rs deleted file mode 100644 index dd241427d31..00000000000 --- a/core-backend/codegen/src/host.rs +++ /dev/null @@ -1,265 +0,0 @@ -// 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 . - -use proc_macro::TokenStream; -use proc_macro2::{Ident, Span}; -use quote::{quote, ToTokens}; -use syn::{ - fold::Fold, parse::Parse, parse_quote, punctuated::Punctuated, Block, Expr, ExprCall, ExprPath, - FnArg, ItemFn, Meta, Pat, PatType, Path, Signature, Token, -}; - -/// Host function builder. -pub struct HostFn { - item: ItemFn, - meta: HostFnMeta, -} - -impl HostFn { - /// Create a new host function builder. - pub fn new(meta: HostFnMeta, item: ItemFn) -> Self { - Self { item, meta } - } - - /// Build the host function. - pub fn build(mut self) -> TokenStream { - let maybe_wgas = self.meta.fold_item_fn(ItemFn { - attrs: self.item.attrs.clone(), - vis: self.item.vis.clone(), - sig: self.build_sig(), - block: self.build_block(), - }); - - if !self.meta.wgas { - return maybe_wgas.into_token_stream().into(); - } - - self.meta.wgas = false; - let without_gas = ItemFn { - attrs: self.item.attrs.clone(), - vis: self.item.vis.clone(), - sig: self.build_sig(), - block: self.build_block(), - }; - - quote! { - #without_gas - - #maybe_wgas - } - .into() - } - - /// Build inputs from the function signature. - fn build_inputs(&self) -> Vec { - let mut inputs = self.item.sig.inputs.iter().cloned().collect::>(); - - if matches!(self.meta.call_type, CallType::Fallible) { - inputs.push(parse_quote!(err_mid_ptr: u32)); - } - - if !self.meta.wgas { - return inputs; - } - - let mut injected = false; - let mut new_inputs = vec![]; - inputs.into_iter().for_each(|a| { - if let FnArg::Typed(PatType { pat, .. }) = a.clone() { - if let Pat::Ident(ident) = pat.as_ref() { - // TODO #2722 - if !injected && (ident.ident == "value_ptr" || ident.ident == "delay") { - new_inputs.push(parse_quote!(gas_limit: u64)); - injected = true; - } - } - } - - new_inputs.push(a); - }); - - new_inputs - } - - /// Build the signature of the function. - fn build_sig(&self) -> Signature { - let name = self.item.sig.ident.clone(); - let inputs = self.build_inputs(); - let output = self.item.sig.output.clone().into_token_stream(); - - parse_quote! { - fn #name(#(#inputs),*) #output - } - } - - /// Build the function body. - fn build_block(&self) -> Box { - let mut name = self.item.sig.ident.clone().to_string(); - if self.meta.wgas { - name += "_wgas"; - } - - let cost = self.meta.runtime_costs.clone(); - let err = self.meta.err.clone(); - let inner_block = self.item.block.clone(); - let inputs = self.build_inputs(); - - let run: Expr = match self.meta.call_type { - CallType::Any => { - parse_quote! { - ctx.run_any(gas, #cost, |ctx| { - #inner_block - }) - } - } - CallType::Fallible => { - parse_quote! { - ctx.run_fallible::<_, _, #err>(gas, err_mid_ptr, #cost, |ctx| { - #inner_block - }) - } - } - }; - - let mut log_args: Vec = vec![parse_quote!(#name)]; - log_args.extend( - inputs - .into_iter() - .skip(1) - .filter_map(|a| match a { - FnArg::Typed(PatType { pat, .. }) => match pat.as_ref() { - Pat::Ident(ident) => Some(Expr::Path(ExprPath { - attrs: Default::default(), - qself: None, - path: Path::from(ident.clone().ident), - })), - _ => None, - }, - _ => None, - }) - .collect::>(), - ); - - parse_quote! ({ - syscall_trace!(#(#log_args),*); - - #run - }) - } -} - -impl From for TokenStream { - fn from(host_fn: HostFn) -> Self { - host_fn.build() - } -} - -/// Call type of the host function. -#[derive(Default)] -pub enum CallType { - #[default] - Any, - Fallible, -} - -/// Attribute meta of the host function. -pub struct HostFnMeta { - /// Call type of the host function. - pub call_type: CallType, - /// If the host function is wgas. - pub wgas: bool, - /// The runtime costs of the host function. - runtime_costs: Expr, - /// The length of the error. - pub err: Expr, -} - -impl Parse for HostFnMeta { - fn parse(input: syn::parse::ParseStream) -> syn::Result { - let mut call_type = Default::default(); - let mut wgas = false; - let mut runtime_costs = parse_quote!(RuntimeCosts::Null); - let mut err = parse_quote!(ErrorWithHash); - - let meta_list = Punctuated::::parse_terminated(input)?; - for meta in meta_list { - let ident = meta.path().get_ident().expect("Missing ident"); - match ident.to_string().as_ref() { - "fallible" => call_type = CallType::Fallible, - "wgas" => wgas = true, - "cost" => runtime_costs = meta.require_name_value()?.value.clone(), - "err" => err = meta.require_name_value()?.value.clone(), - _ => {} - } - } - - Ok(Self { - call_type, - wgas, - runtime_costs, - err, - }) - } -} - -impl Fold for HostFnMeta { - fn fold_item_fn(&mut self, mut item: ItemFn) -> ItemFn { - if !self.wgas { - return item; - } - - item.sig.ident = Ident::new( - &(item.sig.ident.to_token_stream().to_string() + "_wgas"), - Span::call_site(), - ); - - item.block = Box::new(self.fold_block(*item.block)); - item - } - - fn fold_expr_call(&mut self, mut expr: ExprCall) -> ExprCall { - if !self.wgas { - return expr; - } - - if let Expr::Path(ExprPath { - path: Path { segments, .. }, - .. - }) = &mut *expr.func - { - if segments - .first() - .map(|i| i.to_token_stream().to_string().ends_with("Packet")) - != Some(true) - { - return expr; - } - - if let Some(new) = segments.last_mut() { - new.ident = Ident::new("new_with_gas", Span::call_site()); - } - } - - if let Some(value) = expr.args.pop() { - expr.args.push(parse_quote!(gas_limit)); - expr.args.push(value.value().clone()); - } - - expr - } -} diff --git a/core-backend/codegen/src/lib.rs b/core-backend/codegen/src/lib.rs deleted file mode 100644 index 427ff085416..00000000000 --- a/core-backend/codegen/src/lib.rs +++ /dev/null @@ -1,91 +0,0 @@ -// 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 . - -use host::{HostFn, HostFnMeta}; -use proc_macro::TokenStream; -use syn::ItemFn; - -mod host; - -/// Apply host state wrapper to host functions. -/// -/// Supported meta attributes: -/// * `fallible` - if the host function executes fallible call. -/// * `wgas` - if the host function supports with-gas version. -/// * `cost` - RuntimeCosts definition, for example `#[host(cost = RuntimeCosts::Null)]` -/// * `err` - Structure definition with error code, for example `#[host(err = ErrorWithHash)]` -/// -/// # Example -/// -/// ```ignore -/// #[host(fallible, wgas, cost = RuntimeCosts::Reply(len))] -/// pub fn reply( -/// ctx: &mut R, -/// payload_ptr: u32, -/// len: u32, -/// value_ptr: u32, -/// delay: u32, -/// ) -> Result<(), R::Error> { -/// let read_payload = ctx.register_read(payload_ptr, len); -/// let value = ctx.register_and_read_value(value_ptr)?; -/// let payload = ctx.read(read_payload)?.try_into()?; -/// -/// let state = ctx.host_state_mut(); -/// state.ext.reply(ReplyPacket::new(payload, value), delay) -/// .map_err(Into::into) -/// } -/// ``` -/// -/// will generate -/// -/// ```ignore -/// pub fn reply(ctx: &mut R, -/// payload_ptr: u32, -/// len: u32, -/// value_ptr: u32, -/// delay: u32 -/// ) -> Result<(), R::Error> { -/// syscall_trace!("reply", payload_ptr, len, value_ptr, delay, err_mid_ptr); -/// -/// ctx.run_fallible::<_, _, ErrorWithHash>(err_mid_ptr, RuntimeCosts::Reply(len), |ctx| { -/// // ... -/// }) -/// } -/// -/// pub fn reply_wgas( -/// ctx: &mut R, -/// payload_ptr: u32, -/// len: u32, -/// gas_limit: u64, -/// value_ptr: u32, -/// delay: u32 -/// ) -> Result<(), R::Error> { -/// syscall_trace!("reply_wgas", payload_ptr, len, gas_limit, value_ptr, delay, err_mid_ptr); -/// -/// ctx.run_fallible::<_, _, ErrorWithHash>(err_mid_ptr, RuntimeCosts::ReplyWGas(len), |ctx| { -/// // ... -/// }) -/// } -/// ``` -#[proc_macro_attribute] -pub fn host(meta: TokenStream, item: TokenStream) -> TokenStream { - let meta: HostFnMeta = syn::parse_macro_input!(meta); - let item: ItemFn = syn::parse_macro_input!(item); - - HostFn::new(meta, item).into() -} diff --git a/core-backend/src/env.rs b/core-backend/src/env.rs index 625eec6e8b0..1f0b4fdf24e 100644 --- a/core-backend/src/env.rs +++ b/core-backend/src/env.rs @@ -26,7 +26,6 @@ use crate::{ funcs::FuncsHandler, memory::MemoryWrap, runtime, - runtime::CallerWrap, state::{HostState, State}, BackendExternalities, }; @@ -51,110 +50,21 @@ use gear_sandbox::{ default_executor::{ EnvironmentDefinitionBuilder, Instance, Memory as DefaultExecutorMemory, Store, }, - AsContextExt, HostError, HostFuncType, IntoValue, ReturnValue, SandboxEnvironmentBuilder, - SandboxInstance, SandboxMemory, SandboxStore, Value, + AsContextExt, HostFuncType, ReturnValue, SandboxEnvironmentBuilder, SandboxInstance, + SandboxMemory, SandboxStore, Value, }; -use gear_sandbox_env::WasmReturnValue; use gear_wasm_instrument::{ syscalls::SysCallName::{self, *}, GLOBAL_NAME_GAS, STACK_END_EXPORT_NAME, }; -#[derive(Clone, Copy)] -struct SandboxValue(Value); - -impl From for SandboxValue { - fn from(value: i32) -> Self { - SandboxValue(Value::I32(value)) - } -} - -impl From for SandboxValue { - fn from(value: u32) -> Self { - SandboxValue(Value::I32(value as i32)) - } -} - -impl From for SandboxValue { - fn from(value: i64) -> Self { - SandboxValue(Value::I64(value)) - } -} - -impl TryFrom for u32 { - type Error = HostError; - - fn try_from(val: SandboxValue) -> Result { - if let Value::I32(val) = val.0 { - Ok(val as u32) - } else { - Err(HostError) - } - } -} - -impl TryFrom for u64 { - type Error = HostError; - - fn try_from(val: SandboxValue) -> Result { - if let Value::I64(val) = val.0 { - Ok(val as u64) - } else { - Err(HostError) - } - } -} - -macro_rules! wrap_common_func_internal_ret{ - ($func:path, $($arg_no:expr),*) => { - |ctx, args: &[Value]| -> Result { - let mut ctx = CallerWrap::prepare(ctx).map_err(|_| HostError)?; - $func(&mut ctx, $(SandboxValue(args[$arg_no]).try_into()?,)*) - .map(|(gas, r)| WasmReturnValue { - gas: gas as i64, - inner: r.into_value().into(), - }) - } - } -} - -macro_rules! wrap_common_func_internal_no_ret{ - ($func:path, $($arg_no:expr),*) => { - |ctx, _args: &[Value]| -> Result { - let mut ctx = CallerWrap::prepare(ctx).map_err(|_| HostError)?; - $func(&mut ctx, $(SandboxValue(_args[$arg_no]).try_into()?,)*) - .map(|(gas, _)| WasmReturnValue { - gas: gas as i64, - inner: ReturnValue::Unit, - }) - } - } -} - -#[rustfmt::skip] -macro_rules! wrap_common_func { - ($func:path, () -> ()) => { wrap_common_func_internal_no_ret!($func,) }; - ($func:path, (1) -> ()) => { wrap_common_func_internal_no_ret!($func, 0) }; - ($func:path, (2) -> ()) => { wrap_common_func_internal_no_ret!($func, 0, 1) }; - ($func:path, (3) -> ()) => { wrap_common_func_internal_no_ret!($func, 0, 1, 2) }; - ($func:path, (4) -> ()) => { wrap_common_func_internal_no_ret!($func, 0, 1, 2, 3) }; - ($func:path, (5) -> ()) => { wrap_common_func_internal_no_ret!($func, 0, 1, 2, 3, 4) }; - ($func:path, (6) -> ()) => { wrap_common_func_internal_no_ret!($func, 0, 1, 2, 3, 4, 5) }; - ($func:path, (7) -> ()) => { wrap_common_func_internal_no_ret!($func, 0, 1, 2, 3, 4, 5, 6) }; - ($func:path, (8) -> ()) => { wrap_common_func_internal_no_ret!($func, 0, 1, 2, 3, 4, 5, 6, 7) }; - ($func:path, (9) -> ()) => { wrap_common_func_internal_no_ret!($func, 0, 1, 2, 3, 4, 5, 6, 7, 8) }; - ($func:path, (10) -> ()) => { wrap_common_func_internal_no_ret!($func, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9) }; - - ($func:path, () -> (1)) => { wrap_common_func_internal_ret!($func,) }; - ($func:path, (1) -> (1)) => { wrap_common_func_internal_ret!($func, 0) }; - ($func:path, (2) -> (1)) => { wrap_common_func_internal_ret!($func, 0, 1) }; - ($func:path, (3) -> (1)) => { wrap_common_func_internal_ret!($func, 0, 1, 2) }; - ($func:path, (4) -> (1)) => { wrap_common_func_internal_ret!($func, 0, 1, 2, 3) }; - ($func:path, (5) -> (1)) => { wrap_common_func_internal_ret!($func, 0, 1, 2, 3, 4) }; - ($func:path, (6) -> (1)) => { wrap_common_func_internal_ret!($func, 0, 1, 2, 3, 4, 5) }; - ($func:path, (7) -> (1)) => { wrap_common_func_internal_ret!($func, 0, 1, 2, 3, 4, 5, 6) }; - ($func:path, (8) -> (1)) => { wrap_common_func_internal_ret!($func, 0, 1, 2, 3, 4, 5, 6, 7) }; - ($func:path, (9) -> (1)) => { wrap_common_func_internal_ret!($func, 0, 1, 2, 3, 4, 5, 6, 7, 8) }; +// we have requirement to pass function pointer for `gear_sandbox` +// so the only reason this macro exists is const function pointers are not stabilized yet +// so we create non-capturing closure that can be coerced into function pointer +macro_rules! wrap_syscall { + ($func:ident) => { + |caller, args| FuncsHandler::execute(caller, args, FuncsHandler::$func) + }; } fn store_host_state_mut( @@ -242,11 +152,8 @@ where f: HostFuncType>, ) { if self.forbidden_funcs.contains(&name) { - self.env_def_builder.add_host_func( - "env", - name.to_str(), - wrap_common_func!(FuncsHandler::forbidden, (1) -> ()), - ); + self.env_def_builder + .add_host_func("env", name.to_str(), wrap_syscall!(forbidden)); } else { self.env_def_builder.add_host_func("env", name.to_str(), f); } @@ -277,62 +184,62 @@ where { #[rustfmt::skip] fn bind_funcs(builder: &mut EnvBuilder) { - builder.add_func(BlockHeight, wrap_common_func!(FuncsHandler::block_height, (2) -> ())); - builder.add_func(BlockTimestamp,wrap_common_func!(FuncsHandler::block_timestamp, (2) -> ())); - builder.add_func(CreateProgram, wrap_common_func!(FuncsHandler::create_program, (8) -> ())); - builder.add_func(CreateProgramWGas, wrap_common_func!(FuncsHandler::create_program_wgas, (9) -> ())); - builder.add_func(Debug, wrap_common_func!(FuncsHandler::debug, (3) -> ())); - builder.add_func(Panic, wrap_common_func!(FuncsHandler::panic, (3) -> ())); - builder.add_func(OomPanic, wrap_common_func!(FuncsHandler::oom_panic, (1) -> ())); - builder.add_func(Exit, wrap_common_func!(FuncsHandler::exit, (2) -> ())); - builder.add_func(ReplyCode, wrap_common_func!(FuncsHandler::reply_code, (2) -> ())); - builder.add_func(SignalCode, wrap_common_func!(FuncsHandler::signal_code, (2) -> ())); - builder.add_func(ReserveGas, wrap_common_func!(FuncsHandler::reserve_gas, (4) -> ())); - builder.add_func(ReplyDeposit, wrap_common_func!(FuncsHandler::reply_deposit, (4) -> ())); - builder.add_func(UnreserveGas, wrap_common_func!(FuncsHandler::unreserve_gas, (3) -> ())); - builder.add_func(GasAvailable, wrap_common_func!(FuncsHandler::gas_available, (2) -> ())); - builder.add_func(Leave, wrap_common_func!(FuncsHandler::leave, (1) -> ())); - builder.add_func(MessageId, wrap_common_func!(FuncsHandler::message_id, (2) -> ())); - builder.add_func(PayProgramRent, wrap_common_func!(FuncsHandler::pay_program_rent, (3) -> ())); - builder.add_func(ProgramId, wrap_common_func!(FuncsHandler::program_id, (2) -> ())); - builder.add_func(Random, wrap_common_func!(FuncsHandler::random, (3) -> ())); - builder.add_func(Read, wrap_common_func!(FuncsHandler::read, (5) -> ())); - builder.add_func(Reply, wrap_common_func!(FuncsHandler::reply, (5) -> ())); - builder.add_func(ReplyCommit, wrap_common_func!(FuncsHandler::reply_commit, (3) -> ())); - builder.add_func(ReplyCommitWGas, wrap_common_func!(FuncsHandler::reply_commit_wgas, (4) -> ())); - builder.add_func(ReplyPush, wrap_common_func!(FuncsHandler::reply_push, (4) -> ())); - builder.add_func(ReplyTo, wrap_common_func!(FuncsHandler::reply_to, (2) -> ())); - builder.add_func(SignalFrom, wrap_common_func!(FuncsHandler::signal_from, (2) -> ())); - builder.add_func(ReplyWGas, wrap_common_func!(FuncsHandler::reply_wgas, (6) -> ())); - builder.add_func(ReplyInput, wrap_common_func!(FuncsHandler::reply_input, (5) -> ())); - builder.add_func(ReplyPushInput, wrap_common_func!(FuncsHandler::reply_push_input, (4) -> ())); - builder.add_func(ReplyInputWGas, wrap_common_func!(FuncsHandler::reply_input_wgas, (6) -> ())); - builder.add_func(Send, wrap_common_func!(FuncsHandler::send, (6) -> ())); - builder.add_func(SendCommit, wrap_common_func!(FuncsHandler::send_commit, (5) -> ())); - builder.add_func(SendCommitWGas, wrap_common_func!(FuncsHandler::send_commit_wgas, (6) -> ())); - builder.add_func(SendInit, wrap_common_func!(FuncsHandler::send_init, (2) -> ())); - builder.add_func(SendPush, wrap_common_func!(FuncsHandler::send_push, (5) -> ())); - builder.add_func(SendWGas, wrap_common_func!(FuncsHandler::send_wgas, (7) -> ())); - builder.add_func(SendInput, wrap_common_func!(FuncsHandler::send_input, (6) -> ())); - builder.add_func(SendPushInput, wrap_common_func!(FuncsHandler::send_push_input, (5) -> ())); - builder.add_func(SendInputWGas, wrap_common_func!(FuncsHandler::send_input_wgas, (7) -> ())); - builder.add_func(Size, wrap_common_func!(FuncsHandler::size, (2) -> ())); - builder.add_func(Source, wrap_common_func!(FuncsHandler::source, (2) -> ())); - builder.add_func(Value, wrap_common_func!(FuncsHandler::value, (2) -> ())); - builder.add_func(ValueAvailable, wrap_common_func!(FuncsHandler::value_available, (2) -> ())); - builder.add_func(Wait, wrap_common_func!(FuncsHandler::wait, (1) -> ())); - builder.add_func(WaitFor, wrap_common_func!(FuncsHandler::wait_for, (2) -> ())); - builder.add_func(WaitUpTo, wrap_common_func!(FuncsHandler::wait_up_to, (2) -> ())); - builder.add_func(Wake, wrap_common_func!(FuncsHandler::wake, (4) -> ())); - builder.add_func(SystemReserveGas, wrap_common_func!(FuncsHandler::system_reserve_gas, (3) -> ())); - builder.add_func(ReservationReply, wrap_common_func!(FuncsHandler::reservation_reply, (5) -> ())); - builder.add_func(ReservationReplyCommit, wrap_common_func!(FuncsHandler::reservation_reply_commit, (3) -> ())); - builder.add_func(ReservationSend, wrap_common_func!(FuncsHandler::reservation_send, (6) -> ())); - builder.add_func(ReservationSendCommit, wrap_common_func!(FuncsHandler::reservation_send_commit, (5) -> ())); - builder.add_func(OutOfGas, wrap_common_func!(FuncsHandler::out_of_gas, (1) -> ())); - - builder.add_func(Alloc, wrap_common_func!(FuncsHandler::alloc, (2) -> (1))); - builder.add_func(Free, wrap_common_func!(FuncsHandler::free, (2) -> (1))); + builder.add_func(BlockHeight, wrap_syscall!(block_height)); + builder.add_func(BlockTimestamp,wrap_syscall!(block_timestamp)); + builder.add_func(CreateProgram, wrap_syscall!(create_program)); + builder.add_func(CreateProgramWGas, wrap_syscall!(create_program_wgas)); + builder.add_func(Debug, wrap_syscall!(debug)); + builder.add_func(Panic, wrap_syscall!(panic)); + builder.add_func(OomPanic, wrap_syscall!(oom_panic)); + builder.add_func(Exit, wrap_syscall!(exit)); + builder.add_func(ReplyCode, wrap_syscall!(reply_code)); + builder.add_func(SignalCode, wrap_syscall!(signal_code)); + builder.add_func(ReserveGas, wrap_syscall!(reserve_gas)); + builder.add_func(ReplyDeposit, wrap_syscall!(reply_deposit)); + builder.add_func(UnreserveGas, wrap_syscall!(unreserve_gas)); + builder.add_func(GasAvailable, wrap_syscall!(gas_available)); + builder.add_func(Leave, wrap_syscall!(leave)); + builder.add_func(MessageId, wrap_syscall!(message_id)); + builder.add_func(PayProgramRent, wrap_syscall!(pay_program_rent)); + builder.add_func(ProgramId, wrap_syscall!(program_id)); + builder.add_func(Random, wrap_syscall!(random)); + builder.add_func(Read, wrap_syscall!(read)); + builder.add_func(Reply, wrap_syscall!(reply)); + builder.add_func(ReplyCommit, wrap_syscall!(reply_commit)); + builder.add_func(ReplyCommitWGas, wrap_syscall!(reply_commit_wgas)); + builder.add_func(ReplyPush, wrap_syscall!(reply_push)); + builder.add_func(ReplyTo, wrap_syscall!(reply_to)); + builder.add_func(SignalFrom, wrap_syscall!(signal_from)); + builder.add_func(ReplyWGas, wrap_syscall!(reply_wgas)); + builder.add_func(ReplyInput, wrap_syscall!(reply_input)); + builder.add_func(ReplyPushInput, wrap_syscall!(reply_push_input)); + builder.add_func(ReplyInputWGas, wrap_syscall!(reply_input_wgas)); + builder.add_func(Send, wrap_syscall!(send)); + builder.add_func(SendCommit, wrap_syscall!(send_commit)); + builder.add_func(SendCommitWGas, wrap_syscall!(send_commit_wgas)); + builder.add_func(SendInit, wrap_syscall!(send_init)); + builder.add_func(SendPush, wrap_syscall!(send_push)); + builder.add_func(SendWGas, wrap_syscall!(send_wgas)); + builder.add_func(SendInput, wrap_syscall!(send_input)); + builder.add_func(SendPushInput, wrap_syscall!(send_push_input)); + builder.add_func(SendInputWGas, wrap_syscall!(send_input_wgas)); + builder.add_func(Size, wrap_syscall!(size)); + builder.add_func(Source, wrap_syscall!(source)); + builder.add_func(Value, wrap_syscall!(value)); + builder.add_func(ValueAvailable, wrap_syscall!(value_available)); + builder.add_func(Wait, wrap_syscall!(wait)); + builder.add_func(WaitFor, wrap_syscall!(wait_for)); + builder.add_func(WaitUpTo, wrap_syscall!(wait_up_to)); + builder.add_func(Wake, wrap_syscall!(wake)); + builder.add_func(SystemReserveGas, wrap_syscall!(system_reserve_gas)); + builder.add_func(ReservationReply, wrap_syscall!(reservation_reply)); + builder.add_func(ReservationReplyCommit, wrap_syscall!(reservation_reply_commit)); + builder.add_func(ReservationSend, wrap_syscall!(reservation_send)); + builder.add_func(ReservationSendCommit, wrap_syscall!(reservation_send_commit)); + builder.add_func(OutOfGas, wrap_syscall!(out_of_gas)); + + builder.add_func(Alloc, wrap_syscall!(alloc)); + builder.add_func(Free, wrap_syscall!(free)); } } diff --git a/core-backend/src/funcs.rs b/core-backend/src/funcs.rs index 3b064431d60..7bfe666ce92 100644 --- a/core-backend/src/funcs.rs +++ b/core-backend/src/funcs.rs @@ -24,8 +24,9 @@ use crate::{ TrapExplanation, UndefinedTerminationReason, UnrecoverableExecutionError, UnrecoverableMemoryError, }, - memory::{MemoryAccessError, WasmMemoryRead}, + memory::{ExecutorMemory, MemoryAccessError, WasmMemoryRead}, runtime::CallerWrap, + state::HostState, BackendExternalities, }; use alloc::string::{String, ToString}; @@ -36,55 +37,303 @@ use gear_core::{ costs::RuntimeCosts, env::{DropPayloadLockBound, Externalities}, gas::CounterType, + ids::{MessageId, ProgramId}, message::{ HandlePacket, InitPacket, MessageWaitedType, Payload, PayloadSizeError, ReplyPacket, }, pages::{PageNumber, PageU32Size, WasmPage}, }; -use gear_core_backend_codegen::host; use gear_core_errors::{MessageError, ReplyCode, SignalCode}; -use gear_sandbox_env::HostError; +use gear_sandbox::{default_executor::Caller, ReturnValue, Value}; +use gear_sandbox_env::{HostError, WasmReturnValue}; use gsys::{ BlockNumberWithHash, ErrorBytes, ErrorWithBlockNumberAndValue, ErrorWithGas, ErrorWithHandle, - ErrorWithHash, ErrorWithReplyCode, ErrorWithSignalCode, ErrorWithTwoHashes, Hash, + ErrorWithHash, ErrorWithReplyCode, ErrorWithSignalCode, ErrorWithTwoHashes, Gas, Hash, HashWithValue, TwoHashesWithValue, }; -#[macro_export(local_inner_macros)] -macro_rules! syscall_args_trace { - ($val:expr) => { - { - let s = ::core::stringify!($val); - if s.ends_with("_ptr") { - alloc::format!(", {} = {:#x?}", s, $val) - } else { - alloc::format!(", {} = {:?}", s, $val) - } +const PTR_SPECIAL: u32 = u32::MAX; + +/// Actually just wrapper around [`Value`] to implement conversions. +#[derive(Clone, Copy)] +struct SysCallValue(Value); + +impl From for SysCallValue { + fn from(value: i32) -> Self { + SysCallValue(Value::I32(value)) + } +} + +impl From for SysCallValue { + fn from(value: u32) -> Self { + SysCallValue(Value::I32(value as i32)) + } +} + +impl From for SysCallValue { + fn from(value: i64) -> Self { + SysCallValue(Value::I64(value)) + } +} + +impl TryFrom for u32 { + type Error = HostError; + + fn try_from(val: SysCallValue) -> Result { + if let Value::I32(val) = val.0 { + Ok(val as u32) + } else { + Err(HostError) } - }; - ($val:expr, $($rest:expr),+) => { - { - let mut s = syscall_args_trace!($val); - s.push_str(&syscall_args_trace!($($rest),+)); - s + } +} + +impl TryFrom for u64 { + type Error = HostError; + + fn try_from(val: SysCallValue) -> Result { + if let Value::I64(val) = val.0 { + Ok(val as u64) + } else { + Err(HostError) } - }; + } +} + +/// Actually just wrapper around [`ReturnValue`] to implement conversions. +pub struct SysCallReturnValue(ReturnValue); + +impl From for ReturnValue { + fn from(value: SysCallReturnValue) -> Self { + value.0 + } +} + +impl From<()> for SysCallReturnValue { + fn from((): ()) -> Self { + Self(ReturnValue::Unit) + } +} + +impl From for SysCallReturnValue { + fn from(value: i32) -> Self { + Self(ReturnValue::Value(Value::I32(value))) + } +} + +impl From for SysCallReturnValue { + fn from(value: u32) -> Self { + Self(ReturnValue::Value(Value::I32(value as i32))) + } +} + +pub(crate) trait SysCallContext: Sized { + fn from_args(args: &[Value]) -> Result<(Self, &[Value]), HostError>; } -macro_rules! syscall_trace { - ($name:expr, $($args:expr),+) => { +impl SysCallContext for () { + fn from_args(args: &[Value]) -> Result<(Self, &[Value]), HostError> { + Ok(((), args)) + } +} + +pub(crate) trait SysCall { + type Context: SysCallContext; + + fn execute( + self, + caller: &mut CallerWrap, + ctx: Self::Context, + ) -> Result<(Gas, T), HostError>; +} + +/// Trait is implemented for functions. +/// +/// # Generics +/// `Args` is to make specialization based on function arguments +/// `Ext` and `Res` are for sys-call itself (`SysCall`) +pub(crate) trait SysCallBuilder { + fn build(self, args: &[Value]) -> Result; +} + +impl SysCallBuilder for Builder +where + Builder: FnOnce() -> Call, + Call: SysCall, +{ + fn build(self, args: &[Value]) -> Result { + let _: [Value; 0] = args.try_into().map_err(|_| HostError)?; + Ok((self)()) + } +} + +impl SysCallBuilder for Builder +where + Builder: for<'a> FnOnce(&'a [Value]) -> Call, + Call: SysCall, +{ + fn build(self, args: &[Value]) -> Result { + Ok((self)(args)) + } +} + +// implement [`SysCallBuilder`] for functions with different amount of arguments +macro_rules! impl_syscall_builder { + ($($generic:ident),+) => { + #[allow(non_snake_case)] + impl SysCallBuilder + for Builder + where + Builder: FnOnce($($generic),+) -> Call, + Call: SysCall, + $( $generic: TryFrom,)+ { - ::log::trace!(target: "syscalls", "{}{}", $name, syscall_args_trace!($($args),+)); + fn build(self, args: &[Value]) -> Result { + const ARGS_AMOUNT: usize = impl_syscall_builder!(@count $($generic),+); + + let [$($generic),+]: [Value; ARGS_AMOUNT] = args.try_into().map_err(|_| HostError)?; + $( + let $generic = SysCallValue($generic).try_into()?; + )+ + Ok((self)($($generic),+)) + } } }; - ($name:expr) => { - { - ::log::trace!(target: "syscalls", "{}", $name); + (@count $generic:ident) => { 1 }; + (@count $generic:ident, $($generics:ident),+) => { 1 + impl_syscall_builder!(@count $($generics),+) }; +} + +impl_syscall_builder!(A); +impl_syscall_builder!(A, B); +impl_syscall_builder!(A, B, C); +impl_syscall_builder!(A, B, C, D); +impl_syscall_builder!(A, B, C, D, E); +impl_syscall_builder!(A, B, C, D, E, F); +impl_syscall_builder!(A, B, C, D, E, F, G); + +/// "raw" sys-call without any argument parsing or without calling [`CallerWrap`] helper methods +struct RawSysCall(F); + +impl RawSysCall { + fn new(f: F) -> Self { + Self(f) + } +} + +impl SysCall for RawSysCall +where + F: FnOnce(&mut CallerWrap) -> Result<(Gas, T), HostError>, + Ext: BackendExternalities + 'static, +{ + type Context = (); + + fn execute( + self, + caller: &mut CallerWrap, + (): Self::Context, + ) -> Result<(Gas, T), HostError> { + (self.0)(caller) + } +} + +/// Fallible sys-call context that parses `gas` and `err_ptr` arguments. +struct FallibleSysCallContext { + gas: Gas, + res_ptr: u32, +} + +impl SysCallContext for FallibleSysCallContext { + fn from_args(args: &[Value]) -> Result<(Self, &[Value]), HostError> { + let (gas, args) = args.split_first().ok_or(HostError)?; + let gas: Gas = SysCallValue(*gas).try_into()?; + let (res_ptr, args) = args.split_last().ok_or(HostError)?; + let res_ptr: u32 = SysCallValue(*res_ptr).try_into()?; + Ok((FallibleSysCallContext { gas, res_ptr }, args)) + } +} + +/// Fallible sys-call that calls [`CallerWrap::run_fallible`] underneath. +struct FallibleSysCall { + costs: RuntimeCosts, + error: PhantomData, + f: F, +} + +impl FallibleSysCall<(), F> { + fn new(costs: RuntimeCosts, f: F) -> FallibleSysCall { + FallibleSysCall { + costs, + error: PhantomData, + f, } } } -const PTR_SPECIAL: u32 = u32::MAX; +impl SysCall for FallibleSysCall +where + F: FnOnce(&mut CallerWrap) -> Result, + E: From>, + Ext: BackendExternalities + 'static, +{ + type Context = FallibleSysCallContext; + + fn execute( + self, + caller: &mut CallerWrap, + context: Self::Context, + ) -> Result<(Gas, ()), HostError> { + let Self { + costs, + error: _error, + f, + } = self; + let FallibleSysCallContext { gas, res_ptr } = context; + caller.run_fallible::(gas, res_ptr, costs, f) + } +} + +/// Infallible sys-call context that parses `gas` argument. +pub struct InfallibleSysCallContext { + gas: Gas, +} + +impl SysCallContext for InfallibleSysCallContext { + fn from_args(args: &[Value]) -> Result<(Self, &[Value]), HostError> { + let (gas, args) = args.split_first().ok_or(HostError)?; + let gas: Gas = SysCallValue(*gas).try_into()?; + Ok((Self { gas }, args)) + } +} + +/// Infallible sys-call that calls [`CallerWrap::run_any`] underneath +struct InfallibleSysCall { + costs: RuntimeCosts, + f: F, +} + +impl InfallibleSysCall { + fn new(costs: RuntimeCosts, f: F) -> Self { + Self { costs, f } + } +} + +impl SysCall for InfallibleSysCall +where + F: Fn(&mut CallerWrap) -> Result, + Ext: BackendExternalities + 'static, +{ + type Context = InfallibleSysCallContext; + + fn execute( + self, + caller: &mut CallerWrap, + ctx: Self::Context, + ) -> Result<(Gas, T), HostError> { + let Self { costs, f } = self; + let InfallibleSysCallContext { gas } = ctx; + caller.run_any::(gas, costs, f) + } +} pub(crate) struct FuncsHandler { _phantom: PhantomData, @@ -97,6 +346,32 @@ where RunFallibleError: From, Ext::AllocError: BackendAllocSyscallError, { + pub fn execute( + caller: &mut Caller>, + args: &[Value], + builder: Builder, + ) -> Result + where + Builder: SysCallBuilder, + Args: ?Sized, + Call: SysCall, + Res: Into, + { + crate::log::trace_syscall::(args); + + let mut caller = CallerWrap::prepare(caller); + + let (ctx, args) = Call::Context::from_args(args)?; + let sys_call = builder.build(args)?; + let (gas, value) = sys_call.execute(&mut caller, ctx)?; + let value = value.into(); + + Ok(WasmReturnValue { + gas: gas as i64, + inner: value.0, + }) + } + /// !!! Usage warning: make sure to do it before any other read/write, /// because it may contain registered read. fn register_and_read_value( @@ -121,17 +396,14 @@ where .map_err(RunFallibleError::FallibleExt) } - // TODO #3037 - #[allow(clippy::too_many_arguments)] - #[host(fallible, wgas, cost = RuntimeCosts::Send(len))] - pub fn send( - ctx: &mut CallerWrap<'_, '_, Ext>, - gas: u64, + fn send_inner( + ctx: &mut CallerWrap, pid_value_ptr: u32, payload_ptr: u32, len: u32, + gas_limit: Option, delay: u32, - ) -> Result<(u64, ()), HostError> { + ) -> Result { let read_hash_val = ctx.manager.register_read_as(pid_value_ptr); let read_payload = ctx.manager.register_read(payload_ptr, len); let HashWithValue { @@ -141,18 +413,44 @@ where let payload = Self::read_message_payload(ctx, read_payload)?; ctx.ext_mut() - .send(HandlePacket::new(destination.into(), payload, value), delay) + .send( + HandlePacket::maybe_with_gas(destination.into(), payload, gas_limit, value), + delay, + ) .map_err(Into::into) } - #[host(fallible, wgas, cost = RuntimeCosts::SendCommit)] - pub fn send_commit( - ctx: &mut CallerWrap<'_, '_, Ext>, - gas: u64, + pub fn send(pid_value_ptr: u32, payload_ptr: u32, len: u32, delay: u32) -> impl SysCall { + FallibleSysCall::new::( + RuntimeCosts::Send(len), + move |ctx: &mut CallerWrap| { + Self::send_inner(ctx, pid_value_ptr, payload_ptr, len, None, delay) + }, + ) + } + + pub fn send_wgas( + pid_value_ptr: u32, + payload_ptr: u32, + len: u32, + gas_limit: u64, + delay: u32, + ) -> impl SysCall { + FallibleSysCall::new::( + RuntimeCosts::SendWGas(len), + move |ctx: &mut CallerWrap| { + Self::send_inner(ctx, pid_value_ptr, payload_ptr, len, Some(gas_limit), delay) + }, + ) + } + + fn send_commit_inner( + ctx: &mut CallerWrap, handle: u32, pid_value_ptr: u32, + gas_limit: Option, delay: u32, - ) -> Result<(u64, ()), HostError> { + ) -> Result { let read_pid_value = ctx.manager.register_read_as(pid_value_ptr); let HashWithValue { hash: destination, @@ -162,670 +460,748 @@ where ctx.ext_mut() .send_commit( handle, - HandlePacket::new(destination.into(), Default::default(), value), + HandlePacket::maybe_with_gas( + destination.into(), + Default::default(), + gas_limit, + value, + ), delay, ) .map_err(Into::into) } - #[host(fallible, cost = RuntimeCosts::SendInit, err = ErrorWithHandle)] - pub fn send_init(ctx: &mut CallerWrap<'_, '_, Ext>, gas: u64) -> Result<(u64, ()), HostError> { - ctx.ext_mut().send_init().map_err(Into::into) + pub fn send_commit(handle: u32, pid_value_ptr: u32, delay: u32) -> impl SysCall { + FallibleSysCall::new::( + RuntimeCosts::SendCommit, + move |ctx: &mut CallerWrap| { + Self::send_commit_inner(ctx, handle, pid_value_ptr, None, delay) + }, + ) } - #[host(fallible, cost = RuntimeCosts::SendPush(len), err = ErrorBytes)] - pub fn send_push( - ctx: &mut CallerWrap<'_, '_, Ext>, - gas: u64, + pub fn send_commit_wgas( handle: u32, - payload_ptr: u32, - len: u32, - ) -> Result<(u64, ()), HostError> { - let read_payload = ctx.manager.register_read(payload_ptr, len); - let payload = ctx.read(read_payload)?; + pid_value_ptr: u32, + gas_limit: u64, + delay: u32, + ) -> impl SysCall { + FallibleSysCall::new::( + RuntimeCosts::SendCommitWGas, + move |ctx: &mut CallerWrap| { + Self::send_commit_inner(ctx, handle, pid_value_ptr, Some(gas_limit), delay) + }, + ) + } - ctx.ext_mut() - .send_push(handle, &payload) - .map_err(Into::into) + pub fn send_init() -> impl SysCall { + FallibleSysCall::new::( + RuntimeCosts::SendInit, + move |ctx: &mut CallerWrap| ctx.ext_mut().send_init().map_err(Into::into), + ) + } + + pub fn send_push(handle: u32, payload_ptr: u32, len: u32) -> impl SysCall { + FallibleSysCall::new::( + RuntimeCosts::SendPush(len), + move |ctx: &mut CallerWrap| { + let read_payload = ctx.manager.register_read(payload_ptr, len); + let payload = ctx.read(read_payload)?; + + ctx.ext_mut() + .send_push(handle, &payload) + .map_err(Into::into) + }, + ) } - #[host(fallible, cost = RuntimeCosts::ReservationSend(len))] pub fn reservation_send( - ctx: &mut CallerWrap<'_, '_, Ext>, - gas: u64, rid_pid_value_ptr: u32, payload_ptr: u32, len: u32, delay: u32, - ) -> Result<(u64, ()), HostError> { - let read_rid_pid_value = ctx.manager.register_read_as(rid_pid_value_ptr); - let read_payload = ctx.manager.register_read(payload_ptr, len); - let TwoHashesWithValue { - hash1: reservation_id, - hash2: destination, - value, - } = ctx.read_as(read_rid_pid_value)?; - let payload = Self::read_message_payload(ctx, read_payload)?; - - ctx.ext_mut() - .reservation_send( - reservation_id.into(), - HandlePacket::new(destination.into(), payload, value), - delay, - ) - .map_err(Into::into) + ) -> impl SysCall { + FallibleSysCall::new::( + RuntimeCosts::ReservationSend(len), + move |ctx: &mut CallerWrap| { + let read_rid_pid_value = ctx.manager.register_read_as(rid_pid_value_ptr); + let read_payload = ctx.manager.register_read(payload_ptr, len); + let TwoHashesWithValue { + hash1: reservation_id, + hash2: destination, + value, + } = ctx.read_as(read_rid_pid_value)?; + let payload = Self::read_message_payload(ctx, read_payload)?; + + ctx.ext_mut() + .reservation_send( + reservation_id.into(), + HandlePacket::new(destination.into(), payload, value), + delay, + ) + .map_err(Into::into) + }, + ) } - #[host(fallible, cost = RuntimeCosts::ReservationSendCommit)] pub fn reservation_send_commit( - ctx: &mut CallerWrap<'_, '_, Ext>, - gas: u64, handle: u32, rid_pid_value_ptr: u32, delay: u32, - ) -> Result<(u64, ()), HostError> { - let read_rid_pid_value = ctx.manager.register_read_as(rid_pid_value_ptr); - let TwoHashesWithValue { - hash1: reservation_id, - hash2: destination, - value, - } = ctx.read_as(read_rid_pid_value)?; - - ctx.ext_mut() - .reservation_send_commit( - reservation_id.into(), - handle, - HandlePacket::new(destination.into(), Default::default(), value), - delay, - ) - .map_err(Into::into) + ) -> impl SysCall { + FallibleSysCall::new::( + RuntimeCosts::ReservationSendCommit, + move |ctx: &mut CallerWrap| { + let read_rid_pid_value = ctx.manager.register_read_as(rid_pid_value_ptr); + let TwoHashesWithValue { + hash1: reservation_id, + hash2: destination, + value, + } = ctx.read_as(read_rid_pid_value)?; + + ctx.ext_mut() + .reservation_send_commit( + reservation_id.into(), + handle, + HandlePacket::new(destination.into(), Default::default(), value), + delay, + ) + .map_err(Into::into) + }, + ) } - #[host(fallible, cost = RuntimeCosts::Read, err = ErrorBytes)] - pub fn read( - ctx: &mut CallerWrap<'_, '_, Ext>, - gas: u64, - at: u32, - len: u32, - buffer_ptr: u32, - ) -> Result<(u64, ()), HostError> { - let payload_lock = ctx.ext_mut().lock_payload(at, len)?; - payload_lock - .drop_with::(|payload_access| { - let write_buffer = ctx.manager.register_write(buffer_ptr, len); - let write_res = ctx.write(write_buffer, payload_access.as_slice()); - let unlock_bound = ctx.ext_mut().unlock_payload(payload_access.into_lock()); - - DropPayloadLockBound::from((unlock_bound, write_res)) - }) - .into_inner() - .map_err(Into::into) + pub fn read(at: u32, len: u32, buffer_ptr: u32) -> impl SysCall { + FallibleSysCall::new::(RuntimeCosts::Read, move |ctx: &mut CallerWrap| { + let payload_lock = ctx.ext_mut().lock_payload(at, len)?; + payload_lock + .drop_with::(|payload_access| { + let write_buffer = ctx.manager.register_write(buffer_ptr, len); + let write_res = ctx.write(write_buffer, payload_access.as_slice()); + let unlock_bound = ctx.ext_mut().unlock_payload(payload_access.into_lock()); + + DropPayloadLockBound::from((unlock_bound, write_res)) + }) + .into_inner() + .map_err(Into::into) + }) } - #[host(cost = RuntimeCosts::Size)] - pub fn size( - ctx: &mut CallerWrap<'_, '_, Ext>, - gas: u64, - size_ptr: u32, - ) -> Result<(u64, ()), HostError> { - let size = ctx.ext_mut().size()? as u32; + pub fn size(size_ptr: u32) -> impl SysCall { + InfallibleSysCall::new(RuntimeCosts::Size, move |ctx: &mut CallerWrap| { + let size = ctx.ext_mut().size()? as u32; - let write_size = ctx.manager.register_write_as(size_ptr); - ctx.write_as(write_size, size.to_le_bytes()) - .map_err(Into::into) + let write_size = ctx.manager.register_write_as(size_ptr); + ctx.write_as(write_size, size.to_le_bytes()) + .map_err(Into::into) + }) } - #[host(cost = RuntimeCosts::Exit)] - pub fn exit( - ctx: &mut CallerWrap<'_, '_, Ext>, - gas: u64, - inheritor_id_ptr: u32, - ) -> Result<(u64, ()), HostError> { - let read_inheritor_id = ctx.manager.register_read_decoded(inheritor_id_ptr); - let inheritor_id = ctx.read_decoded(read_inheritor_id)?; - Err(ActorTerminationReason::Exit(inheritor_id).into()) + pub fn exit(inheritor_id_ptr: u32) -> impl SysCall { + InfallibleSysCall::new(RuntimeCosts::Exit, move |ctx: &mut CallerWrap| { + let read_inheritor_id = ctx.manager.register_read_decoded(inheritor_id_ptr); + let inheritor_id = ctx.read_decoded(read_inheritor_id)?; + Err(ActorTerminationReason::Exit(inheritor_id).into()) + }) } - #[host(fallible, cost = RuntimeCosts::ReplyCode, err = ErrorWithReplyCode)] - pub fn reply_code(ctx: &mut CallerWrap<'_, '_, Ext>, gas: u64) -> Result<(u64, ()), HostError> { - ctx.ext_mut() - .reply_code() - .map(ReplyCode::to_bytes) - .map_err(Into::into) + pub fn reply_code() -> impl SysCall { + FallibleSysCall::new::( + RuntimeCosts::ReplyCode, + move |ctx: &mut CallerWrap| { + ctx.ext_mut() + .reply_code() + .map(ReplyCode::to_bytes) + .map_err(Into::into) + }, + ) } - #[host(fallible, cost = RuntimeCosts::SignalCode, err = ErrorWithSignalCode)] - pub fn signal_code( - ctx: &mut CallerWrap<'_, '_, Ext>, - gas: u64, - ) -> Result<(u64, ()), HostError> { - ctx.ext_mut() - .signal_code() - .map(SignalCode::to_u32) - .map_err(Into::into) + pub fn signal_code() -> impl SysCall { + FallibleSysCall::new::( + RuntimeCosts::SignalCode, + move |ctx: &mut CallerWrap| { + ctx.ext_mut() + .signal_code() + .map(SignalCode::to_u32) + .map_err(Into::into) + }, + ) } - #[host(cost = RuntimeCosts::Alloc(pages))] - pub fn alloc( - ctx: &mut CallerWrap<'_, '_, Ext>, - gas: u64, - pages: u32, - ) -> Result<(u64, u32), HostError> { - let res = ctx.alloc(pages); - let res = ctx.process_alloc_func_result(res)?; - - let page = match res { - Ok(page) => { - log::trace!("Alloc {pages:?} pages at {page:?}"); - page.raw() - } - Err(err) => { - log::trace!("Alloc failed: {err}"); - u32::MAX - } - }; - Ok(page) + pub fn alloc(pages: u32) -> impl SysCall { + InfallibleSysCall::new( + RuntimeCosts::Alloc(pages), + move |ctx: &mut CallerWrap| { + let res = ctx.alloc(pages); + let res = ctx.process_alloc_func_result(res)?; + + let page = match res { + Ok(page) => { + log::trace!("Alloc {pages:?} pages at {page:?}"); + page.raw() + } + Err(err) => { + log::trace!("Alloc failed: {err}"); + u32::MAX + } + }; + Ok(page) + }, + ) } - #[host(cost = RuntimeCosts::Free)] - pub fn free( - ctx: &mut CallerWrap<'_, '_, Ext>, - gas: u64, - page_no: u32, - ) -> Result<(u64, i32), HostError> { - let page = WasmPage::new(page_no).map_err(|_| { - UndefinedTerminationReason::Actor(ActorTerminationReason::Trap( - TrapExplanation::Unknown, - )) - })?; - - let res = ctx.ext_mut().free(page); - let res = ctx.process_alloc_func_result(res)?; - - match &res { - Ok(()) => { - log::trace!("Free {page:?}"); - } - Err(err) => { - log::trace!("Free failed: {err}"); - } - }; - - Ok(res.is_err() as i32) + pub fn free(page_no: u32) -> impl SysCall { + InfallibleSysCall::new(RuntimeCosts::Free, move |ctx: &mut CallerWrap| { + let page = WasmPage::new(page_no).map_err(|_| { + UndefinedTerminationReason::Actor(ActorTerminationReason::Trap( + TrapExplanation::Unknown, + )) + })?; + + let res = ctx.ext_mut().free(page); + let res = ctx.process_alloc_func_result(res)?; + + match &res { + Ok(()) => { + log::trace!("Free {page:?}"); + } + Err(err) => { + log::trace!("Free failed: {err}"); + } + }; + + Ok(res.is_err() as i32) + }) } - #[host(cost = RuntimeCosts::BlockHeight)] - pub fn block_height( - ctx: &mut CallerWrap<'_, '_, Ext>, - gas: u64, - height_ptr: u32, - ) -> Result<(u64, ()), HostError> { - let height = ctx.ext_mut().block_height()?; - - let write_height = ctx.manager.register_write_as(height_ptr); - ctx.write_as(write_height, height.to_le_bytes()) - .map_err(Into::into) + pub fn block_height(height_ptr: u32) -> impl SysCall { + InfallibleSysCall::new( + RuntimeCosts::BlockHeight, + move |ctx: &mut CallerWrap| { + let height = ctx.ext_mut().block_height()?; + + let write_height = ctx.manager.register_write_as(height_ptr); + ctx.write_as(write_height, height.to_le_bytes()) + .map_err(Into::into) + }, + ) } - #[host(cost = RuntimeCosts::BlockTimestamp)] - pub fn block_timestamp( - ctx: &mut CallerWrap<'_, '_, Ext>, - gas: u64, - timestamp_ptr: u32, - ) -> Result<(u64, ()), HostError> { - let timestamp = ctx.ext_mut().block_timestamp()?; - - let write_timestamp = ctx.manager.register_write_as(timestamp_ptr); - ctx.write_as(write_timestamp, timestamp.to_le_bytes()) - .map_err(Into::into) + pub fn block_timestamp(timestamp_ptr: u32) -> impl SysCall { + InfallibleSysCall::new( + RuntimeCosts::BlockTimestamp, + move |ctx: &mut CallerWrap| { + let timestamp = ctx.ext_mut().block_timestamp()?; + + let write_timestamp = ctx.manager.register_write_as(timestamp_ptr); + ctx.write_as(write_timestamp, timestamp.to_le_bytes()) + .map_err(Into::into) + }, + ) } - #[host(cost = RuntimeCosts::Random)] - pub fn random( - ctx: &mut CallerWrap<'_, '_, Ext>, - gas: u64, - subject_ptr: u32, - bn_random_ptr: u32, - ) -> Result<(u64, ()), HostError> { - let read_subject = ctx.manager.register_read_decoded(subject_ptr); - let write_bn_random = ctx.manager.register_write_as(bn_random_ptr); + pub fn random(subject_ptr: u32, bn_random_ptr: u32) -> impl SysCall { + InfallibleSysCall::new(RuntimeCosts::Random, move |ctx: &mut CallerWrap| { + let read_subject = ctx.manager.register_read_decoded(subject_ptr); + let write_bn_random = ctx.manager.register_write_as(bn_random_ptr); - let raw_subject: Hash = ctx.read_decoded(read_subject)?; + let raw_subject: Hash = ctx.read_decoded(read_subject)?; - let (random, bn) = ctx.ext_mut().random()?; - let subject = [&raw_subject, random].concat(); + let (random, bn) = ctx.ext_mut().random()?; + let subject = [&raw_subject, random].concat(); - let mut hash = [0; 32]; - hash.copy_from_slice(blake2b(32, &[], &subject).as_bytes()); + let mut hash = [0; 32]; + hash.copy_from_slice(blake2b(32, &[], &subject).as_bytes()); - ctx.write_as(write_bn_random, BlockNumberWithHash { bn, hash }) - .map_err(Into::into) + ctx.write_as(write_bn_random, BlockNumberWithHash { bn, hash }) + .map_err(Into::into) + }) } - #[host(fallible, wgas, cost = RuntimeCosts::Reply(len))] - pub fn reply( - ctx: &mut CallerWrap<'_, '_, Ext>, - gas: u64, + fn reply_inner( + ctx: &mut CallerWrap, payload_ptr: u32, len: u32, + gas_limit: Option, value_ptr: u32, - ) -> Result<(u64, ()), HostError> { + ) -> Result { let read_payload = ctx.manager.register_read(payload_ptr, len); let value = Self::register_and_read_value(ctx, value_ptr)?; let payload = Self::read_message_payload(ctx, read_payload)?; ctx.ext_mut() - .reply(ReplyPacket::new(payload, value)) + .reply(ReplyPacket::maybe_with_gas(payload, gas_limit, value)) .map_err(Into::into) } - #[host(fallible, wgas, cost = RuntimeCosts::ReplyCommit)] - pub fn reply_commit( - ctx: &mut CallerWrap<'_, '_, Ext>, - gas: u64, + pub fn reply(payload_ptr: u32, len: u32, value_ptr: u32) -> impl SysCall { + FallibleSysCall::new::( + RuntimeCosts::Reply(len), + move |ctx: &mut CallerWrap| { + Self::reply_inner(ctx, payload_ptr, len, None, value_ptr) + }, + ) + } + + pub fn reply_wgas( + payload_ptr: u32, + len: u32, + gas_limit: u64, + value_ptr: u32, + ) -> impl SysCall { + FallibleSysCall::new::( + RuntimeCosts::ReplyWGas(len), + move |ctx: &mut CallerWrap| { + Self::reply_inner(ctx, payload_ptr, len, Some(gas_limit), value_ptr) + }, + ) + } + + fn reply_commit_inner( + ctx: &mut CallerWrap, + gas_limit: Option, value_ptr: u32, - ) -> Result<(u64, ()), HostError> { + ) -> Result { let value = Self::register_and_read_value(ctx, value_ptr)?; ctx.ext_mut() - .reply_commit(ReplyPacket::new(Default::default(), value)) + .reply_commit(ReplyPacket::maybe_with_gas( + Default::default(), + gas_limit, + value, + )) .map_err(Into::into) } - #[host(fallible, cost = RuntimeCosts::ReservationReply(len))] - pub fn reservation_reply( - ctx: &mut CallerWrap<'_, '_, Ext>, - gas: u64, - rid_value_ptr: u32, - payload_ptr: u32, - len: u32, - ) -> Result<(u64, ()), HostError> { - let read_rid_value = ctx.manager.register_read_as(rid_value_ptr); - let read_payload = ctx.manager.register_read(payload_ptr, len); - let HashWithValue { - hash: reservation_id, - value, - } = ctx.read_as(read_rid_value)?; - let payload = Self::read_message_payload(ctx, read_payload)?; + pub fn reply_commit(value_ptr: u32) -> impl SysCall { + FallibleSysCall::new::( + RuntimeCosts::ReplyCommit, + move |ctx: &mut CallerWrap| Self::reply_commit_inner(ctx, None, value_ptr), + ) + } - ctx.ext_mut() - .reservation_reply(reservation_id.into(), ReplyPacket::new(payload, value)) - .map_err(Into::into) + pub fn reply_commit_wgas(gas_limit: u64, value_ptr: u32) -> impl SysCall { + FallibleSysCall::new::( + RuntimeCosts::ReplyCommitWGas, + move |ctx: &mut CallerWrap| { + Self::reply_commit_inner(ctx, Some(gas_limit), value_ptr) + }, + ) } - #[host(fallible, cost = RuntimeCosts::ReservationReplyCommit)] - pub fn reservation_reply_commit( - ctx: &mut CallerWrap<'_, '_, Ext>, - gas: u64, - rid_value_ptr: u32, - ) -> Result<(u64, ()), HostError> { - let read_rid_value = ctx.manager.register_read_as(rid_value_ptr); - let HashWithValue { - hash: reservation_id, - value, - } = ctx.read_as(read_rid_value)?; + pub fn reservation_reply(rid_value_ptr: u32, payload_ptr: u32, len: u32) -> impl SysCall { + FallibleSysCall::new::( + RuntimeCosts::ReservationReply(len), + move |ctx: &mut CallerWrap| { + let read_rid_value = ctx.manager.register_read_as(rid_value_ptr); + let read_payload = ctx.manager.register_read(payload_ptr, len); + let HashWithValue { + hash: reservation_id, + value, + } = ctx.read_as(read_rid_value)?; + let payload = Self::read_message_payload(ctx, read_payload)?; + + ctx.ext_mut() + .reservation_reply(reservation_id.into(), ReplyPacket::new(payload, value)) + .map_err(Into::into) + }, + ) + } - ctx.ext_mut() - .reservation_reply_commit( - reservation_id.into(), - ReplyPacket::new(Default::default(), value), - ) - .map_err(Into::into) + pub fn reservation_reply_commit(rid_value_ptr: u32) -> impl SysCall { + FallibleSysCall::new::( + RuntimeCosts::ReservationReplyCommit, + move |ctx: &mut CallerWrap| { + let read_rid_value = ctx.manager.register_read_as(rid_value_ptr); + let HashWithValue { + hash: reservation_id, + value, + } = ctx.read_as(read_rid_value)?; + + ctx.ext_mut() + .reservation_reply_commit( + reservation_id.into(), + ReplyPacket::new(Default::default(), value), + ) + .map_err(Into::into) + }, + ) } - #[host(fallible, cost = RuntimeCosts::ReplyTo)] - pub fn reply_to(ctx: &mut CallerWrap<'_, '_, Ext>, gas: u64) -> Result<(u64, ()), HostError> { - ctx.ext_mut().reply_to().map_err(Into::into) + pub fn reply_to() -> impl SysCall { + FallibleSysCall::new::( + RuntimeCosts::ReplyTo, + move |ctx: &mut CallerWrap| ctx.ext_mut().reply_to().map_err(Into::into), + ) } - #[host(fallible, cost = RuntimeCosts::SignalFrom)] - pub fn signal_from( - ctx: &mut CallerWrap<'_, '_, Ext>, - gas: u64, - ) -> Result<(u64, ()), HostError> { - ctx.ext_mut().signal_from().map_err(Into::into) + pub fn signal_from() -> impl SysCall { + FallibleSysCall::new::( + RuntimeCosts::SignalFrom, + move |ctx: &mut CallerWrap| ctx.ext_mut().signal_from().map_err(Into::into), + ) } - #[host(fallible, cost = RuntimeCosts::ReplyPush(len), err = ErrorBytes)] - pub fn reply_push( - ctx: &mut CallerWrap<'_, '_, Ext>, - gas: u64, - payload_ptr: u32, - len: u32, - ) -> Result<(u64, ()), HostError> { - let read_payload = ctx.manager.register_read(payload_ptr, len); - let payload = ctx.read(read_payload)?; + pub fn reply_push(payload_ptr: u32, len: u32) -> impl SysCall { + FallibleSysCall::new::( + RuntimeCosts::ReplyPush(len), + move |ctx: &mut CallerWrap| { + let read_payload = ctx.manager.register_read(payload_ptr, len); + let payload = ctx.read(read_payload)?; - ctx.ext_mut().reply_push(&payload).map_err(Into::into) + ctx.ext_mut().reply_push(&payload).map_err(Into::into) + }, + ) } - #[host(fallible, wgas, cost = RuntimeCosts::ReplyInput)] - pub fn reply_input( - ctx: &mut CallerWrap<'_, '_, Ext>, - gas: u64, + fn reply_input_inner( + ctx: &mut CallerWrap, offset: u32, len: u32, + gas_limit: Option, value_ptr: u32, - ) -> Result<(u64, ()), HostError> { - // Charge for `len` is inside `reply_push_input` + ) -> Result { let value = Self::register_and_read_value(ctx, value_ptr)?; - let mut f = || { - ctx.ext_mut().reply_push_input(offset, len)?; - ctx.ext_mut() - .reply_commit(ReplyPacket::new(Default::default(), value)) - }; + // Charge for `len` is inside `reply_push_input` + ctx.ext_mut().reply_push_input(offset, len)?; + + ctx.ext_mut() + .reply_commit(ReplyPacket::maybe_with_gas( + Default::default(), + gas_limit, + value, + )) + .map_err(Into::into) + } - f().map_err(Into::into) + pub fn reply_input(offset: u32, len: u32, value_ptr: u32) -> impl SysCall { + FallibleSysCall::new::( + RuntimeCosts::ReplyInput, + move |ctx: &mut CallerWrap| { + Self::reply_input_inner(ctx, offset, len, None, value_ptr) + }, + ) } - #[host(fallible, cost = RuntimeCosts::ReplyPushInput, err = ErrorBytes)] - pub fn reply_push_input( - ctx: &mut CallerWrap<'_, '_, Ext>, - gas: u64, + pub fn reply_input_wgas( offset: u32, len: u32, - ) -> Result<(u64, ()), HostError> { - ctx.ext_mut() - .reply_push_input(offset, len) - .map_err(Into::into) + gas_limit: u64, + value_ptr: u32, + ) -> impl SysCall { + FallibleSysCall::new::( + RuntimeCosts::ReplyInputWGas, + move |ctx: &mut CallerWrap| { + Self::reply_input_inner(ctx, offset, len, Some(gas_limit), value_ptr) + }, + ) } - #[allow(clippy::too_many_arguments)] - #[host(fallible, wgas, cost = RuntimeCosts::SendInput)] - pub fn send_input( - ctx: &mut CallerWrap<'_, '_, Ext>, - gas: u64, + pub fn reply_push_input(offset: u32, len: u32) -> impl SysCall { + FallibleSysCall::new::( + RuntimeCosts::ReplyPushInput, + move |ctx: &mut CallerWrap| { + ctx.ext_mut() + .reply_push_input(offset, len) + .map_err(Into::into) + }, + ) + } + + fn send_input_inner( + ctx: &mut CallerWrap, pid_value_ptr: u32, offset: u32, len: u32, + gas_limit: Option, delay: u32, - ) -> Result<(u64, ()), HostError> { - // Charge for `len` inside `send_push_input` + ) -> Result { let read_pid_value = ctx.manager.register_read_as(pid_value_ptr); let HashWithValue { hash: destination, value, } = ctx.read_as(read_pid_value)?; - let mut f = || { - let handle = ctx.ext_mut().send_init()?; - ctx.ext_mut().send_push_input(handle, offset, len)?; - ctx.ext_mut().send_commit( + let handle = ctx.ext_mut().send_init()?; + // Charge for `len` inside `send_push_input` + ctx.ext_mut().send_push_input(handle, offset, len)?; + + ctx.ext_mut() + .send_commit( handle, - HandlePacket::new(destination.into(), Default::default(), value), + HandlePacket::maybe_with_gas( + destination.into(), + Default::default(), + gas_limit, + value, + ), delay, ) - }; + .map_err(Into::into) + } - f().map_err(Into::into) + pub fn send_input(pid_value_ptr: u32, offset: u32, len: u32, delay: u32) -> impl SysCall { + FallibleSysCall::new::( + RuntimeCosts::SendInput, + move |ctx: &mut CallerWrap| { + Self::send_input_inner(ctx, pid_value_ptr, offset, len, None, delay) + }, + ) } - #[host(fallible, cost = RuntimeCosts::SendPushInput, err = ErrorBytes)] - pub fn send_push_input( - ctx: &mut CallerWrap<'_, '_, Ext>, - gas: u64, - handle: u32, + pub fn send_input_wgas( + pid_value_ptr: u32, offset: u32, len: u32, - ) -> Result<(u64, ()), HostError> { - ctx.ext_mut() - .send_push_input(handle, offset, len) - .map_err(Into::into) + gas_limit: u64, + delay: u32, + ) -> impl SysCall { + FallibleSysCall::new::( + RuntimeCosts::SendInputWGas, + move |ctx: &mut CallerWrap| { + Self::send_input_inner(ctx, pid_value_ptr, offset, len, Some(gas_limit), delay) + }, + ) } - #[host(cost = RuntimeCosts::Debug(data_len))] - pub fn debug( - ctx: &mut CallerWrap<'_, '_, Ext>, - gas: u64, - data_ptr: u32, - data_len: u32, - ) -> Result<(u64, ()), HostError> { - let read_data = ctx.manager.register_read(data_ptr, data_len); - let data: RuntimeBuffer = ctx - .read(read_data)? - .try_into() - .map_err(|RuntimeBufferSizeError| { - UnrecoverableMemoryError::RuntimeAllocOutOfBounds.into() - }) - .map_err(TrapExplanation::UnrecoverableExt)?; - - let s = String::from_utf8(data.into_vec()) - .map_err(|_err| UnrecoverableExecutionError::InvalidDebugString.into()) - .map_err(TrapExplanation::UnrecoverableExt)?; - ctx.ext_mut().debug(&s)?; + pub fn send_push_input(handle: u32, offset: u32, len: u32) -> impl SysCall { + FallibleSysCall::new::( + RuntimeCosts::SendPushInput, + move |ctx: &mut CallerWrap| { + ctx.ext_mut() + .send_push_input(handle, offset, len) + .map_err(Into::into) + }, + ) + } - Ok(()) + pub fn debug(data_ptr: u32, data_len: u32) -> impl SysCall { + InfallibleSysCall::new( + RuntimeCosts::Debug(data_len), + move |ctx: &mut CallerWrap| { + let read_data = ctx.manager.register_read(data_ptr, data_len); + let data: RuntimeBuffer = ctx + .read(read_data)? + .try_into() + .map_err(|RuntimeBufferSizeError| { + UnrecoverableMemoryError::RuntimeAllocOutOfBounds.into() + }) + .map_err(TrapExplanation::UnrecoverableExt)?; + + let s = String::from_utf8(data.into_vec()) + .map_err(|_err| UnrecoverableExecutionError::InvalidDebugString.into()) + .map_err(TrapExplanation::UnrecoverableExt)?; + ctx.ext_mut().debug(&s)?; + + Ok(()) + }, + ) } - #[host(cost = RuntimeCosts::Null)] - pub fn panic( - ctx: &mut CallerWrap<'_, '_, Ext>, - gas: u64, - data_ptr: u32, - data_len: u32, - ) -> Result<(u64, ()), HostError> { - let read_data = ctx.manager.register_read(data_ptr, data_len); - let data = ctx.read(read_data).unwrap_or_default(); + pub fn panic(data_ptr: u32, data_len: u32) -> impl SysCall { + InfallibleSysCall::new(RuntimeCosts::Null, move |ctx: &mut CallerWrap| { + let read_data = ctx.manager.register_read(data_ptr, data_len); + let data = ctx.read(read_data).unwrap_or_default(); - let s = String::from_utf8_lossy(&data).to_string(); + let s = String::from_utf8_lossy(&data).to_string(); - Err(ActorTerminationReason::Trap(TrapExplanation::Panic(s.into())).into()) + Err(ActorTerminationReason::Trap(TrapExplanation::Panic(s.into())).into()) + }) } - #[host(cost = RuntimeCosts::Null)] - pub fn oom_panic(ctx: &mut CallerWrap<'_, '_, Ext>, gas: u64) -> Result<(u64, ()), HostError> { - Err(ActorTerminationReason::Trap(TrapExplanation::ProgramAllocOutOfBounds).into()) + pub fn oom_panic() -> impl SysCall { + InfallibleSysCall::new(RuntimeCosts::Null, |_ctx: &mut CallerWrap| { + Err(ActorTerminationReason::Trap(TrapExplanation::ProgramAllocOutOfBounds).into()) + }) } - #[host(fallible, cost = RuntimeCosts::ReserveGas)] - pub fn reserve_gas( - ctx: &mut CallerWrap<'_, '_, Ext>, - gas: u64, - gas_value: u64, - duration: u32, - ) -> Result<(u64, ()), HostError> { - ctx.ext_mut() - .reserve_gas(gas_value, duration) - .map_err(Into::into) + pub fn reserve_gas(gas_value: u64, duration: u32) -> impl SysCall { + FallibleSysCall::new::( + RuntimeCosts::ReserveGas, + move |ctx: &mut CallerWrap| { + ctx.ext_mut() + .reserve_gas(gas_value, duration) + .map_err(Into::into) + }, + ) } - #[host(fallible, cost = RuntimeCosts::ReplyDeposit, err = ErrorBytes)] - pub fn reply_deposit( - ctx: &mut CallerWrap<'_, '_, Ext>, - gas: u64, - message_id_ptr: u32, - gas_value: u64, - ) -> Result<(u64, ()), HostError> { - let read_message_id = ctx.manager.register_read_decoded(message_id_ptr); - let message_id = ctx.read_decoded(read_message_id)?; - - ctx.ext_mut() - .reply_deposit(message_id, gas_value) - .map_err(Into::into) + pub fn reply_deposit(message_id_ptr: u32, gas_value: u64) -> impl SysCall { + FallibleSysCall::new::( + RuntimeCosts::ReplyDeposit, + move |ctx: &mut CallerWrap| { + let read_message_id = ctx.manager.register_read_decoded(message_id_ptr); + let message_id = ctx.read_decoded(read_message_id)?; + + ctx.ext_mut() + .reply_deposit(message_id, gas_value) + .map_err(Into::into) + }, + ) } - #[host(fallible, cost = RuntimeCosts::UnreserveGas, err = ErrorWithGas)] - pub fn unreserve_gas( - ctx: &mut CallerWrap<'_, '_, Ext>, - gas: u64, - reservation_id_ptr: u32, - ) -> Result<(u64, ()), HostError> { - let read_reservation_id = ctx.manager.register_read_decoded(reservation_id_ptr); - let reservation_id = ctx.read_decoded(read_reservation_id)?; - - ctx.ext_mut() - .unreserve_gas(reservation_id) - .map_err(Into::into) + pub fn unreserve_gas(reservation_id_ptr: u32) -> impl SysCall { + FallibleSysCall::new::( + RuntimeCosts::UnreserveGas, + move |ctx: &mut CallerWrap| { + let read_reservation_id = ctx.manager.register_read_decoded(reservation_id_ptr); + let reservation_id = ctx.read_decoded(read_reservation_id)?; + + ctx.ext_mut() + .unreserve_gas(reservation_id) + .map_err(Into::into) + }, + ) } - #[host(fallible, cost = RuntimeCosts::SystemReserveGas, err = ErrorBytes)] - pub fn system_reserve_gas( - ctx: &mut CallerWrap<'_, '_, Ext>, - gas: u64, - gas_value: u64, - ) -> Result<(u64, ()), HostError> { - ctx.ext_mut() - .system_reserve_gas(gas_value) - .map_err(Into::into) + pub fn system_reserve_gas(gas_value: u64) -> impl SysCall { + FallibleSysCall::new::( + RuntimeCosts::SystemReserveGas, + move |ctx: &mut CallerWrap| { + ctx.ext_mut() + .system_reserve_gas(gas_value) + .map_err(Into::into) + }, + ) } - #[host(cost = RuntimeCosts::GasAvailable)] - pub fn gas_available( - ctx: &mut CallerWrap<'_, '_, Ext>, - gas: u64, - gas_ptr: u32, - ) -> Result<(u64, ()), HostError> { - let gas_available = ctx.ext_mut().gas_available()?; - - let write_gas = ctx.manager.register_write_as(gas_ptr); - ctx.write_as(write_gas, gas_available.to_le_bytes()) - .map_err(Into::into) + pub fn gas_available(gas_ptr: u32) -> impl SysCall { + InfallibleSysCall::new( + RuntimeCosts::GasAvailable, + move |ctx: &mut CallerWrap| { + let gas_available = ctx.ext_mut().gas_available()?; + + let write_gas = ctx.manager.register_write_as(gas_ptr); + ctx.write_as(write_gas, gas_available.to_le_bytes()) + .map_err(Into::into) + }, + ) } - #[host(cost = RuntimeCosts::MsgId)] - pub fn message_id( - ctx: &mut CallerWrap<'_, '_, Ext>, - gas: u64, - message_id_ptr: u32, - ) -> Result<(u64, ()), HostError> { - let message_id = ctx.ext_mut().message_id()?; + pub fn message_id(message_id_ptr: u32) -> impl SysCall { + InfallibleSysCall::new(RuntimeCosts::MsgId, move |ctx: &mut CallerWrap| { + let message_id = ctx.ext_mut().message_id()?; - let write_message_id = ctx.manager.register_write_as(message_id_ptr); - ctx.write_as(write_message_id, message_id.into_bytes()) - .map_err(Into::into) + let write_message_id = ctx.manager.register_write_as(message_id_ptr); + ctx.write_as(write_message_id, message_id.into_bytes()) + .map_err(Into::into) + }) } - #[host(cost = RuntimeCosts::ProgramId)] - pub fn program_id( - ctx: &mut CallerWrap<'_, '_, Ext>, - gas: u64, - program_id_ptr: u32, - ) -> Result<(u64, ()), HostError> { - let program_id = ctx.ext_mut().program_id()?; + pub fn program_id(program_id_ptr: u32) -> impl SysCall { + InfallibleSysCall::new(RuntimeCosts::ProgramId, move |ctx: &mut CallerWrap| { + let program_id = ctx.ext_mut().program_id()?; - let write_program_id = ctx.manager.register_write_as(program_id_ptr); - ctx.write_as(write_program_id, program_id.into_bytes()) - .map_err(Into::into) + let write_program_id = ctx.manager.register_write_as(program_id_ptr); + ctx.write_as(write_program_id, program_id.into_bytes()) + .map_err(Into::into) + }) } - #[host(fallible, cost = RuntimeCosts::PayProgramRent, err = ErrorWithBlockNumberAndValue)] - pub fn pay_program_rent( - ctx: &mut CallerWrap<'_, '_, Ext>, - gas: u64, - rent_pid_ptr: u32, - ) -> Result<(u64, ()), HostError> { - let read_rent_pid = ctx.manager.register_read_as(rent_pid_ptr); - - let HashWithValue { - hash: program_id, - value: rent, - } = ctx.read_as(read_rent_pid)?; - - ctx.ext_mut() - .pay_program_rent(program_id.into(), rent) - .map_err(Into::into) + pub fn pay_program_rent(rent_pid_ptr: u32) -> impl SysCall { + FallibleSysCall::new::( + RuntimeCosts::PayProgramRent, + move |ctx: &mut CallerWrap| { + let read_rent_pid = ctx.manager.register_read_as(rent_pid_ptr); + + let HashWithValue { + hash: program_id, + value: rent, + } = ctx.read_as(read_rent_pid)?; + + ctx.ext_mut() + .pay_program_rent(program_id.into(), rent) + .map_err(Into::into) + }, + ) } - #[host(cost = RuntimeCosts::Source)] - pub fn source( - ctx: &mut CallerWrap<'_, '_, Ext>, - gas: u64, - source_ptr: u32, - ) -> Result<(u64, ()), HostError> { - let source = ctx.ext_mut().source()?; + pub fn source(source_ptr: u32) -> impl SysCall { + InfallibleSysCall::new(RuntimeCosts::Source, move |ctx: &mut CallerWrap| { + let source = ctx.ext_mut().source()?; - let write_source = ctx.manager.register_write_as(source_ptr); - ctx.write_as(write_source, source.into_bytes()) - .map_err(Into::into) + let write_source = ctx.manager.register_write_as(source_ptr); + ctx.write_as(write_source, source.into_bytes()) + .map_err(Into::into) + }) } - #[host(cost = RuntimeCosts::Value)] - pub fn value( - ctx: &mut CallerWrap<'_, '_, Ext>, - gas: u64, - value_ptr: u32, - ) -> Result<(u64, ()), HostError> { - let value = ctx.ext_mut().value()?; + pub fn value(value_ptr: u32) -> impl SysCall { + InfallibleSysCall::new(RuntimeCosts::Value, move |ctx: &mut CallerWrap| { + let value = ctx.ext_mut().value()?; - let write_value = ctx.manager.register_write_as(value_ptr); - ctx.write_as(write_value, value.to_le_bytes()) - .map_err(Into::into) + let write_value = ctx.manager.register_write_as(value_ptr); + ctx.write_as(write_value, value.to_le_bytes()) + .map_err(Into::into) + }) } - #[host(cost = RuntimeCosts::ValueAvailable)] - pub fn value_available( - ctx: &mut CallerWrap<'_, '_, Ext>, - gas: u64, - value_ptr: u32, - ) -> Result<(u64, ()), HostError> { - let value_available = ctx.ext_mut().value_available()?; - - let write_value = ctx.manager.register_write_as(value_ptr); - ctx.write_as(write_value, value_available.to_le_bytes()) - .map_err(Into::into) + pub fn value_available(value_ptr: u32) -> impl SysCall { + InfallibleSysCall::new( + RuntimeCosts::ValueAvailable, + move |ctx: &mut CallerWrap| { + let value_available = ctx.ext_mut().value_available()?; + + let write_value = ctx.manager.register_write_as(value_ptr); + ctx.write_as(write_value, value_available.to_le_bytes()) + .map_err(Into::into) + }, + ) } - #[host(cost = RuntimeCosts::Leave)] - pub fn leave(ctx: &mut CallerWrap<'_, '_, Ext>, gas: u64) -> Result<(u64, ()), HostError> { - Err(ActorTerminationReason::Leave.into()) + pub fn leave() -> impl SysCall { + InfallibleSysCall::new(RuntimeCosts::Leave, move |_ctx: &mut CallerWrap| { + Err(ActorTerminationReason::Leave.into()) + }) } - #[host(cost = RuntimeCosts::Wait)] - pub fn wait(ctx: &mut CallerWrap<'_, '_, Ext>, gas: u64) -> Result<(u64, ()), HostError> { - ctx.ext_mut().wait()?; - Err(ActorTerminationReason::Wait(None, MessageWaitedType::Wait).into()) + pub fn wait() -> impl SysCall { + InfallibleSysCall::new(RuntimeCosts::Wait, move |ctx: &mut CallerWrap| { + ctx.ext_mut().wait()?; + Err(ActorTerminationReason::Wait(None, MessageWaitedType::Wait).into()) + }) } - #[host(cost = RuntimeCosts::WaitFor)] - pub fn wait_for( - ctx: &mut CallerWrap<'_, '_, Ext>, - gas: u64, - duration: u32, - ) -> Result<(u64, ()), HostError> { - ctx.ext_mut().wait_for(duration)?; - Err(ActorTerminationReason::Wait(Some(duration), MessageWaitedType::WaitFor).into()) + pub fn wait_for(duration: u32) -> impl SysCall { + InfallibleSysCall::new(RuntimeCosts::WaitFor, move |ctx: &mut CallerWrap| { + ctx.ext_mut().wait_for(duration)?; + Err(ActorTerminationReason::Wait(Some(duration), MessageWaitedType::WaitFor).into()) + }) } - #[host(cost = RuntimeCosts::WaitUpTo)] - pub fn wait_up_to( - ctx: &mut CallerWrap<'_, '_, Ext>, - gas: u64, - duration: u32, - ) -> Result<(u64, ()), HostError> { - let waited_type = if ctx.ext_mut().wait_up_to(duration)? { - MessageWaitedType::WaitUpToFull - } else { - MessageWaitedType::WaitUpTo - }; - Err(ActorTerminationReason::Wait(Some(duration), waited_type).into()) + pub fn wait_up_to(duration: u32) -> impl SysCall { + InfallibleSysCall::new(RuntimeCosts::WaitUpTo, move |ctx: &mut CallerWrap| { + let waited_type = if ctx.ext_mut().wait_up_to(duration)? { + MessageWaitedType::WaitUpToFull + } else { + MessageWaitedType::WaitUpTo + }; + Err(ActorTerminationReason::Wait(Some(duration), waited_type).into()) + }) } - #[host(fallible, cost = RuntimeCosts::Wake, err = ErrorBytes)] - pub fn wake( - ctx: &mut CallerWrap<'_, '_, Ext>, - gas: u64, - message_id_ptr: u32, - delay: u32, - ) -> Result<(u64, ()), HostError> { - let read_message_id = ctx.manager.register_read_decoded(message_id_ptr); - let message_id = ctx.read_decoded(read_message_id)?; + pub fn wake(message_id_ptr: u32, delay: u32) -> impl SysCall { + FallibleSysCall::new::(RuntimeCosts::Wake, move |ctx: &mut CallerWrap| { + let read_message_id = ctx.manager.register_read_decoded(message_id_ptr); + let message_id = ctx.read_decoded(read_message_id)?; - ctx.ext_mut().wake(message_id, delay).map_err(Into::into) + ctx.ext_mut().wake(message_id, delay).map_err(Into::into) + }) } #[allow(clippy::too_many_arguments)] - #[host(fallible, wgas, cost = RuntimeCosts::CreateProgram(payload_len, salt_len), err = ErrorWithTwoHashes)] - pub fn create_program( - ctx: &mut CallerWrap<'_, '_, Ext>, - gas: u64, + fn create_program_inner( + ctx: &mut CallerWrap, cid_value_ptr: u32, salt_ptr: u32, salt_len: u32, payload_ptr: u32, payload_len: u32, + gas_limit: Option, delay: u32, - ) -> Result<(u64, ()), HostError> { + ) -> Result<(MessageId, ProgramId), RunFallibleError> { let read_cid_value = ctx.manager.register_read_as(cid_value_ptr); let read_salt = ctx.manager.register_read(salt_ptr, salt_len); let read_payload = ctx.manager.register_read(payload_ptr, payload_len); @@ -840,39 +1216,92 @@ where ctx.ext_mut() .create_program( - InitPacket::new(code_id.into(), salt, payload, Some(message_id), value), + InitPacket::new_from_program( + code_id.into(), + salt, + payload, + message_id, + gas_limit, + value, + ), delay, ) .map_err(Into::into) } - pub fn forbidden(ctx: &mut CallerWrap<'_, '_, Ext>, gas: u64) -> Result<(u64, ()), HostError> { - syscall_trace!("forbidden"); + pub fn create_program( + cid_value_ptr: u32, + salt_ptr: u32, + salt_len: u32, + payload_ptr: u32, + payload_len: u32, + delay: u32, + ) -> impl SysCall { + FallibleSysCall::new::( + RuntimeCosts::CreateProgram(payload_len, salt_len), + move |ctx: &mut CallerWrap| -> Result<_, RunFallibleError> { + Self::create_program_inner( + ctx, + cid_value_ptr, + salt_ptr, + salt_len, + payload_ptr, + payload_len, + None, + delay, + ) + }, + ) + } + + pub fn create_program_wgas( + cid_value_ptr: u32, + salt_ptr: u32, + salt_len: u32, + payload_ptr: u32, + payload_len: u32, + gas_limit: u64, + delay: u32, + ) -> impl SysCall { + FallibleSysCall::new::( + RuntimeCosts::CreateProgramWGas(payload_len, salt_len), + move |ctx: &mut CallerWrap| { + Self::create_program_inner( + ctx, + cid_value_ptr, + salt_ptr, + salt_len, + payload_ptr, + payload_len, + Some(gas_limit), + delay, + ) + }, + ) + } - ctx.run_any(gas, RuntimeCosts::Null, |_| { + pub fn forbidden(_args: &[Value]) -> impl SysCall { + InfallibleSysCall::new(RuntimeCosts::Null, |_: &mut CallerWrap| { Err(ActorTerminationReason::Trap(TrapExplanation::ForbiddenFunction).into()) }) } - pub fn out_of_gas( - ctx: &mut CallerWrap<'_, '_, Ext>, - _gas: u64, - ) -> Result<(u64, ()), HostError> { - syscall_trace!("out_of_gas"); - - let ext = ctx.ext_mut(); - let current_counter = ext.current_counter_type(); - log::trace!(target: "syscalls", "[out_of_gas] Current counter in global represents {current_counter:?}"); - - if current_counter == CounterType::GasAllowance { - // We manually decrease it to 0 because global won't be affected - // since it didn't pass comparison to argument of `gas_charge()` - ext.decrease_current_counter_to(0); - } + pub fn out_of_gas(_gas: Gas) -> impl SysCall { + RawSysCall::new(|ctx: &mut CallerWrap| { + let ext = ctx.ext_mut(); + let current_counter = ext.current_counter_type(); + log::trace!(target: "syscalls", "[out_of_gas] Current counter in global represents {current_counter:?}"); - let termination_reason: ActorTerminationReason = current_counter.into(); + if current_counter == CounterType::GasAllowance { + // We manually decrease it to 0 because global won't be affected + // since it didn't pass comparison to argument of `gas_charge()` + ext.decrease_current_counter_to(0); + } - ctx.set_termination_reason(termination_reason.into()); - Err(HostError) + let termination_reason: ActorTerminationReason = current_counter.into(); + + ctx.set_termination_reason(termination_reason.into()); + Err(HostError) + }) } } diff --git a/core-backend/src/lib.rs b/core-backend/src/lib.rs index 1a20d9bae1f..601e2b843f8 100644 --- a/core-backend/src/lib.rs +++ b/core-backend/src/lib.rs @@ -25,6 +25,7 @@ extern crate alloc; pub mod env; pub mod error; mod funcs; +mod log; pub mod memory; #[cfg(any(feature = "mock", test))] pub mod mock; diff --git a/core-backend/src/log.rs b/core-backend/src/log.rs new file mode 100644 index 00000000000..a5203908f7c --- /dev/null +++ b/core-backend/src/log.rs @@ -0,0 +1,61 @@ +// This file is part of Gear. + +// Copyright (C) 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 . + +use core::fmt; +use gear_sandbox::Value; + +struct ValueFormatter<'a>(&'a Value); + +impl fmt::Display for ValueFormatter<'_> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self.0 { + Value::I32(i32) => fmt::Display::fmt(i32, f), + Value::I64(i64) => fmt::Display::fmt(i64, f), + Value::F32(f32) => fmt::Display::fmt(f32, f), + Value::F64(f64) => fmt::Display::fmt(f64, f), + } + } +} + +struct ArgsFormatter<'a>(&'a [Value]); + +impl fmt::Display for ArgsFormatter<'_> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let mut iter = self.0.iter(); + + if let Some(value) = iter.next() { + write!(f, "{}", ValueFormatter(value))?; + } + + for value in iter { + write!(f, ", {}", ValueFormatter(value))?; + } + + Ok(()) + } +} + +fn function_name() -> &'static str { + let s = core::any::type_name::(); + let pos = s.rfind("::").unwrap(); + &s[pos + 2..] +} + +pub fn trace_syscall(args: &[Value]) { + log::trace!(target: "syscalls", "{}({})", function_name::(), ArgsFormatter(args)); +} diff --git a/core-backend/src/runtime.rs b/core-backend/src/runtime.rs index c5ee4388ab3..c59d4d50b9f 100644 --- a/core-backend/src/runtime.rs +++ b/core-backend/src/runtime.rs @@ -131,15 +131,13 @@ impl<'a, 'b, Ext: BackendExternalities + 'static> CallerWrap<'a, 'b, Ext> { } #[track_caller] - pub fn prepare( - caller: &'a mut Caller<'b, HostState>, - ) -> Result { + pub fn prepare(caller: &'a mut Caller<'b, HostState>) -> Self { let memory = caller_host_state_mut(caller).memory.clone(); - Ok(Self { + Self { caller, manager: Default::default(), memory, - }) + } } #[track_caller] diff --git a/core/src/message/handle.rs b/core/src/message/handle.rs index 5070f11ed82..92b88985469 100644 --- a/core/src/message/handle.rs +++ b/core/src/message/handle.rs @@ -153,6 +153,19 @@ impl HandlePacket { } } + /// Create new packet with optional gas. + pub fn maybe_with_gas( + destination: ProgramId, + payload: Payload, + gas_limit: Option, + value: Value, + ) -> Self { + match gas_limit { + None => Self::new(destination, payload, value), + Some(gas_limit) => Self::new_with_gas(destination, payload, gas_limit, value), + } + } + /// Prepend payload. pub(super) fn try_prepend(&mut self, data: Payload) -> Result<(), PayloadSizeError> { self.payload.try_prepend(data) diff --git a/core/src/message/init.rs b/core/src/message/init.rs index 9aec00466e7..fee255a2573 100644 --- a/core/src/message/init.rs +++ b/core/src/message/init.rs @@ -131,50 +131,40 @@ pub struct InitPacket { } impl InitPacket { - /// Create new InitPacket without gas. - pub fn new( + /// Create new InitPacket via user. + pub fn new_from_user( code_id: CodeId, salt: Salt, payload: Payload, - message_id: Option, + gas_limit: GasLimit, value: Value, ) -> Self { - let program_id = if let Some(id) = message_id { - ProgramId::generate_from_program(code_id, salt.inner(), id) - } else { - ProgramId::generate_from_user(code_id, salt.inner()) - }; Self { - program_id, + program_id: ProgramId::generate_from_user(code_id, salt.inner()), code_id, salt, payload, + gas_limit: Some(gas_limit), value, - gas_limit: None, } } - /// Create new InitPacket with gas. - pub fn new_with_gas( + /// Create new InitPacket via program. + pub fn new_from_program( code_id: CodeId, salt: Salt, payload: Payload, - message_id: Option, - gas_limit: GasLimit, + message_id: MessageId, + gas_limit: Option, value: Value, ) -> Self { - let program_id = if let Some(id) = message_id { - ProgramId::generate_from_program(code_id, salt.inner(), id) - } else { - ProgramId::generate_from_user(code_id, salt.inner()) - }; Self { - program_id, + program_id: ProgramId::generate_from_program(code_id, salt.inner(), message_id), code_id, salt, payload, + gas_limit, value, - gas_limit: Some(gas_limit), } } diff --git a/core/src/message/reply.rs b/core/src/message/reply.rs index bcfb04cd1b3..9b647fa0568 100644 --- a/core/src/message/reply.rs +++ b/core/src/message/reply.rs @@ -193,6 +193,14 @@ impl ReplyPacket { } } + /// Create new manual ReplyPacket with optional gas. + pub fn maybe_with_gas(payload: Payload, gas_limit: Option, value: Value) -> Self { + match gas_limit { + None => Self::new(payload, value), + Some(gas_limit) => Self::new_with_gas(payload, gas_limit, value), + } + } + // TODO: consider using here `impl CoreError` and/or provide `AsStatusCode` // trait or append such functionality to `CoreError` (issue #1083). /// Create new system generated ReplyPacket. diff --git a/pallets/gear/src/lib.rs b/pallets/gear/src/lib.rs index 69263cfe5ec..15321032968 100644 --- a/pallets/gear/src/lib.rs +++ b/pallets/gear/src/lib.rs @@ -573,14 +573,13 @@ pub mod pallet { let code_and_id = CodeAndId::new(code); let code_info = CodeInfo::from_code_and_id(&code_and_id); - let packet = InitPacket::new_with_gas( + let packet = InitPacket::new_from_user( code_and_id.code_id(), salt.try_into() .map_err(|err: PayloadSizeError| DispatchError::Other(err.into()))?, init_payload .try_into() .map_err(|err: PayloadSizeError| DispatchError::Other(err.into()))?, - None, gas_limit, value.unique_saturated_into(), ); @@ -1176,14 +1175,13 @@ pub mod pallet { value: BalanceOf, keep_alive: bool, ) -> Result { - let packet = InitPacket::new_with_gas( + let packet = InitPacket::new_from_user( code_id, salt.try_into() .map_err(|err: PayloadSizeError| DispatchError::Other(err.into()))?, init_payload .try_into() .map_err(|err: PayloadSizeError| DispatchError::Other(err.into()))?, - None, gas_limit, value.unique_saturated_into(), );