use super::{allocator, vm};
use crate::{trie, util};
use alloc::{borrow::ToOwned as _, boxed::Box, string::String, sync::Arc, vec, vec::Vec};
use core::{fmt, iter, str};
use functions::HostFunction;
pub mod runtime_version;
pub use runtime_version::{
CoreVersion, CoreVersionApisFromSliceErr, CoreVersionError, CoreVersionRef,
FindEncodedEmbeddedRuntimeVersionApisError,
};
pub use trie::TrieEntryVersion;
pub use vm::HeapPages;
pub use zstd::Error as ModuleFormatError;
mod functions;
mod tests;
mod zstd;
pub struct Config<TModule> {
pub module: TModule,
pub heap_pages: HeapPages,
pub exec_hint: vm::ExecHint,
pub allow_unresolved_imports: bool,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum StorageProofSizeBehavior {
Unimplemented,
ConstantReturnValue(u64),
}
impl StorageProofSizeBehavior {
pub fn proof_recording_disabled() -> Self {
StorageProofSizeBehavior::ConstantReturnValue(u64::MAX)
}
}
#[derive(Clone)]
pub struct HostVmPrototype {
common: Box<VmCommon>,
vm_proto: vm::VirtualMachinePrototype,
}
#[derive(Clone)]
struct VmCommon {
runtime_version: Option<CoreVersion>,
heap_base: u32,
registered_functions: Arc<[FunctionImport]>,
heap_pages: HeapPages,
memory_total_pages: HeapPages,
}
impl HostVmPrototype {
pub fn new(config: Config<impl AsRef<[u8]>>) -> Result<Self, NewErr> {
let module_bytes = zstd::zstd_decode_if_necessary(config.module.as_ref(), 50 * 1024 * 1024)
.map_err(NewErr::BadFormat)?;
let runtime_version = match runtime_version::find_embedded_runtime_version(&module_bytes) {
Ok(Some(r)) => Some(r),
Ok(None) => None,
Err(
runtime_version::FindEmbeddedRuntimeVersionError::CustomSectionsPresenceMismatch,
) => None,
Err(runtime_version::FindEmbeddedRuntimeVersionError::FindSections(err)) => {
return Err(NewErr::RuntimeVersion(
FindEmbeddedRuntimeVersionError::FindSections(err),
));
}
Err(runtime_version::FindEmbeddedRuntimeVersionError::RuntimeApisDecode(err)) => {
return Err(NewErr::RuntimeVersion(
FindEmbeddedRuntimeVersionError::RuntimeApisDecode(err),
));
}
Err(runtime_version::FindEmbeddedRuntimeVersionError::RuntimeVersionDecode) => {
return Err(NewErr::RuntimeVersion(
FindEmbeddedRuntimeVersionError::RuntimeVersionDecode,
));
}
};
let (mut vm_proto, registered_functions) = {
let mut registered_functions = Vec::new();
let vm_proto = vm::VirtualMachinePrototype::new(vm::Config {
module_bytes: &module_bytes[..],
exec_hint: config.exec_hint,
symbols: &mut |mod_name, f_name, signature| {
if mod_name != "env" {
return Err(());
}
let id = registered_functions.len();
registered_functions.push(match HostFunction::by_name(f_name) {
Some(f) if f.signature() == *signature => FunctionImport::Resolved(f),
Some(_) | None if !config.allow_unresolved_imports => {
return Err(());
}
Some(_) | None => FunctionImport::Unresolved {
name: f_name.to_owned(),
module: mod_name.to_owned(),
},
});
Ok(id)
},
})?;
(vm_proto, registered_functions.into())
};
let heap_base = vm_proto
.global_value("__heap_base")
.map_err(|_| NewErr::HeapBaseNotFound)?;
let memory_total_pages = if heap_base == 0 {
config.heap_pages
} else {
HeapPages::new((heap_base - 1) / (64 * 1024)) + config.heap_pages + HeapPages::new(1)
};
if vm_proto
.memory_max_pages()
.map_or(false, |max| max < memory_total_pages)
{
return Err(NewErr::MemoryMaxSizeTooLow);
}
let mut host_vm_prototype = HostVmPrototype {
vm_proto,
common: Box::new(VmCommon {
runtime_version,
heap_base,
registered_functions,
heap_pages: config.heap_pages,
memory_total_pages,
}),
};
if host_vm_prototype.common.runtime_version.is_none() {
let mut vm: HostVm = match host_vm_prototype.run_no_param(
"Core_version",
StorageProofSizeBehavior::proof_recording_disabled(),
) {
Ok(vm) => vm.into(),
Err((err, _)) => return Err(NewErr::CoreVersion(CoreVersionError::Start(err))),
};
loop {
match vm {
HostVm::ReadyToRun(r) => vm = r.run(),
HostVm::Finished(finished) => {
let version =
match CoreVersion::from_slice(finished.value().as_ref().to_vec()) {
Ok(v) => v,
Err(_) => {
return Err(NewErr::CoreVersion(CoreVersionError::Decode));
}
};
host_vm_prototype = finished.into_prototype();
host_vm_prototype.common.runtime_version = Some(version);
break;
}
HostVm::GetMaxLogLevel(resume) => {
vm = resume.resume(0); }
HostVm::LogEmit(log) => vm = log.resume(),
HostVm::Error { error, .. } => {
return Err(NewErr::CoreVersion(CoreVersionError::Run(error)));
}
_ => return Err(NewErr::CoreVersion(CoreVersionError::ForbiddenHostFunction)),
}
}
}
debug_assert!(host_vm_prototype.common.runtime_version.is_some());
Ok(host_vm_prototype)
}
pub fn heap_pages(&self) -> HeapPages {
self.common.heap_pages
}
pub fn runtime_version(&self) -> &CoreVersion {
self.common
.runtime_version
.as_ref()
.unwrap_or_else(|| unreachable!())
}
pub fn run(
self,
function_to_call: &str,
storage_proof_size_behavior: StorageProofSizeBehavior,
data: &[u8],
) -> Result<ReadyToRun, (StartErr, Self)> {
self.run_vectored(
function_to_call,
storage_proof_size_behavior,
iter::once(data),
)
}
pub fn run_no_param(
self,
function_to_call: &str,
storage_proof_size_behavior: StorageProofSizeBehavior,
) -> Result<ReadyToRun, (StartErr, Self)> {
self.run_vectored(
function_to_call,
storage_proof_size_behavior,
iter::empty::<Vec<u8>>(),
)
}
pub fn run_vectored(
mut self,
function_to_call: &str,
storage_proof_size_behavior: StorageProofSizeBehavior,
data: impl Iterator<Item = impl AsRef<[u8]>> + Clone,
) -> Result<ReadyToRun, (StartErr, Self)> {
let mut data_len_u32: u32 = 0;
for data in data.clone() {
let len = match u32::try_from(data.as_ref().len()) {
Ok(v) => v,
Err(_) => return Err((StartErr::DataSizeOverflow, self)),
};
data_len_u32 = match data_len_u32.checked_add(len) {
Some(v) => v,
None => return Err((StartErr::DataSizeOverflow, self)),
};
}
let mut allocator = allocator::FreeingBumpHeapAllocator::new(self.common.heap_base);
let mut vm = self.vm_proto.prepare();
let data_ptr = match allocator.allocate(
&mut MemAccess {
vm: MemAccessVm::Prepare(&mut vm),
memory_total_pages: self.common.memory_total_pages,
},
data_len_u32,
) {
Ok(p) => p,
Err(_) => {
self.vm_proto = vm.into_prototype();
return Err((StartErr::DataSizeOverflow, self));
}
};
if let Some(to_grow) = ((data_ptr + data_len_u32).saturating_sub(1) / (64 * 1024) + 1)
.checked_sub(u32::from(vm.memory_size()))
{
vm.grow_memory(HeapPages::from(to_grow))
.unwrap_or_else(|_| unreachable!());
}
let mut data_ptr_iter = data_ptr;
for data in data {
let data = data.as_ref();
vm.write_memory(data_ptr_iter, data)
.unwrap_or_else(|_| unreachable!());
data_ptr_iter = data_ptr_iter
.checked_add(u32::try_from(data.len()).unwrap_or_else(|_| unreachable!()))
.unwrap_or_else(|| unreachable!());
}
let vm = match vm.start(
function_to_call,
&[
vm::WasmValue::I32(i32::from_ne_bytes(data_ptr.to_ne_bytes())),
vm::WasmValue::I32(i32::from_ne_bytes(data_len_u32.to_ne_bytes())),
],
) {
Ok(vm) => vm,
Err((error, vm_proto)) => {
self.vm_proto = vm_proto;
return Err((error.into(), self));
}
};
Ok(ReadyToRun {
resume_value: None,
inner: Box::new(Inner {
common: self.common,
vm,
storage_transaction_depth: 0,
signatures_batch_verification: None,
allocator,
storage_proof_size_behavior,
}),
})
}
}
impl fmt::Debug for HostVmPrototype {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_tuple("HostVmPrototype").finish()
}
}
#[must_use]
#[derive(derive_more::From, Debug)]
pub enum HostVm {
#[from]
ReadyToRun(ReadyToRun),
#[from]
Finished(Finished),
Error {
prototype: HostVmPrototype,
error: Error,
},
#[from]
ExternalStorageGet(ExternalStorageGet),
#[from]
ExternalStorageSet(ExternalStorageSet),
#[from]
ExternalStorageAppend(ExternalStorageAppend),
#[from]
ExternalStorageClearPrefix(ExternalStorageClearPrefix),
#[from]
ExternalStorageRoot(ExternalStorageRoot),
#[from]
ExternalStorageNextKey(ExternalStorageNextKey),
#[from]
ExternalOffchainIndexSet(ExternalOffchainIndexSet),
#[from]
ExternalOffchainStorageGet(ExternalOffchainStorageGet),
#[from]
ExternalOffchainStorageSet(ExternalOffchainStorageSet),
#[from]
OffchainTimestamp(OffchainTimestamp),
#[from]
OffchainRandomSeed(OffchainRandomSeed),
#[from]
OffchainSubmitTransaction(OffchainSubmitTransaction),
#[from]
SignatureVerification(SignatureVerification),
#[from]
CallRuntimeVersion(CallRuntimeVersion),
#[from]
StartStorageTransaction(StartStorageTransaction),
EndStorageTransaction {
resume: EndStorageTransaction,
rollback: bool,
},
#[from]
GetMaxLogLevel(GetMaxLogLevel),
#[from]
LogEmit(LogEmit),
}
impl HostVm {
pub fn into_prototype(self) -> HostVmPrototype {
match self {
HostVm::ReadyToRun(inner) => inner.inner.into_prototype(),
HostVm::Finished(inner) => inner.inner.into_prototype(),
HostVm::Error { prototype, .. } => prototype,
HostVm::ExternalStorageGet(inner) => inner.inner.into_prototype(),
HostVm::ExternalStorageSet(inner) => inner.inner.into_prototype(),
HostVm::ExternalStorageAppend(inner) => inner.inner.into_prototype(),
HostVm::ExternalStorageClearPrefix(inner) => inner.inner.into_prototype(),
HostVm::ExternalStorageRoot(inner) => inner.inner.into_prototype(),
HostVm::ExternalStorageNextKey(inner) => inner.inner.into_prototype(),
HostVm::ExternalOffchainIndexSet(inner) => inner.inner.into_prototype(),
HostVm::ExternalOffchainStorageGet(inner) => inner.inner.into_prototype(),
HostVm::ExternalOffchainStorageSet(inner) => inner.inner.into_prototype(),
HostVm::OffchainTimestamp(inner) => inner.inner.into_prototype(),
HostVm::OffchainRandomSeed(inner) => inner.inner.into_prototype(),
HostVm::OffchainSubmitTransaction(inner) => inner.inner.into_prototype(),
HostVm::SignatureVerification(inner) => inner.inner.into_prototype(),
HostVm::CallRuntimeVersion(inner) => inner.inner.into_prototype(),
HostVm::StartStorageTransaction(inner) => inner.inner.into_prototype(),
HostVm::EndStorageTransaction { resume, .. } => resume.inner.into_prototype(),
HostVm::GetMaxLogLevel(inner) => inner.inner.into_prototype(),
HostVm::LogEmit(inner) => inner.inner.into_prototype(),
}
}
}
pub struct ReadyToRun {
inner: Box<Inner>,
resume_value: Option<vm::WasmValue>,
}
impl ReadyToRun {
pub fn run(mut self) -> HostVm {
loop {
match self.run_once() {
HostVm::ReadyToRun(r) => self = r,
other => return other,
}
}
}
fn run_once(mut self) -> HostVm {
let (id, params) = match self.inner.vm.run(self.resume_value) {
Ok(vm::ExecOutcome::Interrupted { id, params }) => (id, params),
Ok(vm::ExecOutcome::Finished {
return_value: Ok(Some(vm::WasmValue::I64(ret))),
}) => {
if self.inner.storage_transaction_depth > 0 {
return HostVm::Error {
prototype: self.inner.into_prototype(),
error: Error::FinishedWithPendingTransaction,
};
}
let ret = u64::from_ne_bytes(ret.to_ne_bytes());
let value_size = u32::try_from(ret >> 32).unwrap_or_else(|_| unreachable!());
let value_ptr = u32::try_from(ret & 0xffff_ffff).unwrap_or_else(|_| unreachable!());
if value_size.saturating_add(value_ptr)
<= u32::from(self.inner.vm.memory_size()) * 64 * 1024
{
return HostVm::Finished(Finished {
inner: self.inner,
value_ptr,
value_size,
});
}
let error = Error::ReturnedPtrOutOfRange {
pointer: value_ptr,
size: value_size,
memory_size: u32::from(self.inner.vm.memory_size()) * 64 * 1024,
};
return HostVm::Error {
prototype: self.inner.into_prototype(),
error,
};
}
Ok(vm::ExecOutcome::Finished {
return_value: Ok(return_value),
}) => {
return HostVm::Error {
prototype: self.inner.into_prototype(),
error: Error::BadReturnValue {
actual: return_value.map(|v| v.ty()),
},
};
}
Ok(vm::ExecOutcome::Finished {
return_value: Err(err),
}) => {
return HostVm::Error {
error: Error::Trap(err),
prototype: self.inner.into_prototype(),
};
}
Err(vm::RunErr::BadValueTy { .. }) => {
unreachable!()
}
Err(vm::RunErr::Poisoned) => {
unreachable!()
}
};
let host_fn = match self.inner.common.registered_functions.get(id) {
Some(FunctionImport::Resolved(f)) => *f,
Some(FunctionImport::Unresolved { name, module }) => {
return HostVm::Error {
error: Error::UnresolvedFunctionCalled {
function: name.clone(),
module_name: module.clone(),
},
prototype: self.inner.into_prototype(),
};
}
None => unreachable!(),
};
macro_rules! expect_pointer_size {
($num:expr) => {{
let val = match ¶ms[$num] {
vm::WasmValue::I64(v) => u64::from_ne_bytes(v.to_ne_bytes()),
_ => unreachable!(),
};
let len = u32::try_from(val >> 32).unwrap_or_else(|_| unreachable!());
let ptr = u32::try_from(val & 0xffffffff).unwrap_or_else(|_| unreachable!());
let result = self.inner.vm.read_memory(ptr, len);
match result {
Ok(v) => v,
Err(vm::OutOfBoundsError) => {
drop(result);
return HostVm::Error {
error: Error::ParamOutOfRange {
function: host_fn.name(),
param_num: $num,
pointer: ptr,
length: len,
},
prototype: self.inner.into_prototype(),
};
}
}
}};
}
macro_rules! expect_pointer_size_raw {
($num:expr) => {{
let val = match ¶ms[$num] {
vm::WasmValue::I64(v) => u64::from_ne_bytes(v.to_ne_bytes()),
_ => unreachable!(),
};
let len = u32::try_from(val >> 32).unwrap_or_else(|_| unreachable!());
let ptr = u32::try_from(val & 0xffffffff).unwrap_or_else(|_| unreachable!());
if len.saturating_add(ptr) > u32::from(self.inner.vm.memory_size()) * 64 * 1024 {
return HostVm::Error {
error: Error::ParamOutOfRange {
function: host_fn.name(),
param_num: $num,
pointer: ptr,
length: len,
},
prototype: self.inner.into_prototype(),
};
}
(ptr, len)
}};
}
macro_rules! expect_pointer_constant_size {
($num:expr, $size:expr) => {{
let ptr = match params[$num] {
vm::WasmValue::I32(v) => u32::from_ne_bytes(v.to_ne_bytes()),
_ => unreachable!(),
};
let result = self.inner.vm.read_memory(ptr, $size);
match result {
Ok(v) => {
*<&[u8; $size]>::try_from(v.as_ref()).unwrap_or_else(|_| unreachable!())
}
Err(vm::OutOfBoundsError) => {
drop(result);
return HostVm::Error {
error: Error::ParamOutOfRange {
function: host_fn.name(),
param_num: $num,
pointer: ptr,
length: $size,
},
prototype: self.inner.into_prototype(),
};
}
}
}};
}
macro_rules! expect_pointer_constant_size_raw {
($num:expr, $size:expr) => {{
let ptr = match params[$num] {
vm::WasmValue::I32(v) => u32::from_ne_bytes(v.to_ne_bytes()),
_ => unreachable!(),
};
if u32::saturating_add($size, ptr)
> u32::from(self.inner.vm.memory_size()) * 64 * 1024
{
return HostVm::Error {
error: Error::ParamOutOfRange {
function: host_fn.name(),
param_num: $num,
pointer: ptr,
length: $size,
},
prototype: self.inner.into_prototype(),
};
}
ptr
}};
}
macro_rules! expect_u32 {
($num:expr) => {{
match ¶ms[$num] {
vm::WasmValue::I32(v) => u32::from_ne_bytes(v.to_ne_bytes()),
_ => unreachable!(),
}
}};
}
macro_rules! expect_offchain_storage_kind {
($num:expr) => {{
match ¶ms[$num] {
vm::WasmValue::I32(0) => true,
vm::WasmValue::I32(1) => false,
vm::WasmValue::I32(_) => {
return HostVm::Error {
error: Error::ParamDecodeError,
prototype: self.inner.into_prototype(),
};
}
_ => unreachable!(),
}
}};
}
macro_rules! expect_state_version {
($num:expr) => {{
match ¶ms[$num] {
vm::WasmValue::I32(0) => TrieEntryVersion::V0,
vm::WasmValue::I32(1) => TrieEntryVersion::V1,
vm::WasmValue::I32(_) => {
return HostVm::Error {
error: Error::ParamDecodeError,
prototype: self.inner.into_prototype(),
};
}
_ => unreachable!(),
}
}};
}
macro_rules! host_fn_not_implemented {
() => {{
return HostVm::Error {
error: Error::HostFunctionNotImplemented {
function: host_fn.name(),
},
prototype: self.inner.into_prototype(),
};
}};
}
match host_fn {
HostFunction::ext_storage_set_version_1 => {
let (key_ptr, key_size) = expect_pointer_size_raw!(0);
let (value_ptr, value_size) = expect_pointer_size_raw!(1);
HostVm::ExternalStorageSet(ExternalStorageSet {
key_ptr,
key_size,
child_trie_ptr_size: None,
value: Some((value_ptr, value_size)),
inner: self.inner,
})
}
HostFunction::ext_storage_get_version_1 => {
let (key_ptr, key_size) = expect_pointer_size_raw!(0);
HostVm::ExternalStorageGet(ExternalStorageGet {
key_ptr,
key_size,
child_trie_ptr_size: None,
calling: id,
value_out_ptr: None,
offset: 0,
max_size: u32::MAX,
inner: self.inner,
})
}
HostFunction::ext_storage_read_version_1 => {
let (key_ptr, key_size) = expect_pointer_size_raw!(0);
let (value_out_ptr, value_out_size) = expect_pointer_size_raw!(1);
let offset = expect_u32!(2);
HostVm::ExternalStorageGet(ExternalStorageGet {
key_ptr,
key_size,
child_trie_ptr_size: None,
calling: id,
value_out_ptr: Some(value_out_ptr),
offset,
max_size: value_out_size,
inner: self.inner,
})
}
HostFunction::ext_storage_clear_version_1 => {
let (key_ptr, key_size) = expect_pointer_size_raw!(0);
HostVm::ExternalStorageSet(ExternalStorageSet {
key_ptr,
key_size,
child_trie_ptr_size: None,
value: None,
inner: self.inner,
})
}
HostFunction::ext_storage_exists_version_1 => {
let (key_ptr, key_size) = expect_pointer_size_raw!(0);
HostVm::ExternalStorageGet(ExternalStorageGet {
key_ptr,
key_size,
child_trie_ptr_size: None,
calling: id,
value_out_ptr: None,
offset: 0,
max_size: 0,
inner: self.inner,
})
}
HostFunction::ext_storage_clear_prefix_version_1 => {
let (prefix_ptr, prefix_size) = expect_pointer_size_raw!(0);
HostVm::ExternalStorageClearPrefix(ExternalStorageClearPrefix {
prefix_ptr_size: Some((prefix_ptr, prefix_size)),
child_trie_ptr_size: None,
inner: self.inner,
max_keys_to_remove: None,
calling: id,
})
}
HostFunction::ext_storage_clear_prefix_version_2 => {
let (prefix_ptr, prefix_size) = expect_pointer_size_raw!(0);
let max_keys_to_remove = {
let input = expect_pointer_size!(1);
let parsing_result: Result<_, nom::Err<(&[u8], nom::error::ErrorKind)>> =
nom::Parser::parse(
&mut nom::combinator::all_consuming(util::nom_option_decode(
nom::number::streaming::le_u32,
)),
input.as_ref(),
)
.map(|(_, parse_result)| parse_result);
match parsing_result {
Ok(val) => Ok(val),
Err(_) => Err(()),
}
};
let max_keys_to_remove = match max_keys_to_remove {
Ok(l) => l,
Err(()) => {
return HostVm::Error {
error: Error::ParamDecodeError,
prototype: self.inner.into_prototype(),
};
}
};
HostVm::ExternalStorageClearPrefix(ExternalStorageClearPrefix {
prefix_ptr_size: Some((prefix_ptr, prefix_size)),
child_trie_ptr_size: None,
inner: self.inner,
max_keys_to_remove,
calling: id,
})
}
HostFunction::ext_storage_root_version_1 => {
HostVm::ExternalStorageRoot(ExternalStorageRoot {
inner: self.inner,
calling: id,
child_trie_ptr_size: None,
})
}
HostFunction::ext_storage_root_version_2 => {
let version_param = expect_state_version!(0);
let version_spec = self
.inner
.common
.runtime_version
.as_ref()
.unwrap_or_else(|| unreachable!())
.decode()
.state_version
.unwrap_or(TrieEntryVersion::V0);
if version_param != version_spec {
return HostVm::Error {
error: Error::StateVersionMismatch {
parameter: version_param,
specification: version_spec,
},
prototype: self.inner.into_prototype(),
};
}
HostVm::ExternalStorageRoot(ExternalStorageRoot {
inner: self.inner,
calling: id,
child_trie_ptr_size: None,
})
}
HostFunction::ext_storage_changes_root_version_1 => {
self.inner.alloc_write_and_return_pointer_size(
HostFunction::ext_storage_changes_root_version_1.name(),
iter::once(&[0][..]),
)
}
HostFunction::ext_storage_next_key_version_1 => {
let (key_ptr, key_size) = expect_pointer_size_raw!(0);
HostVm::ExternalStorageNextKey(ExternalStorageNextKey {
key_ptr,
key_size,
child_trie_ptr_size: None,
inner: self.inner,
})
}
HostFunction::ext_storage_append_version_1 => {
let (key_ptr, key_size) = expect_pointer_size_raw!(0);
let (value_ptr, value_size) = expect_pointer_size_raw!(1);
HostVm::ExternalStorageAppend(ExternalStorageAppend {
key_ptr,
key_size,
value_ptr,
value_size,
inner: self.inner,
})
}
HostFunction::ext_storage_start_transaction_version_1 => {
self.inner.storage_transaction_depth += 1;
HostVm::StartStorageTransaction(StartStorageTransaction { inner: self.inner })
}
HostFunction::ext_storage_rollback_transaction_version_1 => {
if self.inner.storage_transaction_depth == 0 {
return HostVm::Error {
error: Error::NoActiveTransaction,
prototype: self.inner.into_prototype(),
};
}
self.inner.storage_transaction_depth -= 1;
HostVm::EndStorageTransaction {
resume: EndStorageTransaction { inner: self.inner },
rollback: true,
}
}
HostFunction::ext_storage_commit_transaction_version_1 => {
if self.inner.storage_transaction_depth == 0 {
return HostVm::Error {
error: Error::NoActiveTransaction,
prototype: self.inner.into_prototype(),
};
}
self.inner.storage_transaction_depth -= 1;
HostVm::EndStorageTransaction {
resume: EndStorageTransaction { inner: self.inner },
rollback: false,
}
}
HostFunction::ext_storage_proof_size_storage_proof_size_version_1 => {
match self.inner.storage_proof_size_behavior {
StorageProofSizeBehavior::ConstantReturnValue(value) => {
HostVm::ReadyToRun(ReadyToRun {
inner: self.inner,
resume_value: Some(vm::WasmValue::I64(i64::from_ne_bytes(
value.to_ne_bytes(),
))),
})
}
StorageProofSizeBehavior::Unimplemented => HostVm::Error {
error: Error::HostFunctionNotImplemented {
function: host_fn.name(),
},
prototype: self.inner.into_prototype(),
},
}
}
HostFunction::ext_default_child_storage_get_version_1 => {
let (child_trie_ptr, child_trie_size) = expect_pointer_size_raw!(0);
let (key_ptr, key_size) = expect_pointer_size_raw!(1);
HostVm::ExternalStorageGet(ExternalStorageGet {
key_ptr,
key_size,
child_trie_ptr_size: Some((child_trie_ptr, child_trie_size)),
calling: id,
value_out_ptr: None,
offset: 0,
max_size: u32::MAX,
inner: self.inner,
})
}
HostFunction::ext_default_child_storage_read_version_1 => {
let (child_trie_ptr, child_trie_size) = expect_pointer_size_raw!(0);
let (key_ptr, key_size) = expect_pointer_size_raw!(1);
let (value_out_ptr, value_out_size) = expect_pointer_size_raw!(2);
let offset = expect_u32!(3);
HostVm::ExternalStorageGet(ExternalStorageGet {
key_ptr,
key_size,
child_trie_ptr_size: Some((child_trie_ptr, child_trie_size)),
calling: id,
value_out_ptr: Some(value_out_ptr),
offset,
max_size: value_out_size,
inner: self.inner,
})
}
HostFunction::ext_default_child_storage_storage_kill_version_1 => {
let (child_trie_ptr, child_trie_size) = expect_pointer_size_raw!(0);
HostVm::ExternalStorageClearPrefix(ExternalStorageClearPrefix {
prefix_ptr_size: None,
child_trie_ptr_size: Some((child_trie_ptr, child_trie_size)),
inner: self.inner,
max_keys_to_remove: None,
calling: id,
})
}
HostFunction::ext_default_child_storage_storage_kill_version_2
| HostFunction::ext_default_child_storage_storage_kill_version_3 => {
let (child_trie_ptr, child_trie_size) = expect_pointer_size_raw!(0);
let max_keys_to_remove = {
let input = expect_pointer_size!(1);
let parsing_result: Result<_, nom::Err<(&[u8], nom::error::ErrorKind)>> =
nom::Parser::parse(
&mut nom::combinator::all_consuming(util::nom_option_decode(
nom::number::streaming::le_u32,
)),
input.as_ref(),
)
.map(|(_, parse_result)| parse_result);
match parsing_result {
Ok(val) => Ok(val),
Err(_) => Err(()),
}
};
let max_keys_to_remove = match max_keys_to_remove {
Ok(l) => l,
Err(()) => {
return HostVm::Error {
error: Error::ParamDecodeError,
prototype: self.inner.into_prototype(),
};
}
};
HostVm::ExternalStorageClearPrefix(ExternalStorageClearPrefix {
prefix_ptr_size: None,
child_trie_ptr_size: Some((child_trie_ptr, child_trie_size)),
inner: self.inner,
max_keys_to_remove,
calling: id,
})
}
HostFunction::ext_default_child_storage_clear_prefix_version_1 => {
let (child_trie_ptr, child_trie_size) = expect_pointer_size_raw!(0);
let (prefix_ptr, prefix_size) = expect_pointer_size_raw!(1);
HostVm::ExternalStorageClearPrefix(ExternalStorageClearPrefix {
prefix_ptr_size: Some((prefix_ptr, prefix_size)),
child_trie_ptr_size: Some((child_trie_ptr, child_trie_size)),
inner: self.inner,
max_keys_to_remove: None,
calling: id,
})
}
HostFunction::ext_default_child_storage_clear_prefix_version_2 => {
let (child_trie_ptr, child_trie_size) = expect_pointer_size_raw!(0);
let (prefix_ptr, prefix_size) = expect_pointer_size_raw!(1);
let max_keys_to_remove = {
let input = expect_pointer_size!(2);
let parsing_result: Result<_, nom::Err<(&[u8], nom::error::ErrorKind)>> =
nom::Parser::parse(
&mut nom::combinator::all_consuming(util::nom_option_decode(
nom::number::streaming::le_u32,
)),
input.as_ref(),
)
.map(|(_, parse_result)| parse_result);
match parsing_result {
Ok(val) => Ok(val),
Err(_) => Err(()),
}
};
let max_keys_to_remove = match max_keys_to_remove {
Ok(l) => l,
Err(()) => {
return HostVm::Error {
error: Error::ParamDecodeError,
prototype: self.inner.into_prototype(),
};
}
};
HostVm::ExternalStorageClearPrefix(ExternalStorageClearPrefix {
prefix_ptr_size: Some((prefix_ptr, prefix_size)),
child_trie_ptr_size: Some((child_trie_ptr, child_trie_size)),
inner: self.inner,
max_keys_to_remove,
calling: id,
})
}
HostFunction::ext_default_child_storage_set_version_1 => {
let (child_trie_ptr, child_trie_size) = expect_pointer_size_raw!(0);
let (key_ptr, key_size) = expect_pointer_size_raw!(1);
let (value_ptr, value_size) = expect_pointer_size_raw!(2);
HostVm::ExternalStorageSet(ExternalStorageSet {
key_ptr,
key_size,
child_trie_ptr_size: Some((child_trie_ptr, child_trie_size)),
value: Some((value_ptr, value_size)),
inner: self.inner,
})
}
HostFunction::ext_default_child_storage_clear_version_1 => {
let (child_trie_ptr, child_trie_size) = expect_pointer_size_raw!(0);
let (key_ptr, key_size) = expect_pointer_size_raw!(1);
HostVm::ExternalStorageSet(ExternalStorageSet {
key_ptr,
key_size,
child_trie_ptr_size: Some((child_trie_ptr, child_trie_size)),
value: None,
inner: self.inner,
})
}
HostFunction::ext_default_child_storage_exists_version_1 => {
let (child_trie_ptr, child_trie_size) = expect_pointer_size_raw!(0);
let (key_ptr, key_size) = expect_pointer_size_raw!(1);
HostVm::ExternalStorageGet(ExternalStorageGet {
key_ptr,
key_size,
child_trie_ptr_size: Some((child_trie_ptr, child_trie_size)),
calling: id,
value_out_ptr: None,
offset: 0,
max_size: 0,
inner: self.inner,
})
}
HostFunction::ext_default_child_storage_next_key_version_1 => {
let (child_trie_ptr, child_trie_size) = expect_pointer_size_raw!(0);
let (key_ptr, key_size) = expect_pointer_size_raw!(1);
HostVm::ExternalStorageNextKey(ExternalStorageNextKey {
key_ptr,
key_size,
child_trie_ptr_size: Some((child_trie_ptr, child_trie_size)),
inner: self.inner,
})
}
HostFunction::ext_default_child_storage_root_version_1 => {
let (child_trie_ptr, child_trie_size) = expect_pointer_size_raw!(0);
HostVm::ExternalStorageRoot(ExternalStorageRoot {
inner: self.inner,
calling: id,
child_trie_ptr_size: Some((child_trie_ptr, child_trie_size)),
})
}
HostFunction::ext_default_child_storage_root_version_2 => {
let (child_trie_ptr, child_trie_size) = expect_pointer_size_raw!(0);
let version_param = expect_state_version!(1);
let version_spec = self
.inner
.common
.runtime_version
.as_ref()
.unwrap_or_else(|| unreachable!())
.decode()
.state_version
.unwrap_or(TrieEntryVersion::V0);
if version_param != version_spec {
return HostVm::Error {
error: Error::StateVersionMismatch {
parameter: version_param,
specification: version_spec,
},
prototype: self.inner.into_prototype(),
};
}
HostVm::ExternalStorageRoot(ExternalStorageRoot {
inner: self.inner,
calling: id,
child_trie_ptr_size: Some((child_trie_ptr, child_trie_size)),
})
}
HostFunction::ext_crypto_ed25519_public_keys_version_1 => host_fn_not_implemented!(),
HostFunction::ext_crypto_ed25519_generate_version_1 => host_fn_not_implemented!(),
HostFunction::ext_crypto_ed25519_sign_version_1 => host_fn_not_implemented!(),
HostFunction::ext_crypto_ed25519_verify_version_1
| HostFunction::ext_crypto_ed25519_batch_verify_version_1 => {
let is_batch_verification = matches!(
host_fn,
HostFunction::ext_crypto_ed25519_batch_verify_version_1
);
if is_batch_verification && self.inner.signatures_batch_verification.is_none() {
return HostVm::Error {
error: Error::BatchVerifyWithoutStarting,
prototype: self.inner.into_prototype(),
};
}
let (message_ptr, message_size) = expect_pointer_size_raw!(1);
HostVm::SignatureVerification(SignatureVerification {
algorithm: SignatureVerificationAlgorithm::Ed25519,
signature_ptr: expect_pointer_constant_size_raw!(0, 64),
public_key_ptr: expect_pointer_constant_size_raw!(2, 32),
message_ptr,
message_size,
inner: self.inner,
is_batch_verification,
})
}
HostFunction::ext_crypto_sr25519_public_keys_version_1 => host_fn_not_implemented!(),
HostFunction::ext_crypto_sr25519_generate_version_1 => host_fn_not_implemented!(),
HostFunction::ext_crypto_sr25519_sign_version_1 => host_fn_not_implemented!(),
HostFunction::ext_crypto_sr25519_verify_version_1
| HostFunction::ext_crypto_sr25519_batch_verify_version_1 => {
let is_batch_verification = matches!(
host_fn,
HostFunction::ext_crypto_sr25519_batch_verify_version_1
);
if is_batch_verification && self.inner.signatures_batch_verification.is_none() {
return HostVm::Error {
error: Error::BatchVerifyWithoutStarting,
prototype: self.inner.into_prototype(),
};
}
let (message_ptr, message_size) = expect_pointer_size_raw!(1);
HostVm::SignatureVerification(SignatureVerification {
algorithm: SignatureVerificationAlgorithm::Sr25519V1,
signature_ptr: expect_pointer_constant_size_raw!(0, 64),
public_key_ptr: expect_pointer_constant_size_raw!(2, 32),
message_ptr,
message_size,
inner: self.inner,
is_batch_verification,
})
}
HostFunction::ext_crypto_sr25519_verify_version_2 => {
let (message_ptr, message_size) = expect_pointer_size_raw!(1);
HostVm::SignatureVerification(SignatureVerification {
algorithm: SignatureVerificationAlgorithm::Sr25519V2,
signature_ptr: expect_pointer_constant_size_raw!(0, 64),
public_key_ptr: expect_pointer_constant_size_raw!(2, 32),
message_ptr,
message_size,
inner: self.inner,
is_batch_verification: false,
})
}
HostFunction::ext_crypto_ecdsa_generate_version_1 => host_fn_not_implemented!(),
HostFunction::ext_crypto_ecdsa_sign_version_1 => {
let data = <[u8; 32]>::try_from(
blake2_rfc::blake2b::blake2b(32, &[], expect_pointer_size!(0).as_ref())
.as_bytes(),
)
.unwrap_or_else(|_| unreachable!());
let message = libsecp256k1::Message::parse(&data);
if let Ok(sc) =
libsecp256k1::SecretKey::parse(&expect_pointer_constant_size!(1, 32))
{
let (sig, ri) = libsecp256k1::sign(&message, &sc);
self.inner.alloc_write_and_return_pointer(
host_fn.name(),
[&sig.serialize()[..], &[ri.serialize()]].into_iter(),
)
} else {
HostVm::Error {
error: Error::ParamDecodeError,
prototype: self.inner.into_prototype(),
}
}
}
HostFunction::ext_crypto_ecdsa_public_keys_version_1 => host_fn_not_implemented!(),
HostFunction::ext_crypto_ecdsa_verify_version_1
| HostFunction::ext_crypto_ecdsa_batch_verify_version_1 => {
let is_batch_verification = matches!(
host_fn,
HostFunction::ext_crypto_ecdsa_batch_verify_version_1
);
if is_batch_verification && self.inner.signatures_batch_verification.is_none() {
return HostVm::Error {
error: Error::BatchVerifyWithoutStarting,
prototype: self.inner.into_prototype(),
};
}
let (message_ptr, message_size) = expect_pointer_size_raw!(1);
HostVm::SignatureVerification(SignatureVerification {
algorithm: SignatureVerificationAlgorithm::Ecdsa,
signature_ptr: expect_pointer_constant_size_raw!(0, 65),
public_key_ptr: expect_pointer_constant_size_raw!(2, 33),
message_ptr,
message_size,
inner: self.inner,
is_batch_verification,
})
}
HostFunction::ext_crypto_ecdsa_verify_version_2 => host_fn_not_implemented!(),
HostFunction::ext_crypto_ecdsa_sign_prehashed_version_1 => {
let message = libsecp256k1::Message::parse(&expect_pointer_constant_size!(0, 32));
if let Ok(sc) =
libsecp256k1::SecretKey::parse(&expect_pointer_constant_size!(1, 32))
{
let (sig, ri) = libsecp256k1::sign(&message, &sc);
self.inner.alloc_write_and_return_pointer(
host_fn.name(),
[&sig.serialize()[..], &[ri.serialize()]].into_iter(),
)
} else {
HostVm::Error {
error: Error::ParamDecodeError,
prototype: self.inner.into_prototype(),
}
}
}
HostFunction::ext_crypto_ecdsa_verify_prehashed_version_1 => {
HostVm::SignatureVerification(SignatureVerification {
algorithm: SignatureVerificationAlgorithm::EcdsaPrehashed,
signature_ptr: expect_pointer_constant_size_raw!(0, 65),
public_key_ptr: expect_pointer_constant_size_raw!(2, 33),
message_ptr: expect_pointer_constant_size_raw!(1, 32),
message_size: 32,
inner: self.inner,
is_batch_verification: false,
})
}
HostFunction::ext_crypto_secp256k1_ecdsa_recover_version_1
| HostFunction::ext_crypto_secp256k1_ecdsa_recover_version_2 => {
let sig = expect_pointer_constant_size!(0, 65);
let msg = expect_pointer_constant_size!(1, 32);
let is_v2 = matches!(
host_fn,
HostFunction::ext_crypto_secp256k1_ecdsa_recover_version_2
);
let result = {
let rs = if is_v2 {
libsecp256k1::Signature::parse_standard_slice(&sig[0..64])
} else {
libsecp256k1::Signature::parse_overflowing_slice(&sig[0..64])
};
if let Ok(rs) = rs {
let v = libsecp256k1::RecoveryId::parse(if sig[64] > 26 {
sig[64] - 27
} else {
sig[64]
});
if let Ok(v) = v {
let pubkey = libsecp256k1::recover(
&libsecp256k1::Message::parse_slice(&msg)
.unwrap_or_else(|_| unreachable!()),
&rs,
&v,
);
if let Ok(pubkey) = pubkey {
let mut res = Vec::with_capacity(65);
res.push(0);
res.extend_from_slice(&pubkey.serialize()[1..65]);
res
} else {
vec![1, 2]
}
} else {
vec![1, 1]
}
} else {
vec![1, 0]
}
};
self.inner
.alloc_write_and_return_pointer_size(host_fn.name(), iter::once(&result))
}
HostFunction::ext_crypto_secp256k1_ecdsa_recover_compressed_version_1
| HostFunction::ext_crypto_secp256k1_ecdsa_recover_compressed_version_2 => {
let sig = expect_pointer_constant_size!(0, 65);
let msg = expect_pointer_constant_size!(1, 32);
let is_v2 = matches!(
host_fn,
HostFunction::ext_crypto_secp256k1_ecdsa_recover_compressed_version_2
);
let result = {
let rs = if is_v2 {
libsecp256k1::Signature::parse_standard_slice(&sig[0..64])
} else {
libsecp256k1::Signature::parse_overflowing_slice(&sig[0..64])
};
if let Ok(rs) = rs {
let v = libsecp256k1::RecoveryId::parse(if sig[64] > 26 {
sig[64] - 27
} else {
sig[64]
});
if let Ok(v) = v {
let pubkey = libsecp256k1::recover(
&libsecp256k1::Message::parse_slice(&msg)
.unwrap_or_else(|_| unreachable!()),
&rs,
&v,
);
if let Ok(pubkey) = pubkey {
let mut res = Vec::with_capacity(34);
res.push(0);
res.extend_from_slice(&pubkey.serialize_compressed());
res
} else {
vec![1, 2]
}
} else {
vec![1, 1]
}
} else {
vec![1, 0]
}
};
self.inner
.alloc_write_and_return_pointer_size(host_fn.name(), iter::once(&result))
}
HostFunction::ext_crypto_start_batch_verify_version_1 => {
if self.inner.signatures_batch_verification.is_some() {
return HostVm::Error {
error: Error::AlreadyBatchVerify,
prototype: self.inner.into_prototype(),
};
}
self.inner.signatures_batch_verification = Some(true);
HostVm::ReadyToRun(ReadyToRun {
resume_value: None,
inner: self.inner,
})
}
HostFunction::ext_crypto_finish_batch_verify_version_1 => {
let Some(outcome) = self.inner.signatures_batch_verification.take() else {
return HostVm::Error {
error: Error::NoBatchVerify,
prototype: self.inner.into_prototype(),
};
};
HostVm::ReadyToRun(ReadyToRun {
resume_value: Some(vm::WasmValue::I32(if outcome { 1 } else { 0 })),
inner: self.inner,
})
}
HostFunction::ext_hashing_keccak_256_version_1 => {
let hash =
<sha3::Keccak256 as sha3::Digest>::digest(expect_pointer_size!(0).as_ref());
self.inner
.alloc_write_and_return_pointer(host_fn.name(), iter::once(&hash))
}
HostFunction::ext_hashing_keccak_512_version_1 => {
let hash =
<sha3::Keccak512 as sha3::Digest>::digest(expect_pointer_size!(0).as_ref());
self.inner
.alloc_write_and_return_pointer(host_fn.name(), iter::once(&hash))
}
HostFunction::ext_hashing_sha2_256_version_1 => {
let hash = <sha2::Sha256 as sha2::Digest>::digest(expect_pointer_size!(0).as_ref());
self.inner
.alloc_write_and_return_pointer(host_fn.name(), iter::once(&hash))
}
HostFunction::ext_hashing_blake2_128_version_1 => {
let out = {
let data = expect_pointer_size!(0);
blake2_rfc::blake2b::blake2b(16, &[], data.as_ref())
};
self.inner
.alloc_write_and_return_pointer(host_fn.name(), iter::once(out.as_bytes()))
}
HostFunction::ext_hashing_blake2_256_version_1 => {
let out = {
let data = expect_pointer_size!(0);
blake2_rfc::blake2b::blake2b(32, &[], data.as_ref())
};
self.inner
.alloc_write_and_return_pointer(host_fn.name(), iter::once(out.as_bytes()))
}
HostFunction::ext_hashing_twox_64_version_1 => {
let r0 = {
let data = expect_pointer_size!(0);
twox_hash::XxHash64::oneshot(0, data.as_ref())
};
self.inner
.alloc_write_and_return_pointer(host_fn.name(), iter::once(&r0.to_le_bytes()))
}
HostFunction::ext_hashing_twox_128_version_1 => {
let [r0, r1] = {
let data = expect_pointer_size!(0);
let data = data.as_ref();
[
twox_hash::XxHash64::oneshot(0, data),
twox_hash::XxHash64::oneshot(1, data),
]
};
self.inner.alloc_write_and_return_pointer(
host_fn.name(),
iter::once(&r0.to_le_bytes()).chain(iter::once(&r1.to_le_bytes())),
)
}
HostFunction::ext_hashing_twox_256_version_1 => {
let [r0, r1, r2, r3] = {
let data = expect_pointer_size!(0);
let data = data.as_ref();
[
twox_hash::XxHash64::oneshot(0, data),
twox_hash::XxHash64::oneshot(1, data),
twox_hash::XxHash64::oneshot(2, data),
twox_hash::XxHash64::oneshot(3, data),
]
};
self.inner.alloc_write_and_return_pointer(
host_fn.name(),
iter::once(&r0.to_le_bytes())
.chain(iter::once(&r1.to_le_bytes()))
.chain(iter::once(&r2.to_le_bytes()))
.chain(iter::once(&r3.to_le_bytes())),
)
}
HostFunction::ext_offchain_index_set_version_1 => {
let (key_ptr, key_size) = expect_pointer_size_raw!(0);
let (value_ptr, value_size) = expect_pointer_size_raw!(1);
HostVm::ExternalOffchainIndexSet(ExternalOffchainIndexSet {
key_ptr,
key_size,
value: Some((value_ptr, value_size)),
inner: self.inner,
})
}
HostFunction::ext_offchain_index_clear_version_1 => {
let (key_ptr, key_size) = expect_pointer_size_raw!(0);
HostVm::ExternalOffchainIndexSet(ExternalOffchainIndexSet {
key_ptr,
key_size,
value: None,
inner: self.inner,
})
}
HostFunction::ext_offchain_is_validator_version_1 => HostVm::ReadyToRun(ReadyToRun {
inner: self.inner,
resume_value: Some(vm::WasmValue::I32(1)), }),
HostFunction::ext_offchain_submit_transaction_version_1 => {
let (tx_ptr, tx_size) = expect_pointer_size_raw!(0);
HostVm::OffchainSubmitTransaction(OffchainSubmitTransaction {
inner: self.inner,
calling: id,
tx_ptr,
tx_size,
})
}
HostFunction::ext_offchain_network_state_version_1 => {
host_fn_not_implemented!()
}
HostFunction::ext_offchain_timestamp_version_1 => {
HostVm::OffchainTimestamp(OffchainTimestamp { inner: self.inner })
}
HostFunction::ext_offchain_sleep_until_version_1 => {
host_fn_not_implemented!()
}
HostFunction::ext_offchain_random_seed_version_1 => {
HostVm::OffchainRandomSeed(OffchainRandomSeed {
inner: self.inner,
calling: id,
})
}
HostFunction::ext_offchain_local_storage_set_version_1 => {
if expect_offchain_storage_kind!(0) {
let (key_ptr, key_size) = expect_pointer_size_raw!(1);
let (value_ptr, value_size) = expect_pointer_size_raw!(2);
HostVm::ExternalOffchainStorageSet(ExternalOffchainStorageSet {
key_ptr,
key_size,
value: Some((value_ptr, value_size)),
old_value: None,
inner: self.inner,
})
} else {
HostVm::ReadyToRun(ReadyToRun {
inner: self.inner,
resume_value: None,
})
}
}
HostFunction::ext_offchain_local_storage_compare_and_set_version_1 => {
if expect_offchain_storage_kind!(0) {
let (key_ptr, key_size) = expect_pointer_size_raw!(1);
let (old_value_ptr, old_value_size) = expect_pointer_size_raw!(2);
let (value_ptr, value_size) = expect_pointer_size_raw!(3);
HostVm::ExternalOffchainStorageSet(ExternalOffchainStorageSet {
key_ptr,
key_size,
value: Some((value_ptr, value_size)),
old_value: Some((old_value_ptr, old_value_size)),
inner: self.inner,
})
} else {
HostVm::ReadyToRun(ReadyToRun {
inner: self.inner,
resume_value: Some(vm::WasmValue::I32(0)),
})
}
}
HostFunction::ext_offchain_local_storage_get_version_1 => {
if expect_offchain_storage_kind!(0) {
let (key_ptr, key_size) = expect_pointer_size_raw!(1);
HostVm::ExternalOffchainStorageGet(ExternalOffchainStorageGet {
key_ptr,
key_size,
calling: id,
inner: self.inner,
})
} else {
self.inner
.alloc_write_and_return_pointer_size(host_fn.name(), iter::once(&[0]))
}
}
HostFunction::ext_offchain_local_storage_clear_version_1 => {
if expect_offchain_storage_kind!(0) {
let (key_ptr, key_size) = expect_pointer_size_raw!(1);
HostVm::ExternalOffchainStorageSet(ExternalOffchainStorageSet {
key_ptr,
key_size,
value: None,
old_value: None,
inner: self.inner,
})
} else {
HostVm::ReadyToRun(ReadyToRun {
inner: self.inner,
resume_value: None,
})
}
}
HostFunction::ext_offchain_http_request_start_version_1 => host_fn_not_implemented!(),
HostFunction::ext_offchain_http_request_add_header_version_1 => {
host_fn_not_implemented!()
}
HostFunction::ext_offchain_http_request_write_body_version_1 => {
host_fn_not_implemented!()
}
HostFunction::ext_offchain_http_response_wait_version_1 => host_fn_not_implemented!(),
HostFunction::ext_offchain_http_response_headers_version_1 => {
host_fn_not_implemented!()
}
HostFunction::ext_offchain_http_response_read_body_version_1 => {
host_fn_not_implemented!()
}
HostFunction::ext_trie_blake2_256_root_version_1
| HostFunction::ext_trie_blake2_256_root_version_2
| HostFunction::ext_trie_keccak_256_root_version_1
| HostFunction::ext_trie_keccak_256_root_version_2 => {
let state_version = if matches!(
host_fn,
HostFunction::ext_trie_blake2_256_root_version_2
| HostFunction::ext_trie_keccak_256_root_version_2
) {
expect_state_version!(1)
} else {
TrieEntryVersion::V0
};
let result = {
let input = expect_pointer_size!(0);
let parsing_result: Result<_, nom::Err<(&[u8], nom::error::ErrorKind)>> =
nom::Parser::parse(
&mut nom::combinator::all_consuming(nom::combinator::flat_map(
crate::util::nom_scale_compact_usize,
|num_elems| {
nom::multi::many_m_n(
num_elems,
num_elems,
(
nom::combinator::flat_map(
crate::util::nom_scale_compact_usize,
nom::bytes::streaming::take,
),
nom::combinator::flat_map(
crate::util::nom_scale_compact_usize,
nom::bytes::streaming::take,
),
),
)
},
)),
input.as_ref(),
)
.map(|(_, parse_result)| parse_result);
match parsing_result {
Ok(elements) => Ok(trie::trie_root(
state_version,
if matches!(
host_fn,
HostFunction::ext_trie_blake2_256_root_version_1
| HostFunction::ext_trie_blake2_256_root_version_2
) {
trie::HashFunction::Blake2
} else {
trie::HashFunction::Keccak256
},
&elements[..],
)),
Err(_) => Err(()),
}
};
match result {
Ok(out) => self
.inner
.alloc_write_and_return_pointer(host_fn.name(), iter::once(&out)),
Err(()) => HostVm::Error {
error: Error::ParamDecodeError,
prototype: self.inner.into_prototype(),
},
}
}
HostFunction::ext_trie_blake2_256_ordered_root_version_1
| HostFunction::ext_trie_blake2_256_ordered_root_version_2
| HostFunction::ext_trie_keccak_256_ordered_root_version_1
| HostFunction::ext_trie_keccak_256_ordered_root_version_2 => {
let state_version = if matches!(
host_fn,
HostFunction::ext_trie_blake2_256_ordered_root_version_2
| HostFunction::ext_trie_keccak_256_ordered_root_version_2
) {
expect_state_version!(1)
} else {
TrieEntryVersion::V0
};
let result = {
let input = expect_pointer_size!(0);
let parsing_result: Result<_, nom::Err<(&[u8], nom::error::ErrorKind)>> =
nom::Parser::parse(
&mut nom::combinator::all_consuming(nom::combinator::flat_map(
crate::util::nom_scale_compact_usize,
|num_elems| {
nom::multi::many_m_n(
num_elems,
num_elems,
nom::combinator::flat_map(
crate::util::nom_scale_compact_usize,
nom::bytes::streaming::take,
),
)
},
)),
input.as_ref(),
)
.map(|(_, parse_result)| parse_result);
match parsing_result {
Ok(elements) => Ok(trie::ordered_root(
state_version,
if matches!(
host_fn,
HostFunction::ext_trie_blake2_256_ordered_root_version_1
| HostFunction::ext_trie_blake2_256_ordered_root_version_2
) {
trie::HashFunction::Blake2
} else {
trie::HashFunction::Keccak256
},
&elements[..],
)),
Err(_) => Err(()),
}
};
match result {
Ok(out) => self
.inner
.alloc_write_and_return_pointer(host_fn.name(), iter::once(&out)),
Err(()) => HostVm::Error {
error: Error::ParamDecodeError,
prototype: self.inner.into_prototype(),
},
}
}
HostFunction::ext_trie_blake2_256_verify_proof_version_1 => host_fn_not_implemented!(),
HostFunction::ext_trie_blake2_256_verify_proof_version_2 => host_fn_not_implemented!(),
HostFunction::ext_trie_keccak_256_verify_proof_version_1 => host_fn_not_implemented!(),
HostFunction::ext_trie_keccak_256_verify_proof_version_2 => host_fn_not_implemented!(),
HostFunction::ext_misc_print_num_version_1 => {
let num = match params[0] {
vm::WasmValue::I64(v) => u64::from_ne_bytes(v.to_ne_bytes()),
_ => unreachable!(),
};
HostVm::LogEmit(LogEmit {
inner: self.inner,
log_entry: LogEmitInner::Num(num),
})
}
HostFunction::ext_misc_print_utf8_version_1 => {
let (str_ptr, str_size) = expect_pointer_size_raw!(0);
let utf8_check = str::from_utf8(
self.inner
.vm
.read_memory(str_ptr, str_size)
.unwrap_or_else(|_| unreachable!())
.as_ref(),
)
.map(|_| ());
if let Err(error) = utf8_check {
return HostVm::Error {
error: Error::Utf8Error {
function: host_fn.name(),
param_num: 2,
error,
},
prototype: self.inner.into_prototype(),
};
}
HostVm::LogEmit(LogEmit {
inner: self.inner,
log_entry: LogEmitInner::Utf8 { str_ptr, str_size },
})
}
HostFunction::ext_misc_print_hex_version_1 => {
let (data_ptr, data_size) = expect_pointer_size_raw!(0);
HostVm::LogEmit(LogEmit {
inner: self.inner,
log_entry: LogEmitInner::Hex {
data_ptr,
data_size,
},
})
}
HostFunction::ext_misc_runtime_version_version_1 => {
let (wasm_blob_ptr, wasm_blob_size) = expect_pointer_size_raw!(0);
HostVm::CallRuntimeVersion(CallRuntimeVersion {
inner: self.inner,
wasm_blob_ptr,
wasm_blob_size,
})
}
HostFunction::ext_allocator_malloc_version_1 => {
let size = expect_u32!(0);
let ptr = match self.inner.alloc(host_fn.name(), size) {
Ok(p) => p,
Err(error) => {
return HostVm::Error {
error,
prototype: self.inner.into_prototype(),
};
}
};
let ptr_i32 = i32::from_ne_bytes(ptr.to_ne_bytes());
HostVm::ReadyToRun(ReadyToRun {
resume_value: Some(vm::WasmValue::I32(ptr_i32)),
inner: self.inner,
})
}
HostFunction::ext_allocator_free_version_1 => {
let pointer = expect_u32!(0);
match self.inner.allocator.deallocate(
&mut MemAccess {
vm: MemAccessVm::Running(&mut self.inner.vm),
memory_total_pages: self.inner.common.memory_total_pages,
},
pointer,
) {
Ok(()) => {}
Err(_) => {
return HostVm::Error {
error: Error::FreeError { pointer },
prototype: self.inner.into_prototype(),
};
}
};
HostVm::ReadyToRun(ReadyToRun {
resume_value: None,
inner: self.inner,
})
}
HostFunction::ext_logging_log_version_1 => {
let log_level = expect_u32!(0);
let (target_str_ptr, target_str_size) = expect_pointer_size_raw!(1);
let target_utf8_check = str::from_utf8(
self.inner
.vm
.read_memory(target_str_ptr, target_str_size)
.unwrap_or_else(|_| unreachable!())
.as_ref(),
)
.map(|_| ());
if let Err(error) = target_utf8_check {
return HostVm::Error {
error: Error::Utf8Error {
function: host_fn.name(),
param_num: 1,
error,
},
prototype: self.inner.into_prototype(),
};
}
let (msg_str_ptr, msg_str_size) = expect_pointer_size_raw!(2);
let msg_utf8_check = str::from_utf8(
self.inner
.vm
.read_memory(msg_str_ptr, msg_str_size)
.unwrap_or_else(|_| unreachable!())
.as_ref(),
)
.map(|_| ());
if let Err(error) = msg_utf8_check {
return HostVm::Error {
error: Error::Utf8Error {
function: host_fn.name(),
param_num: 2,
error,
},
prototype: self.inner.into_prototype(),
};
}
HostVm::LogEmit(LogEmit {
inner: self.inner,
log_entry: LogEmitInner::Log {
log_level,
target_str_ptr,
target_str_size,
msg_str_ptr,
msg_str_size,
},
})
}
HostFunction::ext_logging_max_level_version_1 => {
HostVm::GetMaxLogLevel(GetMaxLogLevel { inner: self.inner })
}
HostFunction::ext_panic_handler_abort_on_panic_version_1 => {
let message = {
let message_bytes = expect_pointer_size!(0);
str::from_utf8(message_bytes.as_ref()).map(|msg| msg.to_owned())
};
match message {
Ok(message) => HostVm::Error {
error: Error::AbortOnPanic { message },
prototype: self.inner.into_prototype(),
},
Err(error) => HostVm::Error {
error: Error::Utf8Error {
function: host_fn.name(),
param_num: 0,
error,
},
prototype: self.inner.into_prototype(),
},
}
}
HostFunction::ext_transaction_index_index_version_1 => {
let _tx_ptr = expect_pointer_size_raw!(0);
HostVm::ReadyToRun(ReadyToRun {
inner: self.inner,
resume_value: None,
})
}
HostFunction::ext_transaction_index_renew_version_1 => {
let _tx_ptr = expect_pointer_size_raw!(0);
HostVm::ReadyToRun(ReadyToRun {
inner: self.inner,
resume_value: None,
})
}
}
}
}
impl fmt::Debug for ReadyToRun {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_tuple("ReadyToRun").finish()
}
}
pub struct Finished {
inner: Box<Inner>,
value_ptr: u32,
value_size: u32,
}
impl Finished {
pub fn value(&self) -> impl AsRef<[u8]> {
self.inner
.vm
.read_memory(self.value_ptr, self.value_size)
.unwrap_or_else(|_| unreachable!())
}
pub fn into_prototype(self) -> HostVmPrototype {
self.inner.into_prototype()
}
}
impl fmt::Debug for Finished {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_tuple("Finished").finish()
}
}
pub struct ExternalStorageGet {
inner: Box<Inner>,
calling: usize,
value_out_ptr: Option<u32>,
key_ptr: u32,
key_size: u32,
child_trie_ptr_size: Option<(u32, u32)>,
offset: u32,
max_size: u32,
}
impl ExternalStorageGet {
pub fn key(&self) -> impl AsRef<[u8]> {
self.inner
.vm
.read_memory(self.key_ptr, self.key_size)
.unwrap_or_else(|_| unreachable!())
}
pub fn child_trie(&self) -> Option<impl AsRef<[u8]>> {
if let Some((child_trie_ptr, child_trie_size)) = self.child_trie_ptr_size {
let child_trie = self
.inner
.vm
.read_memory(child_trie_ptr, child_trie_size)
.unwrap_or_else(|_| unreachable!());
Some(child_trie)
} else {
None
}
}
pub fn offset(&self) -> u32 {
self.offset
}
pub fn max_size(&self) -> u32 {
self.max_size
}
pub fn resume_full_value(self, value: Option<&[u8]>) -> HostVm {
if let Some(value) = value {
if usize::try_from(self.offset).unwrap_or_else(|_| unreachable!()) < value.len() {
let value_slice =
&value[usize::try_from(self.offset).unwrap_or_else(|_| unreachable!())..];
if usize::try_from(self.max_size).unwrap_or_else(|_| unreachable!())
< value_slice.len()
{
let value_slice = &value_slice
[..usize::try_from(self.max_size).unwrap_or_else(|_| unreachable!())];
self.resume(Some((value_slice, value.len())))
} else {
self.resume(Some((value_slice, value.len())))
}
} else {
self.resume(Some((&[], value.len())))
}
} else {
self.resume(None)
}
}
pub fn resume(self, value: Option<(&[u8], usize)>) -> HostVm {
self.resume_vectored(
value
.as_ref()
.map(|(value, size)| (iter::once(&value[..]), *size)),
)
}
pub fn resume_vectored(
mut self,
value: Option<(impl Iterator<Item = impl AsRef<[u8]>> + Clone, usize)>,
) -> HostVm {
let host_fn = match self.inner.common.registered_functions[self.calling] {
FunctionImport::Resolved(f) => f,
FunctionImport::Unresolved { .. } => unreachable!(),
};
match host_fn {
HostFunction::ext_storage_get_version_1
| HostFunction::ext_default_child_storage_get_version_1 => {
if let Some((value, value_total_len)) = value {
debug_assert_eq!(
value.clone().fold(0, |a, b| a + b.as_ref().len()),
value_total_len
);
let value_len_enc = util::encode_scale_compact_usize(value_total_len);
self.inner.alloc_write_and_return_pointer_size(
host_fn.name(),
iter::once(&[1][..])
.chain(iter::once(value_len_enc.as_ref()))
.map(either::Left)
.chain(value.map(either::Right)),
)
} else {
self.inner
.alloc_write_and_return_pointer_size(host_fn.name(), iter::once(&[0]))
}
}
HostFunction::ext_storage_read_version_1
| HostFunction::ext_default_child_storage_read_version_1 => {
let outcome = if let Some((value, value_total_len)) = value {
let mut remaining_max_allowed =
usize::try_from(self.max_size).unwrap_or_else(|_| unreachable!());
let mut offset = self.value_out_ptr.unwrap_or_else(|| unreachable!());
for value in value {
let value = value.as_ref();
assert!(value.len() <= remaining_max_allowed);
remaining_max_allowed -= value.len();
self.inner
.vm
.write_memory(offset, value)
.unwrap_or_else(|_| unreachable!());
offset += u32::try_from(value.len()).unwrap_or_else(|_| unreachable!());
}
Some(
u32::try_from(value_total_len).unwrap_or_else(|_| unreachable!())
- self.offset,
)
} else {
None
};
return self.inner.alloc_write_and_return_pointer_size(
host_fn.name(),
if let Some(outcome) = outcome {
either::Left(
iter::once(either::Left([1u8]))
.chain(iter::once(either::Right(outcome.to_le_bytes()))),
)
} else {
either::Right(iter::once(either::Left([0u8])))
},
);
}
HostFunction::ext_storage_exists_version_1
| HostFunction::ext_default_child_storage_exists_version_1 => {
HostVm::ReadyToRun(ReadyToRun {
inner: self.inner,
resume_value: Some(if value.is_some() {
vm::WasmValue::I32(1)
} else {
vm::WasmValue::I32(0)
}),
})
}
_ => unreachable!(),
}
}
}
impl fmt::Debug for ExternalStorageGet {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_tuple("ExternalStorageGet").finish()
}
}
pub struct ExternalStorageSet {
inner: Box<Inner>,
key_ptr: u32,
key_size: u32,
child_trie_ptr_size: Option<(u32, u32)>,
value: Option<(u32, u32)>,
}
impl ExternalStorageSet {
pub fn key(&self) -> impl AsRef<[u8]> {
self.inner
.vm
.read_memory(self.key_ptr, self.key_size)
.unwrap_or_else(|_| unreachable!())
}
pub fn child_trie(&self) -> Option<impl AsRef<[u8]>> {
match &self.child_trie_ptr_size {
Some((ptr, size)) => {
let child_trie = self
.inner
.vm
.read_memory(*ptr, *size)
.unwrap_or_else(|_| unreachable!());
Some(child_trie)
}
None => None,
}
}
pub fn value(&self) -> Option<impl AsRef<[u8]>> {
self.value.map(|(ptr, size)| {
self.inner
.vm
.read_memory(ptr, size)
.unwrap_or_else(|_| unreachable!())
})
}
pub fn state_trie_version(&self) -> TrieEntryVersion {
self.inner
.common
.runtime_version
.as_ref()
.unwrap_or_else(|| unreachable!())
.decode()
.state_version
.unwrap_or(TrieEntryVersion::V0)
}
pub fn resume(self) -> HostVm {
HostVm::ReadyToRun(ReadyToRun {
inner: self.inner,
resume_value: None,
})
}
}
impl fmt::Debug for ExternalStorageSet {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_tuple("ExternalStorageSet").finish()
}
}
pub struct ExternalStorageAppend {
inner: Box<Inner>,
key_ptr: u32,
key_size: u32,
value_ptr: u32,
value_size: u32,
}
impl ExternalStorageAppend {
pub fn key(&self) -> impl AsRef<[u8]> {
self.inner
.vm
.read_memory(self.key_ptr, self.key_size)
.unwrap_or_else(|_| unreachable!())
}
pub fn child_trie(&self) -> Option<impl AsRef<[u8]>> {
None::<&'static [u8]>
}
pub fn value(&self) -> impl AsRef<[u8]> {
self.inner
.vm
.read_memory(self.value_ptr, self.value_size)
.unwrap_or_else(|_| unreachable!())
}
pub fn resume(self) -> HostVm {
HostVm::ReadyToRun(ReadyToRun {
inner: self.inner,
resume_value: None,
})
}
}
impl fmt::Debug for ExternalStorageAppend {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_tuple("ExternalStorageAppend").finish()
}
}
pub struct ExternalStorageClearPrefix {
inner: Box<Inner>,
calling: usize,
prefix_ptr_size: Option<(u32, u32)>,
child_trie_ptr_size: Option<(u32, u32)>,
max_keys_to_remove: Option<u32>,
}
impl ExternalStorageClearPrefix {
pub fn prefix(&self) -> impl AsRef<[u8]> {
if let Some((prefix_ptr, prefix_size)) = self.prefix_ptr_size {
either::Left(
self.inner
.vm
.read_memory(prefix_ptr, prefix_size)
.unwrap_or_else(|_| unreachable!()),
)
} else {
either::Right(&[][..])
}
}
pub fn child_trie(&self) -> Option<impl AsRef<[u8]>> {
if let Some((child_trie_ptr, child_trie_size)) = self.child_trie_ptr_size {
let child_trie = self
.inner
.vm
.read_memory(child_trie_ptr, child_trie_size)
.unwrap_or_else(|_| unreachable!());
Some(child_trie)
} else {
None
}
}
pub fn max_keys_to_remove(&self) -> Option<u32> {
self.max_keys_to_remove
}
pub fn resume(self, num_cleared: u32, some_keys_remain: bool) -> HostVm {
let host_fn = match self.inner.common.registered_functions[self.calling] {
FunctionImport::Resolved(f) => f,
FunctionImport::Unresolved { .. } => unreachable!(),
};
match host_fn {
HostFunction::ext_storage_clear_prefix_version_1
| HostFunction::ext_default_child_storage_clear_prefix_version_1
| HostFunction::ext_default_child_storage_storage_kill_version_1 => {
HostVm::ReadyToRun(ReadyToRun {
inner: self.inner,
resume_value: None,
})
}
HostFunction::ext_default_child_storage_storage_kill_version_2 => {
HostVm::ReadyToRun(ReadyToRun {
inner: self.inner,
resume_value: Some(vm::WasmValue::I32(if some_keys_remain { 0 } else { 1 })),
})
}
HostFunction::ext_storage_clear_prefix_version_2
| HostFunction::ext_default_child_storage_clear_prefix_version_2
| HostFunction::ext_default_child_storage_storage_kill_version_3 => {
self.inner.alloc_write_and_return_pointer_size(
host_fn.name(),
[
either::Left(if some_keys_remain { [1u8] } else { [0u8] }),
either::Right(num_cleared.to_le_bytes()),
]
.into_iter(),
)
}
_ => unreachable!(),
}
}
}
impl fmt::Debug for ExternalStorageClearPrefix {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_tuple("ExternalStorageClearPrefix").finish()
}
}
pub struct ExternalStorageRoot {
inner: Box<Inner>,
calling: usize,
child_trie_ptr_size: Option<(u32, u32)>,
}
impl ExternalStorageRoot {
pub fn child_trie(&self) -> Option<impl AsRef<[u8]>> {
if let Some((ptr, size)) = self.child_trie_ptr_size {
let child_trie = self
.inner
.vm
.read_memory(ptr, size)
.unwrap_or_else(|_| unreachable!());
Some(child_trie)
} else {
None
}
}
pub fn resume(self, hash: &[u8; 32]) -> HostVm {
let host_fn = match self.inner.common.registered_functions[self.calling] {
FunctionImport::Resolved(f) => f,
FunctionImport::Unresolved { .. } => unreachable!(),
};
self.inner
.alloc_write_and_return_pointer_size(host_fn.name(), iter::once(hash))
}
}
impl fmt::Debug for ExternalStorageRoot {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_tuple("ExternalStorageRoot").finish()
}
}
pub struct ExternalStorageNextKey {
inner: Box<Inner>,
key_ptr: u32,
key_size: u32,
child_trie_ptr_size: Option<(u32, u32)>,
}
impl ExternalStorageNextKey {
pub fn key(&self) -> impl AsRef<[u8]> {
self.inner
.vm
.read_memory(self.key_ptr, self.key_size)
.unwrap_or_else(|_| unreachable!())
}
pub fn child_trie(&self) -> Option<impl AsRef<[u8]>> {
if let Some((child_trie_ptr, child_trie_size)) = self.child_trie_ptr_size {
let child_trie = self
.inner
.vm
.read_memory(child_trie_ptr, child_trie_size)
.unwrap_or_else(|_| unreachable!());
Some(child_trie)
} else {
None
}
}
pub fn resume(self, follow_up: Option<&[u8]>) -> HostVm {
let key = self
.inner
.vm
.read_memory(self.key_ptr, self.key_size)
.unwrap_or_else(|_| unreachable!());
match follow_up {
Some(next) => {
debug_assert!(key.as_ref() < next);
let value_len_enc = util::encode_scale_compact_usize(next.len());
drop(key);
self.inner.alloc_write_and_return_pointer_size(
HostFunction::ext_storage_next_key_version_1.name(), iter::once(&[1][..])
.chain(iter::once(value_len_enc.as_ref()))
.chain(iter::once(next)),
)
}
None => {
drop(key);
self.inner.alloc_write_and_return_pointer_size(
HostFunction::ext_storage_next_key_version_1.name(), iter::once(&[0]),
)
}
}
}
}
impl fmt::Debug for ExternalStorageNextKey {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_tuple("ExternalStorageNextKey").finish()
}
}
pub struct SignatureVerification {
inner: Box<Inner>,
algorithm: SignatureVerificationAlgorithm,
signature_ptr: u32,
public_key_ptr: u32,
message_ptr: u32,
message_size: u32,
is_batch_verification: bool,
}
enum SignatureVerificationAlgorithm {
Ed25519,
Sr25519V1,
Sr25519V2,
Ecdsa,
EcdsaPrehashed,
}
impl SignatureVerification {
pub fn message(&self) -> impl AsRef<[u8]> {
self.inner
.vm
.read_memory(self.message_ptr, self.message_size)
.unwrap_or_else(|_| unreachable!())
}
pub fn signature(&self) -> impl AsRef<[u8]> {
let signature_size = match self.algorithm {
SignatureVerificationAlgorithm::Ed25519 => 64,
SignatureVerificationAlgorithm::Sr25519V1 => 64,
SignatureVerificationAlgorithm::Sr25519V2 => 64,
SignatureVerificationAlgorithm::Ecdsa => 65,
SignatureVerificationAlgorithm::EcdsaPrehashed => 65,
};
self.inner
.vm
.read_memory(self.signature_ptr, signature_size)
.unwrap_or_else(|_| unreachable!())
}
pub fn public_key(&self) -> impl AsRef<[u8]> {
let public_key_size = match self.algorithm {
SignatureVerificationAlgorithm::Ed25519 => 32,
SignatureVerificationAlgorithm::Sr25519V1 => 32,
SignatureVerificationAlgorithm::Sr25519V2 => 32,
SignatureVerificationAlgorithm::Ecdsa => 33,
SignatureVerificationAlgorithm::EcdsaPrehashed => 33,
};
self.inner
.vm
.read_memory(self.public_key_ptr, public_key_size)
.unwrap_or_else(|_| unreachable!())
}
pub fn is_valid(&self) -> bool {
match self.algorithm {
SignatureVerificationAlgorithm::Ed25519 => {
let public_key =
ed25519_zebra::VerificationKey::try_from(self.public_key().as_ref());
if let Ok(public_key) = public_key {
let signature = ed25519_zebra::Signature::from(
<[u8; 64]>::try_from(self.signature().as_ref())
.unwrap_or_else(|_| unreachable!()),
);
public_key
.verify(&signature, self.message().as_ref())
.is_ok()
} else {
false
}
}
SignatureVerificationAlgorithm::Sr25519V1 => {
schnorrkel::PublicKey::from_bytes(self.public_key().as_ref()).map_or(false, |pk| {
pk.verify_simple_preaudit_deprecated(
b"substrate",
self.message().as_ref(),
self.signature().as_ref(),
)
.is_ok()
})
}
SignatureVerificationAlgorithm::Sr25519V2 => {
let Ok(signature) = schnorrkel::Signature::from_bytes(self.signature().as_ref())
else {
return false;
};
schnorrkel::PublicKey::from_bytes(self.public_key().as_ref()).map_or(false, |pk| {
pk.verify_simple(b"substrate", self.message().as_ref(), &signature)
.is_ok()
})
}
SignatureVerificationAlgorithm::Ecdsa => {
let data = <[u8; 32]>::try_from(
blake2_rfc::blake2b::blake2b(32, &[], self.message().as_ref()).as_bytes(),
)
.unwrap_or_else(|_| unreachable!());
let message = libsecp256k1::Message::parse(&data);
let sig_bytes = self.signature();
libsecp256k1::Signature::parse_standard_slice(&sig_bytes.as_ref()[..64])
.and_then(|sig| {
libsecp256k1::RecoveryId::parse(sig_bytes.as_ref()[64])
.and_then(|ri| libsecp256k1::recover(&message, &sig, &ri))
})
.map_or(false, |actual| {
self.public_key().as_ref()[..] == actual.serialize_compressed()[..]
})
}
SignatureVerificationAlgorithm::EcdsaPrehashed => {
let message = libsecp256k1::Message::parse(
&<[u8; 32]>::try_from(self.message().as_ref())
.unwrap_or_else(|_| unreachable!()),
);
let sig_bytes = self.signature();
if let Ok(sig) =
libsecp256k1::Signature::parse_standard_slice(&sig_bytes.as_ref()[..64])
{
if let Ok(ri) = libsecp256k1::RecoveryId::parse(sig_bytes.as_ref()[64]) {
if let Ok(actual) = libsecp256k1::recover(&message, &sig, &ri) {
self.public_key().as_ref()[..] == actual.serialize_compressed()[..]
} else {
false
}
} else {
false
}
} else {
false
}
}
}
}
pub fn verify_and_resume(self) -> HostVm {
let success = self.is_valid();
self.resume(success)
}
pub fn resume_success(self) -> HostVm {
self.resume(true)
}
pub fn resume_failed(self) -> HostVm {
self.resume(false)
}
fn resume(mut self, success: bool) -> HostVm {
debug_assert!(
!self.is_batch_verification || self.inner.signatures_batch_verification.is_some()
);
if self.is_batch_verification && !success {
self.inner.signatures_batch_verification = Some(false);
}
HostVm::ReadyToRun(ReadyToRun {
resume_value: Some(vm::WasmValue::I32(if success { 1 } else { 0 })),
inner: self.inner,
})
}
}
impl fmt::Debug for SignatureVerification {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("SignatureVerification")
.field("message", &self.message().as_ref())
.field("signature", &self.signature().as_ref())
.field("public_key", &self.public_key().as_ref())
.finish()
}
}
pub struct CallRuntimeVersion {
inner: Box<Inner>,
wasm_blob_ptr: u32,
wasm_blob_size: u32,
}
impl CallRuntimeVersion {
pub fn wasm_code(&self) -> impl AsRef<[u8]> {
self.inner
.vm
.read_memory(self.wasm_blob_ptr, self.wasm_blob_size)
.unwrap_or_else(|_| unreachable!())
}
pub fn resume(self, scale_encoded_runtime_version: Result<&[u8], ()>) -> HostVm {
if let Ok(scale_encoded_runtime_version) = scale_encoded_runtime_version {
self.inner.alloc_write_and_return_pointer_size(
HostFunction::ext_misc_runtime_version_version_1.name(),
iter::once(either::Left([1]))
.chain(iter::once(either::Right(either::Left(
util::encode_scale_compact_usize(scale_encoded_runtime_version.len()),
))))
.chain(iter::once(either::Right(either::Right(
scale_encoded_runtime_version,
)))),
)
} else {
self.inner.alloc_write_and_return_pointer_size(
HostFunction::ext_misc_runtime_version_version_1.name(),
iter::once([0]),
)
}
}
}
impl fmt::Debug for CallRuntimeVersion {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_tuple("CallRuntimeVersion").finish()
}
}
pub struct ExternalOffchainIndexSet {
inner: Box<Inner>,
key_ptr: u32,
key_size: u32,
value: Option<(u32, u32)>,
}
impl ExternalOffchainIndexSet {
pub fn key(&self) -> impl AsRef<[u8]> {
self.inner
.vm
.read_memory(self.key_ptr, self.key_size)
.unwrap_or_else(|_| unreachable!())
}
pub fn value(&self) -> Option<impl AsRef<[u8]>> {
if let Some((ptr, size)) = self.value {
Some(
self.inner
.vm
.read_memory(ptr, size)
.unwrap_or_else(|_| unreachable!()),
)
} else {
None
}
}
pub fn resume(self) -> HostVm {
HostVm::ReadyToRun(ReadyToRun {
inner: self.inner,
resume_value: None,
})
}
}
impl fmt::Debug for ExternalOffchainIndexSet {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_tuple("ExternalOffchainIndexSet").finish()
}
}
pub struct ExternalOffchainStorageSet {
inner: Box<Inner>,
key_ptr: u32,
key_size: u32,
value: Option<(u32, u32)>,
old_value: Option<(u32, u32)>,
}
impl ExternalOffchainStorageSet {
pub fn key(&self) -> impl AsRef<[u8]> {
self.inner
.vm
.read_memory(self.key_ptr, self.key_size)
.unwrap_or_else(|_| unreachable!())
}
pub fn value(&self) -> Option<impl AsRef<[u8]>> {
if let Some((ptr, size)) = self.value {
Some(
self.inner
.vm
.read_memory(ptr, size)
.unwrap_or_else(|_| unreachable!()),
)
} else {
None
}
}
pub fn old_value(&self) -> Option<impl AsRef<[u8]>> {
if let Some((ptr, size)) = self.old_value {
Some(
self.inner
.vm
.read_memory(ptr, size)
.unwrap_or_else(|_| unreachable!()),
)
} else {
None
}
}
pub fn resume(self, replaced: bool) -> HostVm {
if self.old_value.is_some() {
HostVm::ReadyToRun(ReadyToRun {
inner: self.inner,
resume_value: Some(vm::WasmValue::I32(if replaced { 1 } else { 0 })),
})
} else {
HostVm::ReadyToRun(ReadyToRun {
inner: self.inner,
resume_value: None,
})
}
}
}
impl fmt::Debug for ExternalOffchainStorageSet {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_tuple("ExternalOffchainStorageSet").finish()
}
}
pub struct ExternalOffchainStorageGet {
inner: Box<Inner>,
calling: usize,
key_ptr: u32,
key_size: u32,
}
impl ExternalOffchainStorageGet {
pub fn key(&self) -> impl AsRef<[u8]> {
self.inner
.vm
.read_memory(self.key_ptr, self.key_size)
.unwrap_or_else(|_| unreachable!())
}
pub fn resume(self, value: Option<&[u8]>) -> HostVm {
let host_fn = match self.inner.common.registered_functions[self.calling] {
FunctionImport::Resolved(f) => f,
FunctionImport::Unresolved { .. } => unreachable!(),
};
if let Some(value) = value {
let value_len_enc = util::encode_scale_compact_usize(value.len());
self.inner.alloc_write_and_return_pointer_size(
host_fn.name(),
iter::once(&[1][..])
.chain(iter::once(value_len_enc.as_ref()))
.map(either::Left)
.chain(iter::once(value).map(either::Right)),
)
} else {
self.inner
.alloc_write_and_return_pointer_size(host_fn.name(), iter::once(&[0]))
}
}
}
impl fmt::Debug for ExternalOffchainStorageGet {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_tuple("ExternalOffchainStorageGet").finish()
}
}
pub struct OffchainTimestamp {
inner: Box<Inner>,
}
impl OffchainTimestamp {
pub fn resume(self, value: u64) -> HostVm {
HostVm::ReadyToRun(ReadyToRun {
inner: self.inner,
resume_value: Some(vm::WasmValue::I64(i64::from_ne_bytes(value.to_ne_bytes()))),
})
}
}
impl fmt::Debug for OffchainTimestamp {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_tuple("OffchainTimestamp").finish()
}
}
pub struct OffchainRandomSeed {
inner: Box<Inner>,
calling: usize,
}
impl OffchainRandomSeed {
pub fn resume(self, value: [u8; 32]) -> HostVm {
let host_fn = match self.inner.common.registered_functions[self.calling] {
FunctionImport::Resolved(f) => f,
FunctionImport::Unresolved { .. } => unreachable!(),
};
self.inner
.alloc_write_and_return_pointer(host_fn.name(), iter::once(value))
}
}
impl fmt::Debug for OffchainRandomSeed {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_tuple("OffchainRandomSeed").finish()
}
}
pub struct OffchainSubmitTransaction {
inner: Box<Inner>,
calling: usize,
tx_ptr: u32,
tx_size: u32,
}
impl OffchainSubmitTransaction {
pub fn transaction(&self) -> impl AsRef<[u8]> {
self.inner
.vm
.read_memory(self.tx_ptr, self.tx_size)
.unwrap_or_else(|_| unreachable!())
}
pub fn resume(self, success: bool) -> HostVm {
let host_fn = match self.inner.common.registered_functions[self.calling] {
FunctionImport::Resolved(f) => f,
FunctionImport::Unresolved { .. } => unreachable!(),
};
self.inner.alloc_write_and_return_pointer_size(
host_fn.name(),
if success {
iter::once(&[0x00])
} else {
iter::once(&[0x01])
},
)
}
}
impl fmt::Debug for OffchainSubmitTransaction {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_tuple("OffchainSubmitTransaction").finish()
}
}
pub struct LogEmit {
inner: Box<Inner>,
log_entry: LogEmitInner,
}
enum LogEmitInner {
Num(u64),
Utf8 {
str_ptr: u32,
str_size: u32,
},
Hex {
data_ptr: u32,
data_size: u32,
},
Log {
log_level: u32,
target_str_ptr: u32,
target_str_size: u32,
msg_str_ptr: u32,
msg_str_size: u32,
},
}
impl LogEmit {
pub fn info(&'_ self) -> LogEmitInfo<'_> {
match self.log_entry {
LogEmitInner::Num(n) => LogEmitInfo::Num(n),
LogEmitInner::Utf8 { str_ptr, str_size } => LogEmitInfo::Utf8(LogEmitInfoStr {
data: Box::new(
self.inner
.vm
.read_memory(str_ptr, str_size)
.unwrap_or_else(|_| unreachable!()),
),
}),
LogEmitInner::Hex {
data_ptr,
data_size,
} => LogEmitInfo::Hex(LogEmitInfoHex {
data: Box::new(
self.inner
.vm
.read_memory(data_ptr, data_size)
.unwrap_or_else(|_| unreachable!()),
),
}),
LogEmitInner::Log {
msg_str_ptr,
msg_str_size,
target_str_ptr,
target_str_size,
log_level,
} => LogEmitInfo::Log {
log_level,
target: LogEmitInfoStr {
data: Box::new(
self.inner
.vm
.read_memory(target_str_ptr, target_str_size)
.unwrap_or_else(|_| unreachable!()),
),
},
message: LogEmitInfoStr {
data: Box::new(
self.inner
.vm
.read_memory(msg_str_ptr, msg_str_size)
.unwrap_or_else(|_| unreachable!()),
),
},
},
}
}
pub fn resume(self) -> HostVm {
HostVm::ReadyToRun(ReadyToRun {
inner: self.inner,
resume_value: None,
})
}
}
impl fmt::Debug for LogEmit {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("LogEmit")
.field("info", &self.info())
.finish()
}
}
#[derive(Debug)]
pub enum LogEmitInfo<'a> {
Num(u64),
Utf8(LogEmitInfoStr<'a>),
Hex(LogEmitInfoHex<'a>),
Log {
log_level: u32,
target: LogEmitInfoStr<'a>,
message: LogEmitInfoStr<'a>,
},
}
pub struct LogEmitInfoHex<'a> {
data: Box<dyn AsRef<[u8]> + Send + Sync + 'a>,
}
impl<'a> AsRef<[u8]> for LogEmitInfoHex<'a> {
fn as_ref(&self) -> &[u8] {
(*self.data).as_ref()
}
}
impl<'a> fmt::Debug for LogEmitInfoHex<'a> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
fmt::Debug::fmt(self.as_ref(), f)
}
}
impl<'a> fmt::Display for LogEmitInfoHex<'a> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
fmt::Debug::fmt(&hex::encode(self.as_ref()), f)
}
}
pub struct LogEmitInfoStr<'a> {
data: Box<dyn AsRef<[u8]> + Send + Sync + 'a>,
}
impl<'a> AsRef<str> for LogEmitInfoStr<'a> {
fn as_ref(&self) -> &str {
let data = (*self.data).as_ref();
str::from_utf8(data).unwrap_or_else(|_| unreachable!())
}
}
impl<'a> fmt::Debug for LogEmitInfoStr<'a> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
fmt::Debug::fmt(self.as_ref(), f)
}
}
impl<'a> fmt::Display for LogEmitInfoStr<'a> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
fmt::Display::fmt(self.as_ref(), f)
}
}
pub struct GetMaxLogLevel {
inner: Box<Inner>,
}
impl GetMaxLogLevel {
pub fn resume(self, max_level: u32) -> HostVm {
HostVm::ReadyToRun(ReadyToRun {
inner: self.inner,
resume_value: Some(vm::WasmValue::I32(i32::from_ne_bytes(
max_level.to_ne_bytes(),
))),
})
}
}
impl fmt::Debug for GetMaxLogLevel {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_tuple("GetMaxLogLevel").finish()
}
}
pub struct StartStorageTransaction {
inner: Box<Inner>,
}
impl StartStorageTransaction {
pub fn resume(self) -> HostVm {
HostVm::ReadyToRun(ReadyToRun {
inner: self.inner,
resume_value: None,
})
}
}
impl fmt::Debug for StartStorageTransaction {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_tuple("StartStorageTransaction").finish()
}
}
pub struct EndStorageTransaction {
inner: Box<Inner>,
}
impl EndStorageTransaction {
pub fn resume(self) -> HostVm {
HostVm::ReadyToRun(ReadyToRun {
inner: self.inner,
resume_value: None,
})
}
}
impl fmt::Debug for EndStorageTransaction {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_tuple("EndStorageTransaction").finish()
}
}
#[derive(Clone)]
enum FunctionImport {
Resolved(HostFunction),
Unresolved { module: String, name: String },
}
struct Inner {
vm: vm::VirtualMachine,
storage_transaction_depth: u32,
signatures_batch_verification: Option<bool>,
allocator: allocator::FreeingBumpHeapAllocator,
storage_proof_size_behavior: StorageProofSizeBehavior,
common: Box<VmCommon>,
}
impl Inner {
fn alloc_write_and_return_pointer_size(
mut self: Box<Self>,
function_name: &'static str,
data: impl Iterator<Item = impl AsRef<[u8]>> + Clone,
) -> HostVm {
let mut data_len = 0u32;
for chunk in data.clone() {
data_len =
data_len.saturating_add(u32::try_from(chunk.as_ref().len()).unwrap_or(u32::MAX));
}
let dest_ptr = match self.alloc(function_name, data_len) {
Ok(p) => p,
Err(error) => {
return HostVm::Error {
error,
prototype: self.into_prototype(),
};
}
};
let mut ptr_iter = dest_ptr;
for chunk in data {
let chunk = chunk.as_ref();
self.vm
.write_memory(ptr_iter, chunk)
.unwrap_or_else(|_| unreachable!());
ptr_iter += u32::try_from(chunk.len()).unwrap_or(u32::MAX);
}
let ret_val = (u64::from(data_len) << 32) | u64::from(dest_ptr);
let ret_val = i64::from_ne_bytes(ret_val.to_ne_bytes());
ReadyToRun {
inner: self,
resume_value: Some(vm::WasmValue::I64(ret_val)),
}
.into()
}
fn alloc_write_and_return_pointer(
mut self: Box<Self>,
function_name: &'static str,
data: impl Iterator<Item = impl AsRef<[u8]>> + Clone,
) -> HostVm {
let mut data_len = 0u32;
for chunk in data.clone() {
data_len =
data_len.saturating_add(u32::try_from(chunk.as_ref().len()).unwrap_or(u32::MAX));
}
let dest_ptr = match self.alloc(function_name, data_len) {
Ok(p) => p,
Err(error) => {
return HostVm::Error {
error,
prototype: self.into_prototype(),
};
}
};
let mut ptr_iter = dest_ptr;
for chunk in data {
let chunk = chunk.as_ref();
self.vm
.write_memory(ptr_iter, chunk)
.unwrap_or_else(|_| unreachable!());
ptr_iter += u32::try_from(chunk.len()).unwrap_or(u32::MAX);
}
let ret_val = i32::from_ne_bytes(dest_ptr.to_ne_bytes());
ReadyToRun {
inner: self,
resume_value: Some(vm::WasmValue::I32(ret_val)),
}
.into()
}
fn alloc(&mut self, function_name: &'static str, size: u32) -> Result<u32, Error> {
let dest_ptr = match self.allocator.allocate(
&mut MemAccess {
vm: MemAccessVm::Running(&mut self.vm),
memory_total_pages: self.common.memory_total_pages,
},
size,
) {
Ok(p) => p,
Err(_) => {
return Err(Error::OutOfMemory {
function: function_name,
requested_size: size,
});
}
};
let last_byte_memory_page = HeapPages::new((dest_ptr + size - 1) / (64 * 1024));
let current_num_pages = self.vm.memory_size();
debug_assert!(current_num_pages <= self.common.memory_total_pages);
if current_num_pages <= last_byte_memory_page {
let to_grow = last_byte_memory_page - current_num_pages + HeapPages::new(1);
self.vm
.grow_memory(to_grow)
.unwrap_or_else(|_| unreachable!());
}
Ok(dest_ptr)
}
fn into_prototype(self) -> HostVmPrototype {
HostVmPrototype {
vm_proto: self.vm.into_prototype(),
common: self.common,
}
}
}
#[derive(Debug, derive_more::From, derive_more::Display, derive_more::Error, Clone)]
pub enum NewErr {
#[display("{_0}")]
BadFormat(ModuleFormatError),
#[display("{_0}")]
VirtualMachine(vm::NewErr),
#[display("Error in runtime spec Wasm sections: {_0}")]
RuntimeVersion(FindEmbeddedRuntimeVersionError),
#[display("Error while calling Core_version: {_0}")]
CoreVersion(CoreVersionError),
HeapBaseNotFound,
MemoryMaxSizeTooLow,
}
#[derive(Debug, derive_more::Display, derive_more::Error, Clone)]
pub enum FindEmbeddedRuntimeVersionError {
#[display("{_0}")]
FindSections(FindEncodedEmbeddedRuntimeVersionApisError),
RuntimeVersionDecode,
#[display("{_0}")]
RuntimeApisDecode(CoreVersionApisFromSliceErr),
}
#[derive(Debug, Clone, derive_more::From, derive_more::Display, derive_more::Error)]
pub enum StartErr {
#[display("{_0}")]
VirtualMachine(vm::StartErr),
DataSizeOverflow,
}
#[derive(Debug, Clone, derive_more::Display, derive_more::Error)]
pub enum Error {
#[display("{_0}")]
Trap(vm::Trap),
#[display("Runtime has aborted: {message:?}")]
AbortOnPanic {
message: String,
},
#[display("A non-I64 value has been returned: {actual:?}")]
BadReturnValue {
actual: Option<vm::ValueType>,
},
#[display("The pointer and size returned by the function are invalid")]
ReturnedPtrOutOfRange {
pointer: u32,
size: u32,
memory_size: u32,
},
#[display("Called unresolved function `{module_name}`:`{function}`")]
UnresolvedFunctionCalled {
function: String,
module_name: String,
},
ParamDecodeError,
#[display(
"Bad pointer for parameter of index {param_num} of {function}: 0x{pointer:x}, \
len = 0x{length:x}"
)]
ParamOutOfRange {
function: &'static str,
param_num: usize,
pointer: u32,
length: u32,
},
#[display("UTF-8 error for parameter of index {param_num} of {function}: {error}")]
Utf8Error {
function: &'static str,
param_num: usize,
#[error(source)]
error: core::str::Utf8Error,
},
#[display("Attempted to end a transaction while none is in progress")]
NoActiveTransaction,
#[display("Execution returned with a pending storage transaction")]
FinishedWithPendingTransaction,
#[display("Out of memory allocating 0x{requested_size:x} bytes during {function}")]
OutOfMemory {
function: &'static str,
requested_size: u32,
},
#[display("Bad pointer passed to ext_allocator_free_version_1: 0x{pointer:x}")]
FreeError {
pointer: u32,
},
#[display(
"Mismatch between the state trie version provided as parameter ({parameter:?}) and \
the state trie version found in the runtime specification ({specification:?})."
)]
StateVersionMismatch {
parameter: TrieEntryVersion,
specification: TrieEntryVersion,
},
#[display(
"Called `ext_default_child_storage_root_version_1` or
`ext_default_child_storage_root_version_2` on a child trie that doesn't exist."
)]
ChildStorageRootTrieDoesntExist,
BatchVerifyWithoutStarting,
AlreadyBatchVerify,
NoBatchVerify,
#[display("Host function not implemented: {function}")]
HostFunctionNotImplemented {
function: &'static str,
},
}
struct MemAccess<'a> {
vm: MemAccessVm<'a>,
memory_total_pages: HeapPages,
}
enum MemAccessVm<'a> {
Prepare(&'a mut vm::Prepare),
Running(&'a mut vm::VirtualMachine),
}
impl<'a> allocator::Memory for MemAccess<'a> {
fn read_le_u64(&self, ptr: u32) -> Result<u64, allocator::Error> {
if (ptr + 8) > u32::from(self.memory_total_pages) * 64 * 1024 {
return Err(allocator::Error::Other);
}
let accessed_memory_page_start = HeapPages::new(ptr / (64 * 1024));
let accessed_memory_page_end = HeapPages::new((ptr + 7) / (64 * 1024));
let current_num_pages = match self.vm {
MemAccessVm::Prepare(ref vm) => vm.memory_size(),
MemAccessVm::Running(ref vm) => vm.memory_size(),
};
debug_assert!(current_num_pages <= self.memory_total_pages);
if accessed_memory_page_end < current_num_pages {
match self.vm {
MemAccessVm::Prepare(ref vm) => {
let bytes = vm.read_memory(ptr, 8).unwrap_or_else(|_| unreachable!());
Ok(u64::from_le_bytes(
<[u8; 8]>::try_from(bytes.as_ref()).unwrap_or_else(|_| unreachable!()),
))
}
MemAccessVm::Running(ref vm) => {
let bytes = vm.read_memory(ptr, 8).unwrap_or_else(|_| unreachable!());
Ok(u64::from_le_bytes(
<[u8; 8]>::try_from(bytes.as_ref()).unwrap_or_else(|_| unreachable!()),
))
}
}
} else if accessed_memory_page_start < current_num_pages {
match self.vm {
MemAccessVm::Prepare(ref vm) => {
let partial_bytes = vm
.read_memory(ptr, u32::from(current_num_pages) * 64 * 1024 - ptr)
.unwrap_or_else(|_| unreachable!());
let partial_bytes = partial_bytes.as_ref();
debug_assert!(partial_bytes.len() < 8);
let mut out = [0; 8];
out[..partial_bytes.len()].copy_from_slice(partial_bytes);
Ok(u64::from_le_bytes(out))
}
MemAccessVm::Running(ref vm) => {
let partial_bytes = vm
.read_memory(ptr, u32::from(current_num_pages) * 64 * 1024 - ptr)
.unwrap_or_else(|_| unreachable!());
let partial_bytes = partial_bytes.as_ref();
debug_assert!(partial_bytes.len() < 8);
let mut out = [0; 8];
out[..partial_bytes.len()].copy_from_slice(partial_bytes);
Ok(u64::from_le_bytes(out))
}
}
} else {
Ok(0)
}
}
fn write_le_u64(&mut self, ptr: u32, val: u64) -> Result<(), allocator::Error> {
if (ptr + 8) > u32::from(self.memory_total_pages) * 64 * 1024 {
return Err(allocator::Error::Other);
}
let bytes = val.to_le_bytes();
let written_memory_page = HeapPages::new((ptr + 7) / (64 * 1024));
let current_num_pages = match self.vm {
MemAccessVm::Prepare(ref vm) => vm.memory_size(),
MemAccessVm::Running(ref vm) => vm.memory_size(),
};
debug_assert!(current_num_pages <= self.memory_total_pages);
if current_num_pages <= written_memory_page {
let to_grow = written_memory_page - current_num_pages + HeapPages::new(1);
match self.vm {
MemAccessVm::Prepare(ref mut vm) => {
vm.grow_memory(to_grow).unwrap_or_else(|_| unreachable!())
}
MemAccessVm::Running(ref mut vm) => {
vm.grow_memory(to_grow).unwrap_or_else(|_| unreachable!())
}
}
}
match self.vm {
MemAccessVm::Prepare(ref mut vm) => vm
.write_memory(ptr, &bytes)
.unwrap_or_else(|_| unreachable!()),
MemAccessVm::Running(ref mut vm) => vm
.write_memory(ptr, &bytes)
.unwrap_or_else(|_| unreachable!()),
}
Ok(())
}
fn size(&self) -> u32 {
u32::from(self.memory_total_pages)
.saturating_mul(64)
.saturating_mul(1024)
}
}