use std::ops::Deref;
use std::ops::DerefMut;
use fuel_asm::PanicReason;
use fuel_asm::Word;
use fuel_types::ContractId;
use crate::consts::MEM_SIZE;
use crate::consts::VM_MAX_RAM;
use crate::prelude::Bug;
use crate::prelude::BugId;
use crate::prelude::BugVariant;
use crate::prelude::RuntimeError;
pub mod reg_key;
#[derive(Clone)]
pub struct CheckedMemRange(core::ops::Range<usize>);
#[derive(Clone)]
pub struct CheckedMemConstLen<const LEN: usize>(CheckedMemRange);
#[derive(Clone)]
pub struct CheckedMemValue<T>(CheckedMemRange, core::marker::PhantomData<T>);
impl<T> CheckedMemValue<T> {
pub fn new<const SIZE: usize>(address: Word) -> Result<Self, RuntimeError> {
Ok(Self(
CheckedMemRange::new_const::<SIZE>(address)?,
core::marker::PhantomData,
))
}
pub fn try_from(self, memory: &[u8; MEM_SIZE]) -> Result<T, RuntimeError>
where
T: for<'a> TryFrom<&'a [u8]>,
RuntimeError: for<'a> From<<T as TryFrom<&'a [u8]>>::Error>,
{
Ok(T::try_from(&memory[self.0 .0])?)
}
pub fn start(&self) -> usize {
self.0.start()
}
pub fn end(&self) -> usize {
self.0.end()
}
#[cfg(test)]
pub fn inspect(self, memory: &[u8; MEM_SIZE]) -> T
where
T: std::io::Write + Default,
{
let mut t = T::default();
t.write_all(&memory[self.0 .0]).unwrap();
t
}
}
impl CheckedMemRange {
const DEFAULT_CONSTRAINT: core::ops::Range<Word> = 0..VM_MAX_RAM;
pub fn new_const<const SIZE: usize>(address: Word) -> Result<Self, RuntimeError> {
Self::new(address, SIZE)
}
pub fn new(address: Word, size: usize) -> Result<Self, RuntimeError> {
Self::new_inner(address, size, Self::DEFAULT_CONSTRAINT)
}
pub fn new_with_constraint(
address: Word,
size: usize,
constraint: core::ops::Range<Word>,
) -> Result<Self, RuntimeError> {
if constraint.end > VM_MAX_RAM {
return Err(Bug::new(BugId::ID009, BugVariant::InvalidMemoryConstraint).into());
}
Self::new_inner(address, size, constraint)
}
fn new_inner(address: Word, size: usize, constraint: core::ops::Range<Word>) -> Result<Self, RuntimeError> {
let (end, of) = (address as usize).overflowing_add(size);
let range = address as usize..end;
if of || range.is_empty() || !constraint.contains(&((range.end - 1) as Word)) {
return Err(PanicReason::MemoryOverflow.into());
}
Ok(Self(range))
}
pub fn start(&self) -> usize {
self.0.start
}
pub fn end(&self) -> usize {
self.0.end
}
pub fn shrink_end(&mut self, by: usize) {
self.0 = self.0.start..self.0.end.saturating_sub(by);
}
pub fn grow_start(&mut self, by: usize) {
self.0 = self.0.start.saturating_add(by)..self.0.end;
}
pub fn read(self, memory: &[u8; MEM_SIZE]) -> &[u8] {
&memory[self.0]
}
pub fn write(self, memory: &mut [u8; MEM_SIZE]) -> &mut [u8] {
&mut memory[self.0]
}
}
impl<const LEN: usize> CheckedMemConstLen<LEN> {
pub fn new(address: Word) -> Result<Self, RuntimeError> {
Ok(Self(CheckedMemRange::new_const::<LEN>(address)?))
}
pub fn new_with_constraint(address: Word, constraint: core::ops::Range<Word>) -> Result<Self, RuntimeError> {
assert!(constraint.end <= VM_MAX_RAM, "Constraint end must be <= VM_MAX_RAM.");
Ok(Self(CheckedMemRange::new_inner(address, LEN, constraint)?))
}
pub fn read(self, memory: &[u8; MEM_SIZE]) -> &[u8; LEN] {
(&memory[self.0 .0])
.try_into()
.expect("This is always correct as the address and LEN are checked on construction.")
}
pub fn write(self, memory: &mut [u8; MEM_SIZE]) -> &mut [u8; LEN] {
(&mut memory[self.0 .0])
.try_into()
.expect("This is always correct as the address and LEN are checked on construction.")
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct InstructionLocation {
pub context: Option<ContractId>,
pub offset: u64,
}
impl<const LEN: usize> Deref for CheckedMemConstLen<LEN> {
type Target = CheckedMemRange;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl<const LEN: usize> DerefMut for CheckedMemConstLen<LEN> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}