use crate::{
runtime::RunFallibleError, ActorTerminationReason, BackendExternalities, BackendSyscallError,
TerminationReason, TrapExplanation, UnrecoverableMemoryError,
};
use alloc::vec::Vec;
use core::{
fmt::Debug,
marker::PhantomData,
mem,
mem::{size_of, MaybeUninit},
result::Result,
slice,
};
use gear_core::{
buffer::{RuntimeBuffer, RuntimeBufferSizeError},
gas::GasLeft,
memory::{Memory, MemoryError, MemoryInterval},
};
use gear_core_errors::MemoryError as FallibleMemoryError;
use scale_info::scale::{self, Decode, DecodeAll, Encode, MaxEncodedLen};
#[derive(Debug, Clone, Encode, Decode)]
#[codec(crate = scale)]
pub enum ProcessAccessError {
OutOfBounds,
GasLimitExceeded,
GasAllowanceExceeded,
}
#[derive(Debug, Clone, derive_more::From)]
pub enum MemoryAccessError {
Memory(MemoryError),
ProcessAccess(ProcessAccessError),
RuntimeBuffer(RuntimeBufferSizeError),
Decode,
}
impl BackendSyscallError for MemoryAccessError {
fn into_termination_reason(self) -> TerminationReason {
match self {
MemoryAccessError::ProcessAccess(ProcessAccessError::OutOfBounds)
| MemoryAccessError::Memory(MemoryError::AccessOutOfBounds) => {
TrapExplanation::UnrecoverableExt(
UnrecoverableMemoryError::AccessOutOfBounds.into(),
)
.into()
}
MemoryAccessError::RuntimeBuffer(RuntimeBufferSizeError) => {
TrapExplanation::UnrecoverableExt(
UnrecoverableMemoryError::RuntimeAllocOutOfBounds.into(),
)
.into()
}
MemoryAccessError::ProcessAccess(ProcessAccessError::GasLimitExceeded) => {
TrapExplanation::GasLimitExceeded.into()
}
MemoryAccessError::ProcessAccess(ProcessAccessError::GasAllowanceExceeded) => {
ActorTerminationReason::GasAllowanceExceeded
}
MemoryAccessError::Decode => unreachable!(),
}
.into()
}
fn into_run_fallible_error(self) -> RunFallibleError {
match self {
MemoryAccessError::Memory(MemoryError::AccessOutOfBounds)
| MemoryAccessError::ProcessAccess(ProcessAccessError::OutOfBounds) => {
RunFallibleError::FallibleExt(FallibleMemoryError::AccessOutOfBounds.into())
}
MemoryAccessError::RuntimeBuffer(RuntimeBufferSizeError) => {
RunFallibleError::FallibleExt(FallibleMemoryError::RuntimeAllocOutOfBounds.into())
}
e => RunFallibleError::TerminationReason(e.into_termination_reason()),
}
}
}
pub trait MemoryAccessRecorder {
fn register_read(&mut self, ptr: u32, size: u32) -> WasmMemoryRead;
fn register_read_as<T: Sized>(&mut self, ptr: u32) -> WasmMemoryReadAs<T>;
fn register_read_decoded<T: Decode + MaxEncodedLen>(
&mut self,
ptr: u32,
) -> WasmMemoryReadDecoded<T>;
fn register_write(&mut self, ptr: u32, size: u32) -> WasmMemoryWrite;
fn register_write_as<T: Sized>(&mut self, ptr: u32) -> WasmMemoryWriteAs<T>;
}
pub trait MemoryOwner {
fn read(&mut self, read: WasmMemoryRead) -> Result<Vec<u8>, MemoryAccessError>;
fn read_as<T: Sized>(&mut self, read: WasmMemoryReadAs<T>) -> Result<T, MemoryAccessError>;
fn read_decoded<T: Decode + MaxEncodedLen>(
&mut self,
read: WasmMemoryReadDecoded<T>,
) -> Result<T, MemoryAccessError>;
fn write(&mut self, write: WasmMemoryWrite, buff: &[u8]) -> Result<(), MemoryAccessError>;
fn write_as<T: Sized>(
&mut self,
write: WasmMemoryWriteAs<T>,
obj: T,
) -> Result<(), MemoryAccessError>;
}
#[derive(Debug)]
pub struct MemoryAccessManager<Ext> {
pub(crate) reads: Vec<MemoryInterval>,
pub(crate) writes: Vec<MemoryInterval>,
pub(crate) _phantom: PhantomData<Ext>,
}
impl<Ext> Default for MemoryAccessManager<Ext> {
fn default() -> Self {
Self {
reads: Vec::new(),
writes: Vec::new(),
_phantom: PhantomData,
}
}
}
impl<Ext> MemoryAccessRecorder for MemoryAccessManager<Ext> {
fn register_read(&mut self, ptr: u32, size: u32) -> WasmMemoryRead {
if size > 0 {
self.reads.push(MemoryInterval { offset: ptr, size });
}
WasmMemoryRead { ptr, size }
}
fn register_read_as<T: Sized>(&mut self, ptr: u32) -> WasmMemoryReadAs<T> {
let size = size_of::<T>() as u32;
if size > 0 {
self.reads.push(MemoryInterval { offset: ptr, size });
}
WasmMemoryReadAs {
ptr,
_phantom: PhantomData,
}
}
fn register_read_decoded<T: Decode + MaxEncodedLen>(
&mut self,
ptr: u32,
) -> WasmMemoryReadDecoded<T> {
let size = T::max_encoded_len() as u32;
if size > 0 {
self.reads.push(MemoryInterval { offset: ptr, size });
}
WasmMemoryReadDecoded {
ptr,
_phantom: PhantomData,
}
}
fn register_write(&mut self, ptr: u32, size: u32) -> WasmMemoryWrite {
if size > 0 {
self.writes.push(MemoryInterval { offset: ptr, size });
}
WasmMemoryWrite { ptr, size }
}
fn register_write_as<T: Sized>(&mut self, ptr: u32) -> WasmMemoryWriteAs<T> {
let size = size_of::<T>() as u32;
if size > 0 {
self.writes.push(MemoryInterval { offset: ptr, size });
}
WasmMemoryWriteAs {
ptr,
_phantom: PhantomData,
}
}
}
impl<Ext: BackendExternalities> MemoryAccessManager<Ext> {
pub(crate) fn pre_process_memory_accesses(
&mut self,
gas_left: &mut GasLeft,
) -> Result<(), MemoryAccessError> {
if self.reads.is_empty() && self.writes.is_empty() {
return Ok(());
}
let res = Ext::pre_process_memory_accesses(&self.reads, &self.writes, gas_left);
self.reads.clear();
self.writes.clear();
res.map_err(Into::into)
}
fn read_into_buf<M: Memory>(
&mut self,
memory: &M,
ptr: u32,
buff: &mut [u8],
gas_left: &mut GasLeft,
) -> Result<(), MemoryAccessError> {
self.pre_process_memory_accesses(gas_left)?;
memory.read(ptr, buff).map_err(Into::into)
}
pub fn read<M: Memory>(
&mut self,
memory: &M,
read: WasmMemoryRead,
gas_left: &mut GasLeft,
) -> Result<Vec<u8>, MemoryAccessError> {
let buff = if read.size == 0 {
Vec::new()
} else {
let mut buff = RuntimeBuffer::try_new_default(read.size as usize)?.into_vec();
self.read_into_buf(memory, read.ptr, &mut buff, gas_left)?;
buff
};
Ok(buff)
}
pub fn read_decoded<M: Memory, T: Decode + MaxEncodedLen>(
&mut self,
memory: &M,
read: WasmMemoryReadDecoded<T>,
gas_left: &mut GasLeft,
) -> Result<T, MemoryAccessError> {
let size = T::max_encoded_len();
let buff = if size == 0 {
Vec::new()
} else {
let mut buff = RuntimeBuffer::try_new_default(size)?.into_vec();
self.read_into_buf(memory, read.ptr, &mut buff, gas_left)?;
buff
};
let decoded = T::decode_all(&mut &buff[..]).map_err(|_| MemoryAccessError::Decode)?;
Ok(decoded)
}
pub fn read_as<M: Memory, T: Sized>(
&mut self,
memory: &M,
read: WasmMemoryReadAs<T>,
gas_left: &mut GasLeft,
) -> Result<T, MemoryAccessError> {
self.pre_process_memory_accesses(gas_left)?;
read_memory_as(memory, read.ptr).map_err(Into::into)
}
pub fn write<M: Memory>(
&mut self,
memory: &mut M,
write: WasmMemoryWrite,
buff: &[u8],
gas_left: &mut GasLeft,
) -> Result<(), MemoryAccessError> {
if buff.len() != write.size as usize {
unreachable!("Backend bug error: buffer size is not equal to registered buffer size");
}
if write.size == 0 {
Ok(())
} else {
self.pre_process_memory_accesses(gas_left)?;
memory.write(write.ptr, buff).map_err(Into::into)
}
}
pub fn write_as<M: Memory, T: Sized>(
&mut self,
memory: &mut M,
write: WasmMemoryWriteAs<T>,
obj: T,
gas_left: &mut GasLeft,
) -> Result<(), MemoryAccessError> {
self.pre_process_memory_accesses(gas_left)?;
write_memory_as(memory, write.ptr, obj).map_err(Into::into)
}
}
fn write_memory_as<T: Sized>(
memory: &mut impl Memory,
ptr: u32,
obj: T,
) -> Result<(), MemoryError> {
let size = mem::size_of::<T>();
if size > 0 {
let slice = unsafe { slice::from_raw_parts(&obj as *const T as *const u8, size) };
memory.write(ptr, slice)
} else {
Ok(())
}
}
fn read_memory_as<T: Sized>(memory: &impl Memory, ptr: u32) -> Result<T, MemoryError> {
let mut buf = MaybeUninit::<T>::uninit();
let size = mem::size_of::<T>();
if size > 0 {
let mut_slice = unsafe { slice::from_raw_parts_mut(buf.as_mut_ptr() as *mut u8, size) };
memory.read(ptr, mut_slice)?;
}
Ok(unsafe { buf.assume_init() })
}
pub struct WasmMemoryReadAs<T> {
pub(crate) ptr: u32,
pub(crate) _phantom: PhantomData<T>,
}
pub struct WasmMemoryReadDecoded<T: Decode + MaxEncodedLen> {
pub(crate) ptr: u32,
pub(crate) _phantom: PhantomData<T>,
}
pub struct WasmMemoryRead {
pub(crate) ptr: u32,
pub(crate) size: u32,
}
pub struct WasmMemoryWriteAs<T> {
pub(crate) ptr: u32,
pub(crate) _phantom: PhantomData<T>,
}
pub struct WasmMemoryWrite {
pub(crate) ptr: u32,
pub(crate) size: u32,
}