#[allow(unused_macros)]
macro_rules! call_with_name {
( $M:ident => @in $mod:ident : $func:ident < [ $( $arg_name:ident : $arg_type:ident ),* ] -> [ $( $returns:ident ),* ] > ) => {
$M!($mod / $func : $func < [ $( $arg_name : $arg_type ),* ] -> [ $( $returns ),* ] >)
};
( $M:ident => @as $name:ident : $func:ident < [ $( $arg_name:ident : $arg_type:ident ),* ] -> [ $( $returns:ident ),* ] > ) => {
$M!(env / $name : $func < [ $( $arg_name : $arg_type ),* ] -> [ $( $returns ),* ] >)
};
( $M:ident => $func:ident < [ $( $arg_name:ident : $arg_type:ident ),* ] -> [ $( $returns:ident ),* ] > ) => {
$M!(env / $func : $func < [ $( $arg_name : $arg_type ),* ] -> [ $( $returns ),* ] >)
};
}
macro_rules! imports {
(
$($(#[$config_field:ident])? $(##[$feature_name:literal])?
$( @in $mod:ident : )?
$( @as $name:ident : )?
$func:ident < [ $( $arg_name:ident : $arg_type:ident ),* ] -> [ $( $returns:ident ),* ] >,)*
) => {
#[allow(unused_macros)]
macro_rules! for_each_available_import {
($config:expr, $M:ident) => {$(
$(#[cfg(feature = $feature_name)])?
if true $(&& ($config).$config_field)? {
call_with_name!($M => $( @in $mod : )? $( @as $name : )? $func < [ $( $arg_name : $arg_type ),* ] -> [ $( $returns ),* ] >);
}
)*}
}
}
}
imports! {
@in internal: finite_wasm_gas<[gas: u64] -> []>,
@in internal: finite_wasm_stack<[operand_size: u64, frame_size: u64] -> []>,
@in internal: finite_wasm_unstack<[operand_size: u64, frame_size: u64] -> []>,
read_register<[register_id: u64, ptr: u64] -> []>,
register_len<[register_id: u64] -> [u64]>,
write_register<[register_id: u64, data_len: u64, data_ptr: u64] -> []>,
current_account_id<[register_id: u64] -> []>,
signer_account_id<[register_id: u64] -> []>,
signer_account_pk<[register_id: u64] -> []>,
predecessor_account_id<[register_id: u64] -> []>,
input<[register_id: u64] -> []>,
block_index<[] -> [u64]>,
block_timestamp<[] -> [u64]>,
epoch_height<[] -> [u64]>,
storage_usage<[] -> [u64]>,
account_balance<[balance_ptr: u64] -> []>,
account_locked_balance<[balance_ptr: u64] -> []>,
attached_deposit<[balance_ptr: u64] -> []>,
prepaid_gas<[] -> [u64]>,
used_gas<[] -> [u64]>,
random_seed<[register_id: u64] -> []>,
sha256<[value_len: u64, value_ptr: u64, register_id: u64] -> []>,
keccak256<[value_len: u64, value_ptr: u64, register_id: u64] -> []>,
keccak512<[value_len: u64, value_ptr: u64, register_id: u64] -> []>,
#[ed25519_verify] ed25519_verify<[sig_len: u64,
sig_ptr: u64,
msg_len: u64,
msg_ptr: u64,
pub_key_len: u64,
pub_key_ptr: u64
] -> [u64]>,
#[math_extension] ripemd160<[value_len: u64, value_ptr: u64, register_id: u64] -> []>,
#[math_extension] ecrecover<[hash_len: u64, hash_ptr: u64, sign_len: u64, sig_ptr: u64, v: u64, malleability_flag: u64, register_id: u64] -> [u64]>,
value_return<[value_len: u64, value_ptr: u64] -> []>,
panic<[] -> []>,
panic_utf8<[len: u64, ptr: u64] -> []>,
log_utf8<[len: u64, ptr: u64] -> []>,
log_utf16<[len: u64, ptr: u64] -> []>,
abort<[msg_ptr: u32, filename_ptr: u32, line: u32, col: u32] -> []>,
promise_create<[
account_id_len: u64,
account_id_ptr: u64,
method_name_len: u64,
method_name_ptr: u64,
arguments_len: u64,
arguments_ptr: u64,
amount_ptr: u64,
gas: u64
] -> [u64]>,
promise_then<[
promise_index: u64,
account_id_len: u64,
account_id_ptr: u64,
method_name_len: u64,
method_name_ptr: u64,
arguments_len: u64,
arguments_ptr: u64,
amount_ptr: u64,
gas: u64
] -> [u64]>,
promise_and<[promise_idx_ptr: u64, promise_idx_count: u64] -> [u64]>,
promise_batch_create<[account_id_len: u64, account_id_ptr: u64] -> [u64]>,
promise_batch_then<[promise_index: u64, account_id_len: u64, account_id_ptr: u64] -> [u64]>,
promise_batch_action_create_account<[promise_index: u64] -> []>,
promise_batch_action_deploy_contract<[promise_index: u64, code_len: u64, code_ptr: u64] -> []>,
promise_batch_action_function_call<[
promise_index: u64,
method_name_len: u64,
method_name_ptr: u64,
arguments_len: u64,
arguments_ptr: u64,
amount_ptr: u64,
gas: u64
] -> []>,
#[function_call_weight] promise_batch_action_function_call_weight<[
promise_index: u64,
method_name_len: u64,
method_name_ptr: u64,
arguments_len: u64,
arguments_ptr: u64,
amount_ptr: u64,
gas: u64,
gas_weight: u64
] -> []>,
promise_batch_action_transfer<[promise_index: u64, amount_ptr: u64] -> []>,
promise_batch_action_stake<[
promise_index: u64,
amount_ptr: u64,
public_key_len: u64,
public_key_ptr: u64
] -> []>,
promise_batch_action_add_key_with_full_access<[
promise_index: u64,
public_key_len: u64,
public_key_ptr: u64,
nonce: u64
] -> []>,
promise_batch_action_add_key_with_function_call<[
promise_index: u64,
public_key_len: u64,
public_key_ptr: u64,
nonce: u64,
allowance_ptr: u64,
receiver_id_len: u64,
receiver_id_ptr: u64,
method_names_len: u64,
method_names_ptr: u64
] -> []>,
promise_batch_action_delete_key<[
promise_index: u64,
public_key_len: u64,
public_key_ptr: u64
] -> []>,
promise_batch_action_delete_account<[
promise_index: u64,
beneficiary_id_len: u64,
beneficiary_id_ptr: u64
] -> []>,
promise_results_count<[] -> [u64]>,
promise_result<[result_idx: u64, register_id: u64] -> [u64]>,
promise_return<[promise_idx: u64] -> []>,
storage_write<[key_len: u64, key_ptr: u64, value_len: u64, value_ptr: u64, register_id: u64] -> [u64]>,
storage_read<[key_len: u64, key_ptr: u64, register_id: u64] -> [u64]>,
storage_remove<[key_len: u64, key_ptr: u64, register_id: u64] -> [u64]>,
storage_has_key<[key_len: u64, key_ptr: u64] -> [u64]>,
storage_iter_prefix<[prefix_len: u64, prefix_ptr: u64] -> [u64]>,
storage_iter_range<[start_len: u64, start_ptr: u64, end_len: u64, end_ptr: u64] -> [u64]>,
storage_iter_next<[iterator_id: u64, key_register_id: u64, value_register_id: u64] -> [u64]>,
@as gas: gas_seen_from_wasm<[gas_amount: u32] -> []>,
validator_stake<[account_id_len: u64, account_id_ptr: u64, stake_ptr: u64] -> []>,
validator_total_stake<[stake_ptr: u64] -> []>,
validator_power<[account_id_len: u64, account_id_ptr: u64, power_ptr: u64] -> []>,
validator_total_power<[power_ptr: u64] -> []>,
#[alt_bn128] alt_bn128_g1_multiexp<[value_len: u64, value_ptr: u64, register_id: u64] -> []>,
#[alt_bn128] alt_bn128_g1_sum<[value_len: u64, value_ptr: u64, register_id: u64] -> []>,
#[alt_bn128] alt_bn128_pairing_check<[value_len: u64, value_ptr: u64] -> [u64]>,
##["sandbox"] sandbox_debug_log<[len: u64, ptr: u64] -> []>,
}
#[cfg(all(feature = "wasmer0_vm", target_arch = "x86_64"))]
pub(crate) mod wasmer {
use super::str_eq;
use crate::logic::{VMLogic, VMLogicError};
use std::ffi::c_void;
#[derive(Clone, Copy)]
struct ImportReference(pub *mut c_void);
unsafe impl Send for ImportReference {}
unsafe impl Sync for ImportReference {}
pub(crate) fn build(
memory: wasmer_runtime::memory::Memory,
logic: &mut VMLogic<'_>,
) -> wasmer_runtime::ImportObject {
let raw_ptr = logic as *mut _ as *mut c_void;
let import_reference = ImportReference(raw_ptr);
let mut import_object = wasmer_runtime::ImportObject::new_with_data(move || {
let dtor = (|_: *mut c_void| {}) as fn(*mut c_void);
({ import_reference }.0, dtor)
});
let mut ns_internal = wasmer_runtime_core::import::Namespace::new();
let mut ns_env = wasmer_runtime_core::import::Namespace::new();
ns_env.insert("memory", memory);
macro_rules! add_import {
(
$mod:ident / $name:ident : $func:ident < [ $( $arg_name:ident : $arg_type:ident ),* ] -> [ $( $returns:ident ),* ] >
) => {
#[allow(unused_parens)]
fn $name( ctx: &mut wasmer_runtime::Ctx, $( $arg_name: $arg_type ),* ) -> Result<($( $returns ),*), VMLogicError> {
const IS_GAS: bool = str_eq(stringify!($name), "gas") || str_eq(stringify!($name), "finite_wasm_gas");
let _span = if IS_GAS {
None
} else {
Some(tracing::trace_span!(target: "host-function", stringify!($name)).entered())
};
let logic: &mut VMLogic<'_> = unsafe { &mut *(ctx.data as *mut VMLogic<'_>) };
logic.$func( $( $arg_name, )* )
}
match stringify!($mod) {
"env" => ns_env.insert(stringify!($name), wasmer_runtime::func!($name)),
"internal" => ns_internal.insert(stringify!($name), wasmer_runtime::func!($name)),
_ => unimplemented!(),
}
};
}
for_each_available_import!(logic.config, add_import);
import_object.register("env", ns_env);
import_object.register("internal", ns_internal);
import_object
}
}
#[cfg(all(feature = "wasmer2_vm", target_arch = "x86_64"))]
pub(crate) mod wasmer2 {
use std::sync::Arc;
use super::str_eq;
use crate::logic::VMLogic;
use wasmer_engine::Engine;
use wasmer_engine_universal::UniversalEngine;
use wasmer_vm::{
ExportFunction, ExportFunctionMetadata, Resolver, VMFunction, VMFunctionKind, VMMemory,
};
pub(crate) struct Wasmer2Imports<'engine, 'vmlogic, 'vmlogic_refs> {
pub(crate) memory: VMMemory,
pub(crate) vmlogic: &'vmlogic mut VMLogic<'vmlogic_refs>,
pub(crate) metadata: Arc<ExportFunctionMetadata>,
pub(crate) engine: &'engine UniversalEngine,
}
trait Wasmer2Type {
type Wasmer;
fn to_wasmer(self) -> Self::Wasmer;
fn ty() -> wasmer_types::Type;
}
macro_rules! wasmer_types {
($($native:ty as $wasmer:ty => $type_expr:expr;)*) => {
$(impl Wasmer2Type for $native {
type Wasmer = $wasmer;
fn to_wasmer(self) -> $wasmer {
self as _
}
fn ty() -> wasmer_types::Type {
$type_expr
}
})*
}
}
wasmer_types! {
u32 as i32 => wasmer_types::Type::I32;
u64 as i64 => wasmer_types::Type::I64;
}
macro_rules! return_ty {
($return_type: ident = [ ]) => {
type $return_type = ();
fn make_ret() -> () {}
};
($return_type: ident = [ $($returns: ident),* ]) => {
#[repr(C)]
struct $return_type($(<$returns as Wasmer2Type>::Wasmer),*);
fn make_ret($($returns: $returns),*) -> Ret { Ret($($returns.to_wasmer()),*) }
}
}
impl<'e, 'l, 'lr> Resolver for Wasmer2Imports<'e, 'l, 'lr> {
fn resolve(&self, _index: u32, module: &str, field: &str) -> Option<wasmer_vm::Export> {
if module == "env" && field == "memory" {
return Some(wasmer_vm::Export::Memory(self.memory.clone()));
}
macro_rules! add_import {
(
$mod:ident / $name:ident : $func:ident <
[ $( $arg_name:ident : $arg_type:ident ),* ]
-> [ $( $returns:ident ),* ]
>
) => {
return_ty!(Ret = [ $($returns),* ]);
extern "C" fn $name(env: *mut VMLogic<'_>, $( $arg_name: $arg_type ),* )
-> Ret {
let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
const IS_GAS: bool = str_eq(stringify!($name), "gas") || str_eq(stringify!($name), "finite_wasm_gas");
let _span = if IS_GAS {
None
} else {
Some(tracing::trace_span!(
target: "host-function",
stringify!($name)
).entered())
};
unsafe { (*env).$func( $( $arg_name, )* ) }
}));
let result: Result<Result<_, crate::logic::VMLogicError>, _> = result;
#[allow(unused_parens)]
match result {
Ok(Ok(($($returns),*))) => make_ret($($returns),*),
Ok(Err(trap)) => unsafe {
wasmer_vm::raise_user_trap(Box::new(trap))
},
Err(e) => unsafe {
wasmer_vm::resume_panic(e)
},
}
}
if module == stringify!($mod) && field == stringify!($name) {
let args = [$(<$arg_type as Wasmer2Type>::ty()),*];
let rets = [$(<$returns as Wasmer2Type>::ty()),*];
let signature = wasmer_types::FunctionTypeRef::new(&args[..], &rets[..]);
let signature = self.engine.register_signature(signature);
return Some(wasmer_vm::Export::Function(ExportFunction {
vm_function: VMFunction {
address: $name as *const _,
vmctx: wasmer_vm::VMFunctionEnvironment {
host_env: self.vmlogic as *const _ as *mut _
},
signature,
kind: VMFunctionKind::Static,
call_trampoline: None,
instance_ref: None,
},
metadata: Some(Arc::clone(&self.metadata)),
}));
}
};
}
for_each_available_import!(self.vmlogic.config, add_import);
return None;
}
}
pub(crate) fn build<'e, 'a, 'b>(
memory: VMMemory,
logic: &'a mut VMLogic<'b>,
engine: &'e UniversalEngine,
) -> Wasmer2Imports<'e, 'a, 'b> {
let metadata = unsafe {
ExportFunctionMetadata::new(logic as *mut _ as *mut _, None, |ptr| ptr, |_| {})
};
Wasmer2Imports { memory, vmlogic: logic, metadata: Arc::new(metadata), engine }
}
}
#[cfg(all(feature = "unc_vm", target_arch = "x86_64"))]
pub(crate) mod unc_vm {
use std::sync::Arc;
use super::str_eq;
use crate::logic::VMLogic;
use unc_vm_engine::universal::UniversalEngine;
use unc_vm_vm::{
ExportFunction, ExportFunctionMetadata, Resolver, VMFunction, VMFunctionKind, VMMemory,
};
pub(crate) struct UncVmImports<'engine, 'vmlogic, 'vmlogic_refs> {
pub(crate) memory: VMMemory,
pub(crate) vmlogic: &'vmlogic mut VMLogic<'vmlogic_refs>,
pub(crate) metadata: Arc<ExportFunctionMetadata>,
pub(crate) engine: &'engine UniversalEngine,
}
trait UncVmType {
type UncVm;
fn to_unc_vm(self) -> Self::UncVm;
fn ty() -> unc_vm_types::Type;
}
macro_rules! unc_vm_types {
($($native:ty as $unc_vm:ty => $type_expr:expr;)*) => {
$(impl UncVmType for $native {
type UncVm = $unc_vm;
fn to_unc_vm(self) -> $unc_vm {
self as _
}
fn ty() -> unc_vm_types::Type {
$type_expr
}
})*
}
}
unc_vm_types! {
u32 as i32 => unc_vm_types::Type::I32;
u64 as i64 => unc_vm_types::Type::I64;
}
macro_rules! return_ty {
($return_type: ident = [ ]) => {
type $return_type = ();
fn make_ret() -> () {}
};
($return_type: ident = [ $($returns: ident),* ]) => {
#[repr(C)]
struct $return_type($(<$returns as UncVmType>::UncVm),*);
fn make_ret($($returns: $returns),*) -> Ret { Ret($($returns.to_unc_vm()),*) }
}
}
impl<'e, 'l, 'lr> Resolver for UncVmImports<'e, 'l, 'lr> {
fn resolve(&self, _index: u32, module: &str, field: &str) -> Option<unc_vm_vm::Export> {
if module == "env" && field == "memory" {
return Some(unc_vm_vm::Export::Memory(self.memory.clone()));
}
macro_rules! add_import {
(
$mod:ident / $name:ident : $func:ident <
[ $( $arg_name:ident : $arg_type:ident ),* ]
-> [ $( $returns:ident ),* ]
>
) => {
return_ty!(Ret = [ $($returns),* ]);
extern "C" fn $name(env: *mut VMLogic<'_>, $( $arg_name: $arg_type ),* )
-> Ret {
let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
const IS_GAS: bool = str_eq(stringify!($name), "gas") || str_eq(stringify!($name), "finite_wasm_gas");
let _span = if IS_GAS {
None
} else {
Some(tracing::trace_span!(
target: "host-function",
stringify!($name)
).entered())
};
unsafe { (*env).$func( $( $arg_name, )* ) }
}));
let result: Result<Result<_, crate::logic::VMLogicError>, _> = result;
#[allow(unused_parens)]
match result {
Ok(Ok(($($returns),*))) => make_ret($($returns),*),
Ok(Err(trap)) => unsafe {
unc_vm_vm::raise_user_trap(Box::new(trap))
},
Err(e) => unsafe {
unc_vm_vm::resume_panic(e)
},
}
}
if module == stringify!($mod) && field == stringify!($name) {
let args = [$(<$arg_type as UncVmType>::ty()),*];
let rets = [$(<$returns as UncVmType>::ty()),*];
let signature = unc_vm_types::FunctionType::new(&args[..], &rets[..]);
let signature = self.engine.register_signature(signature);
return Some(unc_vm_vm::Export::Function(ExportFunction {
vm_function: VMFunction {
address: $name as *const _,
vmctx: unc_vm_vm::VMFunctionEnvironment {
host_env: self.vmlogic as *const _ as *mut _
},
signature,
kind: VMFunctionKind::Static,
call_trampoline: None,
instance_ref: None,
},
metadata: Some(Arc::clone(&self.metadata)),
}));
}
};
}
for_each_available_import!(self.vmlogic.config, add_import);
return None;
}
}
pub(crate) fn build<'e, 'a, 'b>(
memory: VMMemory,
logic: &'a mut VMLogic<'b>,
engine: &'e UniversalEngine,
) -> UncVmImports<'e, 'a, 'b> {
let metadata = unsafe {
ExportFunctionMetadata::new(logic as *mut _ as *mut _, None, |ptr| ptr, |_| {})
};
UncVmImports { memory, vmlogic: logic, metadata: Arc::new(metadata), engine }
}
}
#[cfg(feature = "wasmtime_vm")]
pub(crate) mod wasmtime {
use super::str_eq;
use crate::logic::{VMLogic, VMLogicError};
use std::cell::UnsafeCell;
use std::ffi::c_void;
#[derive(Debug)]
pub(crate) struct ErrorContainer(std::sync::Mutex<Option<VMLogicError>>);
impl ErrorContainer {
pub(crate) fn take(&self) -> Option<VMLogicError> {
let mut guard = self.0.lock().unwrap_or_else(|e| e.into_inner());
guard.take()
}
}
impl std::error::Error for ErrorContainer {}
impl std::fmt::Display for ErrorContainer {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str("VMLogic error occurred and is now stored in an opaque storage container")
}
}
thread_local! {
static CALLER_CONTEXT: UnsafeCell<*mut c_void> = const { UnsafeCell::new(core::ptr::null_mut()) };
}
pub(crate) fn link<'a, 'b>(
linker: &mut wasmtime::Linker<()>,
memory: wasmtime::Memory,
store: &wasmtime::Store<()>,
logic: &'a mut VMLogic<'b>,
) {
let raw_logic = logic as *mut _ as *mut c_void;
CALLER_CONTEXT.with(|caller_context| unsafe { *caller_context.get() = raw_logic });
linker.define(store, "env", "memory", memory).expect("cannot define memory");
macro_rules! add_import {
(
$mod:ident / $name:ident : $func:ident < [ $( $arg_name:ident : $arg_type:ident ),* ] -> [ $( $returns:ident ),* ] >
) => {
#[allow(unused_parens)]
fn $name(caller: wasmtime::Caller<'_, ()>, $( $arg_name: $arg_type ),* ) -> anyhow::Result<($( $returns ),*)> {
const IS_GAS: bool = str_eq(stringify!($name), "gas") || str_eq(stringify!($name), "finite_wasm_gas");
let _span = if IS_GAS {
None
} else {
Some(tracing::trace_span!(target: "host-function", stringify!($name)).entered())
};
let data = CALLER_CONTEXT.with(|caller_context| {
unsafe {
*caller_context.get()
}
});
unsafe {
crate::wasmtime_runner::CALLER.with(|runner_caller| *runner_caller.borrow_mut() = std::mem::transmute(caller));
}
let logic: &mut VMLogic<'_> = unsafe { &mut *(data as *mut VMLogic<'_>) };
match logic.$func( $( $arg_name as $arg_type, )* ) {
Ok(result) => Ok(result as ($( $returns ),* ) ),
Err(err) => {
Err(ErrorContainer(std::sync::Mutex::new(Some(err))).into())
}
}
}
linker.func_wrap(stringify!($mod), stringify!($name), $name).expect("cannot link external");
};
}
for_each_available_import!(logic.config, add_import);
}
}
#[allow(dead_code)]
const fn str_eq(s1: &str, s2: &str) -> bool {
let s1 = s1.as_bytes();
let s2 = s2.as_bytes();
if s1.len() != s2.len() {
return false;
}
let mut i = 0;
while i < s1.len() {
if s1[i] != s2[i] {
return false;
}
i += 1;
}
true
}