From 90ecfcdaad7e4a5bdbcf80d34479129aec91fba5 Mon Sep 17 00:00:00 2001 From: Hannes Karppila Date: Tue, 20 Aug 2024 18:06:34 +0200 Subject: [PATCH 1/2] Initial work on getting rid of RegisterId madness --- fuel-vm/src/constraints/reg_key.rs | 39 +- fuel-vm/src/interpreter.rs | 11 +- fuel-vm/src/interpreter/alu.rs | 16 +- fuel-vm/src/interpreter/alu/muldiv.rs | 10 +- fuel-vm/src/interpreter/alu/wideint.rs | 8 +- fuel-vm/src/interpreter/blob.rs | 13 +- fuel-vm/src/interpreter/blockchain.rs | 28 +- fuel-vm/src/interpreter/contract.rs | 4 +- .../src/interpreter/executors/instruction.rs | 96 ++--- fuel-vm/src/interpreter/memory.rs | 401 +++++------------- .../interpreter/memory/allocation_tests.rs | 240 ----------- fuel-vm/src/interpreter/memory/stack_tests.rs | 167 -------- fuel-vm/src/interpreter/metadata.rs | 9 +- fuel-vm/src/interpreter/register.rs | 46 ++ 14 files changed, 257 insertions(+), 831 deletions(-) delete mode 100644 fuel-vm/src/interpreter/memory/stack_tests.rs create mode 100644 fuel-vm/src/interpreter/register.rs diff --git a/fuel-vm/src/constraints/reg_key.rs b/fuel-vm/src/constraints/reg_key.rs index ca80f5055e..91e4e3af39 100644 --- a/fuel-vm/src/constraints/reg_key.rs +++ b/fuel-vm/src/constraints/reg_key.rs @@ -10,14 +10,16 @@ use core::ops::{ use fuel_asm::{ PanicReason, RegId, - RegisterId, Word, }; -use crate::consts::{ - VM_REGISTER_COUNT, - VM_REGISTER_PROGRAM_COUNT, - VM_REGISTER_SYSTEM_COUNT, +use crate::{ + consts::{ + VM_REGISTER_COUNT, + VM_REGISTER_PROGRAM_COUNT, + VM_REGISTER_SYSTEM_COUNT, + }, + interpreter::register::verify_register_user_writable, }; #[cfg(test)] @@ -37,14 +39,6 @@ pub struct Reg<'r, const INDEX: u8>(&'r Word); pub struct WriteRegKey(usize); impl WriteRegKey { - /// Create a new writable register key if the index is within the bounds - /// of the writable registers. - pub fn new(k: impl Into) -> Result { - let k = k.into(); - is_register_writable(&k)?; - Ok(Self(k)) - } - /// Translate this key from an absolute register index /// to a program register index. /// @@ -55,18 +49,6 @@ impl WriteRegKey { } } -/// Check that the register is above the system registers and below the total -/// number of registers. -pub(crate) fn is_register_writable(r: &RegisterId) -> Result<(), PanicReason> { - const W_USIZE: usize = RegId::WRITABLE.to_u8() as usize; - const RANGE: core::ops::Range = W_USIZE..(W_USIZE + VM_REGISTER_PROGRAM_COUNT); - if RANGE.contains(r) { - Ok(()) - } else { - Err(PanicReason::ReservedRegisterNotWritable) - } -} - impl<'r, const INDEX: u8> RegMut<'r, INDEX> { /// Create a new mutable register reference. pub fn new(reg: &'r mut Word) -> Self { @@ -368,11 +350,12 @@ impl<'a> From> for ProgramRegistersRef<'a> { } } -impl TryFrom for WriteRegKey { +impl TryFrom for WriteRegKey { type Error = PanicReason; - fn try_from(r: RegisterId) -> Result { - Self::new(r) + fn try_from(r: RegId) -> Result { + verify_register_user_writable(r)?; + Ok(Self(r.to_u8() as usize)) } } diff --git a/fuel-vm/src/interpreter.rs b/fuel-vm/src/interpreter.rs index 2e92fd56a8..868a40d299 100644 --- a/fuel-vm/src/interpreter.rs +++ b/fuel-vm/src/interpreter.rs @@ -70,6 +70,7 @@ mod receipts; mod debug; mod ecal; +pub(crate) mod register; use crate::profiler::Profiler; @@ -224,16 +225,6 @@ impl, S, Tx, Ecal> Interpreter { } impl Interpreter { - /// Returns the current state of the registers - pub const fn registers(&self) -> &[Word] { - &self.registers - } - - /// Returns mutable access to the registers - pub fn registers_mut(&mut self) -> &mut [Word] { - &mut self.registers - } - pub(crate) fn call_stack(&self) -> &[CallFrame] { self.frames.as_slice() } diff --git a/fuel-vm/src/interpreter/alu.rs b/fuel-vm/src/interpreter/alu.rs index b279699435..2660b49b99 100644 --- a/fuel-vm/src/interpreter/alu.rs +++ b/fuel-vm/src/interpreter/alu.rs @@ -10,11 +10,11 @@ use crate::{ error::SimpleResult, }; -use fuel_asm::PanicReason; -use fuel_types::{ - RegisterId, - Word, +use fuel_asm::{ + PanicReason, + RegId, }; +use fuel_types::Word; mod muldiv; mod wideint; @@ -26,7 +26,7 @@ where /// Stores the overflowed wrapped value into RegId::OF pub(crate) fn alu_capture_overflow( &mut self, - ra: RegisterId, + ra: RegId, f: F, b: B, c: C, @@ -48,7 +48,7 @@ where /// Set RegId::OF to true and zero the result register if overflow occurred. pub(crate) fn alu_boolean_overflow( &mut self, - ra: RegisterId, + ra: RegId, f: F, b: B, c: C, @@ -69,7 +69,7 @@ where pub(crate) fn alu_error( &mut self, - ra: RegisterId, + ra: RegId, f: F, b: B, c: C, @@ -89,7 +89,7 @@ where alu_error(dest, flag.as_ref(), common, f, b, c, err_bool) } - pub(crate) fn alu_set(&mut self, ra: RegisterId, b: Word) -> SimpleResult<()> { + pub(crate) fn alu_set(&mut self, ra: RegId, b: Word) -> SimpleResult<()> { let (SystemRegisters { of, err, pc, .. }, mut w) = split_registers(&mut self.registers); let dest = &mut w[ra.try_into()?]; diff --git a/fuel-vm/src/interpreter/alu/muldiv.rs b/fuel-vm/src/interpreter/alu/muldiv.rs index 812d3753ce..e64eb3f6a5 100644 --- a/fuel-vm/src/interpreter/alu/muldiv.rs +++ b/fuel-vm/src/interpreter/alu/muldiv.rs @@ -9,11 +9,11 @@ use crate::{ error::SimpleResult, }; -use fuel_asm::PanicReason; -use fuel_types::{ - RegisterId, - Word, +use fuel_asm::{ + PanicReason, + RegId, }; +use fuel_types::Word; impl Interpreter where @@ -22,7 +22,7 @@ where /// Stores the overflowed wrapped value into RegId::OF pub(crate) fn alu_muldiv( &mut self, - ra: RegisterId, + ra: RegId, lhs: Word, rhs: Word, divider: Word, diff --git a/fuel-vm/src/interpreter/alu/wideint.rs b/fuel-vm/src/interpreter/alu/wideint.rs index 0b755ac9c5..dcee03afc2 100644 --- a/fuel-vm/src/interpreter/alu/wideint.rs +++ b/fuel-vm/src/interpreter/alu/wideint.rs @@ -3,11 +3,9 @@ use ethnum::U256; use fuel_asm::{ wideint::*, PanicReason, + RegId, }; -use fuel_types::{ - RegisterId, - Word, -}; +use fuel_types::Word; use super::super::{ internal::inc_pc, @@ -70,7 +68,7 @@ macro_rules! wideint_ops { { pub(crate) fn []( &mut self, - ra: RegisterId, + ra: RegId, b: Word, c: Word, args: CompareArgs, diff --git a/fuel-vm/src/interpreter/blob.rs b/fuel-vm/src/interpreter/blob.rs index 949f00ad45..0fb9b14490 100644 --- a/fuel-vm/src/interpreter/blob.rs +++ b/fuel-vm/src/interpreter/blob.rs @@ -1,5 +1,5 @@ use fuel_asm::{ - RegisterId, + RegId, Word, }; use fuel_storage::StorageSize; @@ -18,12 +18,9 @@ use crate::{ use super::{ internal::inc_pc, memory::copy_from_slice_zero_fill, - split_registers, GetRegMut, Interpreter, Memory, - SystemRegisters, - WriteRegKey, }; impl Interpreter @@ -34,7 +31,7 @@ where { pub(crate) fn blob_size( &mut self, - dst: RegisterId, + dst: RegId, blob_id_ptr: Word, ) -> IoResult<(), S::DataError> { let gas_cost = self @@ -51,10 +48,8 @@ where .ok_or(PanicReason::BlobNotFound)?; self.dependent_gas_charge_without_base(gas_cost, size as Word)?; - let (SystemRegisters { pc, .. }, mut w) = split_registers(&mut self.registers); - let result = &mut w[WriteRegKey::try_from(dst)?]; - *result = size as Word; - Ok(inc_pc(pc)?) + *self.write_user_register(dst)? = size as Word; + Ok(self.inc_pc()?) } pub(crate) fn blob_load_data( diff --git a/fuel-vm/src/interpreter/blockchain.rs b/fuel-vm/src/interpreter/blockchain.rs index eed5e364d9..586cbd01c9 100644 --- a/fuel-vm/src/interpreter/blockchain.rs +++ b/fuel-vm/src/interpreter/blockchain.rs @@ -52,6 +52,7 @@ use alloc::vec::Vec; use fuel_asm::{ Imm06, PanicReason, + RegId, }; use fuel_storage::{ StorageInspect, @@ -74,7 +75,6 @@ use fuel_types::{ BlockHeight, Bytes32, ContractId, - RegisterId, Word, }; @@ -254,7 +254,7 @@ where ) } - pub(crate) fn block_height(&mut self, ra: RegisterId) -> IoResult<(), S::DataError> { + pub(crate) fn block_height(&mut self, ra: RegId) -> IoResult<(), S::DataError> { let (SystemRegisters { pc, .. }, mut w) = split_registers(&mut self.registers); let result = &mut w[WriteRegKey::try_from(ra)?]; Ok(block_height(&self.context, pc, result)?) @@ -302,11 +302,7 @@ where .code_root(a, b) } - pub(crate) fn code_size( - &mut self, - ra: RegisterId, - b: Word, - ) -> IoResult<(), S::DataError> { + pub(crate) fn code_size(&mut self, ra: RegId, b: Word) -> IoResult<(), S::DataError> { let gas_cost = self.gas_costs().csiz(); // Charge only for the `base` execution. // We will charge for the contracts size in the `code_size`. @@ -341,7 +337,7 @@ where pub(crate) fn state_clear_qword( &mut self, a: Word, - rb: RegisterId, + rb: RegId, c: Word, ) -> IoResult<(), S::DataError> { let contract_id = self.internal_contract(); @@ -360,8 +356,8 @@ where pub(crate) fn state_read_word( &mut self, - ra: RegisterId, - rb: RegisterId, + ra: RegId, + rb: RegId, c: Word, ) -> IoResult<(), S::DataError> { let (SystemRegisters { fp, pc, .. }, mut w) = @@ -394,7 +390,7 @@ where pub(crate) fn state_read_qword( &mut self, a: Word, - rb: RegisterId, + rb: RegId, c: Word, d: Word, ) -> IoResult<(), S::DataError> { @@ -429,7 +425,7 @@ where pub(crate) fn state_write_word( &mut self, a: Word, - rb: RegisterId, + rb: RegId, c: Word, ) -> IoResult<(), S::DataError> { let new_storage_gas_per_byte = self.gas_costs().new_storage_per_byte(); @@ -474,7 +470,7 @@ where pub(crate) fn state_write_qword( &mut self, a: Word, - rb: RegisterId, + rb: RegId, c: Word, d: Word, ) -> IoResult<(), S::DataError> { @@ -516,11 +512,7 @@ where ) } - pub(crate) fn timestamp( - &mut self, - ra: RegisterId, - b: Word, - ) -> IoResult<(), S::DataError> { + pub(crate) fn timestamp(&mut self, ra: RegId, b: Word) -> IoResult<(), S::DataError> { let block_height = self.get_block_height()?; let (SystemRegisters { pc, .. }, mut w) = split_registers(&mut self.registers); let result = &mut w[WriteRegKey::try_from(ra)?]; diff --git a/fuel-vm/src/interpreter/contract.rs b/fuel-vm/src/interpreter/contract.rs index d0d7d8ad75..c9074a1408 100644 --- a/fuel-vm/src/interpreter/contract.rs +++ b/fuel-vm/src/interpreter/contract.rs @@ -39,7 +39,7 @@ use crate::{ }; use fuel_asm::{ PanicReason, - RegisterId, + RegId, Word, }; use fuel_storage::StorageSize; @@ -65,7 +65,7 @@ where { pub(crate) fn contract_balance( &mut self, - ra: RegisterId, + ra: RegId, b: Word, c: Word, ) -> Result<(), RuntimeError> { diff --git a/fuel-vm/src/interpreter/executors/instruction.rs b/fuel-vm/src/interpreter/executors/instruction.rs index bc7c7b8cfa..b7805b9518 100644 --- a/fuel-vm/src/interpreter/executors/instruction.rs +++ b/fuel-vm/src/interpreter/executors/instruction.rs @@ -110,7 +110,7 @@ where self.gas_charge(self.gas_costs().add())?; let (a, b, c) = add.unpack(); self.alu_capture_overflow( - a.into(), + a, u128::overflowing_add, r!(b).into(), r!(c).into(), @@ -121,7 +121,7 @@ where self.gas_charge(self.gas_costs().addi())?; let (a, b, imm) = addi.unpack(); self.alu_capture_overflow( - a.into(), + a, u128::overflowing_add, r!(b).into(), imm.into(), @@ -131,58 +131,58 @@ where Instruction::AND(and) => { self.gas_charge(self.gas_costs().and())?; let (a, b, c) = and.unpack(); - self.alu_set(a.into(), r!(b) & r!(c))?; + self.alu_set(a, r!(b) & r!(c))?; } Instruction::ANDI(andi) => { self.gas_charge(self.gas_costs().andi())?; let (a, b, imm) = andi.unpack(); - self.alu_set(a.into(), r!(b) & Word::from(imm))?; + self.alu_set(a, r!(b) & Word::from(imm))?; } Instruction::DIV(div) => { self.gas_charge(self.gas_costs().div())?; let (a, b, c) = div.unpack(); let c = r!(c); - self.alu_error(a.into(), Word::div, r!(b), c, c == 0)?; + self.alu_error(a, Word::div, r!(b), c, c == 0)?; } Instruction::DIVI(divi) => { self.gas_charge(self.gas_costs().divi())?; let (a, b, imm) = divi.unpack(); let imm = Word::from(imm); - self.alu_error(a.into(), Word::div, r!(b), imm, imm == 0)?; + self.alu_error(a, Word::div, r!(b), imm, imm == 0)?; } Instruction::EQ(eq) => { self.gas_charge(self.gas_costs().eq_())?; let (a, b, c) = eq.unpack(); - self.alu_set(a.into(), (r!(b) == r!(c)) as Word)?; + self.alu_set(a, (r!(b) == r!(c)) as Word)?; } Instruction::EXP(exp) => { self.gas_charge(self.gas_costs().exp())?; let (a, b, c) = exp.unpack(); - self.alu_boolean_overflow(a.into(), alu::exp, r!(b), r!(c))?; + self.alu_boolean_overflow(a, alu::exp, r!(b), r!(c))?; } Instruction::EXPI(expi) => { self.gas_charge(self.gas_costs().expi())?; let (a, b, imm) = expi.unpack(); let expo = u32::from(imm); - self.alu_boolean_overflow(a.into(), Word::overflowing_pow, r!(b), expo)?; + self.alu_boolean_overflow(a, Word::overflowing_pow, r!(b), expo)?; } Instruction::GT(gt) => { self.gas_charge(self.gas_costs().gt())?; let (a, b, c) = gt.unpack(); - self.alu_set(a.into(), (r!(b) > r!(c)) as Word)?; + self.alu_set(a, (r!(b) > r!(c)) as Word)?; } Instruction::LT(lt) => { self.gas_charge(self.gas_costs().lt())?; let (a, b, c) = lt.unpack(); - self.alu_set(a.into(), (r!(b) < r!(c)) as Word)?; + self.alu_set(a, (r!(b) < r!(c)) as Word)?; } Instruction::WDCM(wdcm) => { @@ -190,7 +190,7 @@ where let (a, b, c, imm) = wdcm.unpack(); let args = wideint::CompareArgs::from_imm(imm) .ok_or(PanicReason::InvalidImmediateValue)?; - self.alu_wideint_cmp_u128(a.into(), r!(b), r!(c), args)?; + self.alu_wideint_cmp_u128(a, r!(b), r!(c), args)?; } Instruction::WQCM(wqcm) => { @@ -198,7 +198,7 @@ where let (a, b, c, imm) = wqcm.unpack(); let args = wideint::CompareArgs::from_imm(imm) .ok_or(PanicReason::InvalidImmediateValue)?; - self.alu_wideint_cmp_u256(a.into(), r!(b), r!(c), args)?; + self.alu_wideint_cmp_u256(a, r!(b), r!(c), args)?; } Instruction::WDOP(wdop) => { @@ -287,7 +287,7 @@ where let (a, b, c) = mlog.unpack(); let (lhs, rhs) = (r!(b), r!(c)); self.alu_error( - a.into(), + a, |l, r| { l.checked_ilog(r) .expect("checked_ilog returned None for valid values") @@ -303,26 +303,26 @@ where self.gas_charge(self.gas_costs().mod_op())?; let (a, b, c) = mod_.unpack(); let rhs = r!(c); - self.alu_error(a.into(), Word::wrapping_rem, r!(b), rhs, rhs == 0)?; + self.alu_error(a, Word::wrapping_rem, r!(b), rhs, rhs == 0)?; } Instruction::MODI(modi) => { self.gas_charge(self.gas_costs().modi())?; let (a, b, imm) = modi.unpack(); let rhs = Word::from(imm); - self.alu_error(a.into(), Word::wrapping_rem, r!(b), rhs, rhs == 0)?; + self.alu_error(a, Word::wrapping_rem, r!(b), rhs, rhs == 0)?; } Instruction::MOVE(move_) => { self.gas_charge(self.gas_costs().move_op())?; let (a, b) = move_.unpack(); - self.alu_set(a.into(), r!(b))?; + self.alu_set(a, r!(b))?; } Instruction::MOVI(movi) => { self.gas_charge(self.gas_costs().movi())?; let (a, imm) = movi.unpack(); - self.alu_set(a.into(), Word::from(imm))?; + self.alu_set(a, Word::from(imm))?; } Instruction::MROO(mroo) => { @@ -330,7 +330,7 @@ where let (a, b, c) = mroo.unpack(); let (lhs, rhs) = (r!(b), r!(c)); self.alu_error( - a.into(), + a, |l, r| { checked_nth_root(l, r) .expect("checked_nth_root returned None for valid values") @@ -346,7 +346,7 @@ where self.gas_charge(self.gas_costs().mul())?; let (a, b, c) = mul.unpack(); self.alu_capture_overflow( - a.into(), + a, u128::overflowing_mul, r!(b).into(), r!(c).into(), @@ -357,7 +357,7 @@ where self.gas_charge(self.gas_costs().muli())?; let (a, b, imm) = muli.unpack(); self.alu_capture_overflow( - a.into(), + a, u128::overflowing_mul, r!(b).into(), imm.into(), @@ -367,7 +367,7 @@ where Instruction::MLDV(mldv) => { self.gas_charge(self.gas_costs().mldv())?; let (a, b, c, d) = mldv.unpack(); - self.alu_muldiv(a.into(), r!(b), r!(c), r!(d))?; + self.alu_muldiv(a, r!(b), r!(c), r!(d))?; } Instruction::NOOP(_noop) => { @@ -378,19 +378,19 @@ where Instruction::NOT(not) => { self.gas_charge(self.gas_costs().not())?; let (a, b) = not.unpack(); - self.alu_set(a.into(), !r!(b))?; + self.alu_set(a, !r!(b))?; } Instruction::OR(or) => { self.gas_charge(self.gas_costs().or())?; let (a, b, c) = or.unpack(); - self.alu_set(a.into(), r!(b) | r!(c))?; + self.alu_set(a, r!(b) | r!(c))?; } Instruction::ORI(ori) => { self.gas_charge(self.gas_costs().ori())?; let (a, b, imm) = ori.unpack(); - self.alu_set(a.into(), r!(b) | Word::from(imm))?; + self.alu_set(a, r!(b) | Word::from(imm))?; } Instruction::SLL(sll) => { @@ -398,7 +398,7 @@ where let (a, b, c) = sll.unpack(); self.alu_set( - a.into(), + a, if let Ok(c) = r!(c).try_into() { Word::checked_shl(r!(b), c).unwrap_or_default() } else { @@ -411,14 +411,14 @@ where self.gas_charge(self.gas_costs().slli())?; let (a, b, imm) = slli.unpack(); let rhs = u32::from(imm); - self.alu_set(a.into(), r!(b).checked_shl(rhs).unwrap_or_default())?; + self.alu_set(a, r!(b).checked_shl(rhs).unwrap_or_default())?; } Instruction::SRL(srl) => { self.gas_charge(self.gas_costs().srl())?; let (a, b, c) = srl.unpack(); self.alu_set( - a.into(), + a, if let Ok(c) = r!(c).try_into() { Word::checked_shr(r!(b), c).unwrap_or_default() } else { @@ -431,14 +431,14 @@ where self.gas_charge(self.gas_costs().srli())?; let (a, b, imm) = srli.unpack(); let rhs = u32::from(imm); - self.alu_set(a.into(), r!(b).checked_shr(rhs).unwrap_or_default())?; + self.alu_set(a, r!(b).checked_shr(rhs).unwrap_or_default())?; } Instruction::SUB(sub) => { self.gas_charge(self.gas_costs().sub())?; let (a, b, c) = sub.unpack(); self.alu_capture_overflow( - a.into(), + a, u128::overflowing_sub, r!(b).into(), r!(c).into(), @@ -449,7 +449,7 @@ where self.gas_charge(self.gas_costs().subi())?; let (a, b, imm) = subi.unpack(); self.alu_capture_overflow( - a.into(), + a, u128::overflowing_sub, r!(b).into(), imm.into(), @@ -459,13 +459,13 @@ where Instruction::XOR(xor) => { self.gas_charge(self.gas_costs().xor())?; let (a, b, c) = xor.unpack(); - self.alu_set(a.into(), r!(b) ^ r!(c))?; + self.alu_set(a, r!(b) ^ r!(c))?; } Instruction::XORI(xori) => { self.gas_charge(self.gas_costs().xori())?; let (a, b, imm) = xori.unpack(); - self.alu_set(a.into(), r!(b) ^ Word::from(imm))?; + self.alu_set(a, r!(b) ^ Word::from(imm))?; } Instruction::JI(ji) => { @@ -662,13 +662,13 @@ where Instruction::LB(lb) => { self.gas_charge(self.gas_costs().lb())?; let (a, b, imm) = lb.unpack(); - self.load_byte(a.into(), r!(b), imm.into())?; + self.load_byte(a, r!(b), imm.into())?; } Instruction::LW(lw) => { self.gas_charge(self.gas_costs().lw())?; let (a, b, imm) = lw.unpack(); - self.load_word(a.into(), r!(b), imm)?; + self.load_word(a, r!(b), imm)?; } Instruction::MCL(mcl) => { @@ -703,7 +703,7 @@ where let (a, b, c, d) = meq.unpack(); let len = r!(d); self.dependent_gas_charge(self.gas_costs().meq(), len)?; - self.memeq(a.into(), r!(b), r!(c), len)?; + self.memeq(a, r!(b), r!(c), len)?; } Instruction::SB(sb) => { @@ -721,13 +721,13 @@ where Instruction::BAL(bal) => { self.gas_charge(self.gas_costs().bal())?; let (a, b, c) = bal.unpack(); - self.contract_balance(a.into(), r!(b), r!(c))?; + self.contract_balance(a, r!(b), r!(c))?; } Instruction::BHEI(bhei) => { self.gas_charge(self.gas_costs().bhei())?; let a = bhei.unpack(); - self.block_height(a.into())?; + self.block_height(a)?; } Instruction::BHSH(bhsh) => { @@ -769,7 +769,7 @@ where Instruction::CSIZ(csiz) => { // We charge for the gas inside of the `code_size` function. let (a, b) = csiz.unpack(); - self.code_size(a.into(), r!(b))?; + self.code_size(a, r!(b))?; } Instruction::LDC(ldc) => { @@ -799,37 +799,37 @@ where Instruction::SCWQ(scwq) => { let (a, b, c) = scwq.unpack(); self.dependent_gas_charge(self.gas_costs().scwq(), r!(c))?; - self.state_clear_qword(r!(a), b.into(), r!(c))?; + self.state_clear_qword(r!(a), b, r!(c))?; } Instruction::SRW(srw) => { self.gas_charge(self.gas_costs().srw())?; let (a, b, c) = srw.unpack(); - self.state_read_word(a.into(), b.into(), r!(c))?; + self.state_read_word(a, b, r!(c))?; } Instruction::SRWQ(srwq) => { let (a, b, c, d) = srwq.unpack(); self.dependent_gas_charge(self.gas_costs().srwq(), r!(d))?; - self.state_read_qword(r!(a), b.into(), r!(c), r!(d))?; + self.state_read_qword(r!(a), b, r!(c), r!(d))?; } Instruction::SWW(sww) => { self.gas_charge(self.gas_costs().sww())?; let (a, b, c) = sww.unpack(); - self.state_write_word(r!(a), b.into(), r!(c))?; + self.state_write_word(r!(a), b, r!(c))?; } Instruction::SWWQ(swwq) => { let (a, b, c, d) = swwq.unpack(); self.dependent_gas_charge(self.gas_costs().swwq(), r!(d))?; - self.state_write_qword(r!(a), b.into(), r!(c), r!(d))?; + self.state_write_qword(r!(a), b, r!(c), r!(d))?; } Instruction::TIME(time) => { self.gas_charge(self.gas_costs().time())?; let (a, b) = time.unpack(); - self.timestamp(a.into(), r!(b))?; + self.timestamp(a, r!(b))?; } Instruction::ECK1(eck1) => { @@ -880,13 +880,13 @@ where Instruction::GM(gm) => { self.gas_charge(self.gas_costs().gm())?; let (a, imm) = gm.unpack(); - self.metadata(a.into(), imm.into())?; + self.metadata(a, imm.into())?; } Instruction::GTF(gtf) => { self.gas_charge(self.gas_costs().gtf())?; let (a, b, imm) = gtf.unpack(); - self.get_transaction_field(a.into(), r!(b), imm.into())?; + self.get_transaction_field(a, r!(b), imm.into())?; } Instruction::TR(tr) => { @@ -909,7 +909,7 @@ where Instruction::BSIZ(bsiz) => { // We charge for this inside the function. let (a, b) = bsiz.unpack(); - self.blob_size(a.into(), r!(b))?; + self.blob_size(a, r!(b))?; } Instruction::BLDD(bldd) => { diff --git a/fuel-vm/src/interpreter/memory.rs b/fuel-vm/src/interpreter/memory.rs index eb7ff51ac3..03a7c5ea00 100644 --- a/fuel-vm/src/interpreter/memory.rs +++ b/fuel-vm/src/interpreter/memory.rs @@ -2,6 +2,7 @@ use super::{ internal::inc_pc, + register::verify_register_user_writable, Interpreter, }; use crate::{ @@ -18,7 +19,6 @@ use fuel_asm::{ }; use fuel_types::{ fmt_truncated_hex, - RegisterId, Word, }; @@ -49,9 +49,6 @@ mod impl_tests; #[cfg(test)] mod allocation_tests; -#[cfg(test)] -mod stack_tests; - /// The trait for the memory. pub trait Memory: AsRef + AsMut {} @@ -484,13 +481,6 @@ impl MemoryRange { pub fn words(&self) -> Range { self.0.start as Word..self.0.end as Word } - - /// Splits range at given relative offset. Panics if offset > range length. - pub fn split_at_offset(self, at: usize) -> (Self, Self) { - let mid = self.0.start.saturating_add(at); - assert!(mid <= self.0.end); - (Self(self.0.start..mid), Self(mid..self.0.end)) - } } impl Interpreter @@ -528,22 +518,37 @@ where segment: ProgramRegistersSegment, bitmask: Imm24, ) -> SimpleResult<()> { - let ( - SystemRegisters { - sp, ssp, hp, pc, .. - }, - program_regs, - ) = split_registers(&mut self.registers); - push_selected_registers( - self.memory.as_mut(), - sp, - ssp.as_ref(), - hp.as_ref(), - pc, - &program_regs, - segment, - bitmask, - ) + let (SystemRegisters { sp, ssp, hp, .. }, program_regs) = + split_registers(&mut self.registers); + let memory: &mut MemoryInstance = self.memory.as_mut(); + let ssp = ssp.as_ref(); + let hp = hp.as_ref(); + let bitmask = bitmask.to_u32(); + + // First update the new stack pointer, as that's the only error condition + let count: u64 = bitmask.count_ones().into(); + let write_size = count + .checked_mul(WORD_SIZE as u64) + .expect("Bitmask size times 8 can never oveflow"); + let write_at = *sp; + // If this would overflow, the stack pointer update below will fail + let new_sp = write_at.saturating_add(write_size); + try_update_stack_pointer(sp, ssp, hp, new_sp, memory)?; + + // Write the registers to the stack + let mut it = memory + .write_noownerchecks(write_at, write_size)? + .chunks_exact_mut(WORD_SIZE); + for (i, reg) in program_regs.segment(segment).iter().enumerate() { + if (bitmask & (1 << i)) != 0 { + let item = it + .next() + .expect("Memory range mismatched with register count"); + item.copy_from_slice(®.to_be_bytes()); + } + } + + self.inc_pc() } pub(crate) fn pop_selected_registers( @@ -551,68 +556,74 @@ where segment: ProgramRegistersSegment, bitmask: Imm24, ) -> SimpleResult<()> { - let ( - SystemRegisters { - sp, ssp, hp, pc, .. - }, - mut program_regs, - ) = split_registers(&mut self.registers); - pop_selected_registers( - self.memory.as_mut(), - sp, - ssp.as_ref(), - hp.as_ref(), - pc, - &mut program_regs, - segment, - bitmask, - ) + let (SystemRegisters { sp, ssp, hp, .. }, mut program_regs) = + split_registers(&mut self.registers); + let memory: &mut MemoryInstance = self.memory.as_mut(); + let ssp = ssp.as_ref(); + let hp = hp.as_ref(); + let bitmask = bitmask.to_u32(); + + // First update the stack pointer, as that's the only error condition + let count: u64 = bitmask.count_ones().into(); + let size_in_stack = count + .checked_mul(WORD_SIZE as u64) + .expect("Bitmask size times 8 can never oveflow"); + let new_sp = sp + .checked_sub(size_in_stack) + .ok_or(PanicReason::MemoryOverflow)?; + try_update_stack_pointer(sp, ssp, hp, new_sp, memory)?; + + // Restore registers from the stack + let mut it = memory.read(new_sp, size_in_stack)?.chunks_exact(WORD_SIZE); + for (i, reg) in program_regs.segment_mut(segment).iter_mut().enumerate() { + if (bitmask & (1 << i)) != 0 { + let mut buf = [0u8; WORD_SIZE]; + buf.copy_from_slice(it.next().expect("Count mismatch")); + *reg = Word::from_be_bytes(buf); + } + } + + self.inc_pc() } - pub(crate) fn load_byte( - &mut self, - ra: RegisterId, - b: Word, - c: Word, - ) -> SimpleResult<()> { - let (SystemRegisters { pc, .. }, mut w) = split_registers(&mut self.registers); - let result = &mut w[WriteRegKey::try_from(ra)?]; - load_byte(self.memory.as_ref(), pc, result, b, c) + pub(crate) fn load_byte(&mut self, ra: RegId, b: Word, c: Word) -> SimpleResult<()> { + verify_register_user_writable(ra)?; + let [b] = self.memory.as_ref().read_bytes(b.saturating_add(c))?; + *self.write_user_register(ra)? = b as Word; + self.inc_pc() } - pub(crate) fn load_word( - &mut self, - ra: RegisterId, - b: Word, - c: Imm12, - ) -> SimpleResult<()> { - let (SystemRegisters { pc, .. }, mut w) = split_registers(&mut self.registers); - let result = &mut w[WriteRegKey::try_from(ra)?]; - load_word(self.memory.as_ref(), pc, result, b, c) + pub(crate) fn load_word(&mut self, ra: RegId, b: Word, c: Imm12) -> SimpleResult<()> { + verify_register_user_writable(ra)?; + let offset = u64::from(c) + .checked_mul(WORD_SIZE as u64) + .expect("u12 * 8 cannot overflow a Word"); + let addr = b.checked_add(offset).ok_or(PanicReason::MemoryOverflow)?; + let result = Word::from_be_bytes(self.memory.as_ref().read_bytes(addr)?); + *self.write_user_register(ra)? = result; + self.inc_pc() } + #[allow(clippy::cast_possible_truncation)] pub(crate) fn store_byte(&mut self, a: Word, b: Word, c: Word) -> SimpleResult<()> { let owner = self.ownership_registers(); - store_byte( - self.memory.as_mut(), - owner, - self.registers.pc_mut(), - a, - b, - c, - ) + self.memory + .as_mut() + .write_bytes(owner, a.saturating_add(c), [b as u8])?; + self.inc_pc() } pub(crate) fn store_word(&mut self, a: Word, b: Word, c: Imm12) -> SimpleResult<()> { let owner = self.ownership_registers(); - store_word( - self.memory.as_mut(), - owner, - self.registers.pc_mut(), - a, - b, - c, - ) + #[allow(clippy::arithmetic_side_effects)] + let offset = u64::from(c) + .checked_mul(WORD_SIZE as u64) + .expect("12-bits number multiplied by 8 cannot overflow a Word"); + let addr = a.saturating_add(offset); + self.memory + .as_mut() + .write_bytes(owner, addr, b.to_be_bytes())?; + self.inc_pc() } /// Expand heap by `amount` bytes. @@ -623,38 +634,51 @@ where } pub(crate) fn malloc(&mut self, a: Word) -> SimpleResult<()> { - let (SystemRegisters { hp, sp, pc, .. }, _) = - split_registers(&mut self.registers); - malloc(hp, sp.as_ref(), pc, a, self.memory.as_mut()) + let (SystemRegisters { hp, sp, .. }, _) = split_registers(&mut self.registers); + let sp = sp.as_ref(); + self.memory.as_mut().grow_heap_by(sp, hp, a)?; + self.inc_pc() } pub(crate) fn memclear(&mut self, a: Word, b: Word) -> SimpleResult<()> { let owner = self.ownership_registers(); - memclear(self.memory.as_mut(), owner, self.registers.pc_mut(), a, b) + self.memory.as_mut().write(owner, a, b)?.fill(0); + self.inc_pc() } pub(crate) fn memcopy(&mut self, a: Word, b: Word, c: Word) -> SimpleResult<()> { let owner = self.ownership_registers(); - memcopy( - self.memory.as_mut(), - owner, - self.registers.pc_mut(), - a, - b, - c, - ) + let memory: &mut MemoryInstance = self.memory.as_mut(); + let dst_range = memory.verify(a, c)?; + let src_range = memory.verify(b, c)?; + + if dst_range.start() <= src_range.start() && src_range.start() < dst_range.end() + || src_range.start() <= dst_range.start() + && dst_range.start() < src_range.end() + || dst_range.start() < src_range.end() && src_range.end() <= dst_range.end() + || src_range.start() < dst_range.end() && dst_range.end() <= src_range.end() + { + return Err(PanicReason::MemoryWriteOverlap.into()) + } + + owner.verify_ownership(&dst_range)?; + memory.memcopy_noownerchecks(a, b, c)?; + + self.inc_pc() } pub(crate) fn memeq( &mut self, - ra: RegisterId, + ra: RegId, b: Word, c: Word, d: Word, ) -> SimpleResult<()> { - let (SystemRegisters { pc, .. }, mut w) = split_registers(&mut self.registers); - let result = &mut w[WriteRegKey::try_from(ra)?]; - memeq(self.memory.as_mut(), result, pc, b, c, d) + verify_register_user_writable(ra)?; + *self.write_user_register(ra)? = (self.memory.as_ref().read(b, d)? + == self.memory.as_ref().read(c, d)?) + as Word; + self.inc_pc() } } @@ -699,197 +723,6 @@ where Ok(inc_pc(pc)?) } -#[allow(clippy::too_many_arguments)] -pub(crate) fn push_selected_registers( - memory: &mut MemoryInstance, - sp: RegMut, - ssp: Reg, - hp: Reg, - pc: RegMut, - program_regs: &ProgramRegisters, - segment: ProgramRegistersSegment, - bitmask: Imm24, -) -> SimpleResult<()> { - let bitmask = bitmask.to_u32(); - - // First update the new stack pointer, as that's the only error condition - let count: u64 = bitmask.count_ones().into(); - let write_size = count - .checked_mul(WORD_SIZE as u64) - .expect("Bitmask size times 8 can never oveflow"); - let write_at = *sp; - // If this would overflow, the stack pointer update below will fail - let new_sp = write_at.saturating_add(write_size); - try_update_stack_pointer(sp, ssp, hp, new_sp, memory)?; - - // Write the registers to the stack - let mut it = memory - .write_noownerchecks(write_at, write_size)? - .chunks_exact_mut(WORD_SIZE); - for (i, reg) in program_regs.segment(segment).iter().enumerate() { - if (bitmask & (1 << i)) != 0 { - let item = it - .next() - .expect("Memory range mismatched with register count"); - item.copy_from_slice(®.to_be_bytes()); - } - } - - Ok(inc_pc(pc)?) -} - -#[allow(clippy::too_many_arguments)] -pub(crate) fn pop_selected_registers( - memory: &mut MemoryInstance, - sp: RegMut, - ssp: Reg, - hp: Reg, - pc: RegMut, - program_regs: &mut ProgramRegisters, - segment: ProgramRegistersSegment, - bitmask: Imm24, -) -> SimpleResult<()> { - let bitmask = bitmask.to_u32(); - - // First update the stack pointer, as that's the only error condition - let count: u64 = bitmask.count_ones().into(); - let size_in_stack = count - .checked_mul(WORD_SIZE as u64) - .expect("Bitmask size times 8 can never oveflow"); - let new_sp = sp - .checked_sub(size_in_stack) - .ok_or(PanicReason::MemoryOverflow)?; - try_update_stack_pointer(sp, ssp, hp, new_sp, memory)?; - - // Restore registers from the stack - let mut it = memory.read(new_sp, size_in_stack)?.chunks_exact(WORD_SIZE); - for (i, reg) in program_regs.segment_mut(segment).iter_mut().enumerate() { - if (bitmask & (1 << i)) != 0 { - let mut buf = [0u8; WORD_SIZE]; - buf.copy_from_slice(it.next().expect("Count mismatch")); - *reg = Word::from_be_bytes(buf); - } - } - - Ok(inc_pc(pc)?) -} - -pub(crate) fn load_byte( - memory: &MemoryInstance, - pc: RegMut, - result: &mut Word, - b: Word, - c: Word, -) -> SimpleResult<()> { - let [b] = memory.read_bytes(b.saturating_add(c))?; - *result = b as Word; - Ok(inc_pc(pc)?) -} - -pub(crate) fn load_word( - memory: &MemoryInstance, - pc: RegMut, - result: &mut Word, - b: Word, - c: Imm12, -) -> SimpleResult<()> { - let offset = u64::from(c) - .checked_mul(WORD_SIZE as u64) - .expect("u12 * 8 cannot overflow a Word"); - let addr = b.checked_add(offset).ok_or(PanicReason::MemoryOverflow)?; - *result = Word::from_be_bytes(memory.read_bytes(addr)?); - Ok(inc_pc(pc)?) -} - -#[allow(clippy::cast_possible_truncation)] -pub(crate) fn store_byte( - memory: &mut MemoryInstance, - owner: OwnershipRegisters, - pc: RegMut, - a: Word, - b: Word, - c: Word, -) -> SimpleResult<()> { - memory.write_bytes(owner, a.saturating_add(c), [b as u8])?; - Ok(inc_pc(pc)?) -} - -pub(crate) fn store_word( - memory: &mut MemoryInstance, - owner: OwnershipRegisters, - pc: RegMut, - a: Word, - b: Word, - c: Imm12, -) -> SimpleResult<()> { - #[allow(clippy::arithmetic_side_effects)] - let offset = u64::from(c) - .checked_mul(WORD_SIZE as u64) - .expect("12-bits number multiplied by 8 cannot overflow a Word"); - let addr = a.saturating_add(offset); - memory.write_bytes(owner, addr, b.to_be_bytes())?; - Ok(inc_pc(pc)?) -} - -pub(crate) fn malloc( - hp: RegMut, - sp: Reg, - pc: RegMut, - amount: Word, - memory: &mut MemoryInstance, -) -> SimpleResult<()> { - memory.grow_heap_by(sp, hp, amount)?; - Ok(inc_pc(pc)?) -} - -pub(crate) fn memclear( - memory: &mut MemoryInstance, - owner: OwnershipRegisters, - pc: RegMut, - a: Word, - b: Word, -) -> SimpleResult<()> { - memory.write(owner, a, b)?.fill(0); - Ok(inc_pc(pc)?) -} - -pub(crate) fn memcopy( - memory: &mut MemoryInstance, - owner: OwnershipRegisters, - pc: RegMut, - a: Word, - b: Word, - c: Word, -) -> SimpleResult<()> { - let dst_range = memory.verify(a, c)?; - let src_range = memory.verify(b, c)?; - - if dst_range.start() <= src_range.start() && src_range.start() < dst_range.end() - || src_range.start() <= dst_range.start() && dst_range.start() < src_range.end() - || dst_range.start() < src_range.end() && src_range.end() <= dst_range.end() - || src_range.start() < dst_range.end() && dst_range.end() <= src_range.end() - { - return Err(PanicReason::MemoryWriteOverlap.into()) - } - - owner.verify_ownership(&dst_range)?; - memory.memcopy_noownerchecks(a, b, c)?; - - Ok(inc_pc(pc)?) -} - -pub(crate) fn memeq( - memory: &mut MemoryInstance, - result: &mut Word, - pc: RegMut, - b: Word, - c: Word, - d: Word, -) -> SimpleResult<()> { - *result = (memory.read(b, d)? == memory.read(c, d)?) as Word; - Ok(inc_pc(pc)?) -} - #[derive(Debug, Clone, Copy)] pub struct OwnershipRegisters { pub(crate) sp: u64, diff --git a/fuel-vm/src/interpreter/memory/allocation_tests.rs b/fuel-vm/src/interpreter/memory/allocation_tests.rs index 569c856958..9b3bc9e3cd 100644 --- a/fuel-vm/src/interpreter/memory/allocation_tests.rs +++ b/fuel-vm/src/interpreter/memory/allocation_tests.rs @@ -1,115 +1,10 @@ #![allow(clippy::arithmetic_side_effects, clippy::cast_possible_truncation)] -use alloc::vec; - use super::*; use test_case::test_case; use crate::error::PanicOrBug; -#[test_case(0, 0, 0 => Ok(0))] -#[test_case(0, 0, 1 => Err(PanicOrBug::Panic(PanicReason::MemoryOverflow)); "Underflow")] -#[test_case(10, 0, 11 => Err(PanicOrBug::Panic(PanicReason::MemoryOverflow)); "Underflow more")] -#[test_case(12, 10, 3 => Err(PanicOrBug::Panic(PanicReason::MemoryGrowthOverlap)); "Into stack")] -#[test_case(10, 10, 0 => Ok(10); "No available memory")] -#[test_case(15, 10, 6 => Err(PanicOrBug::Panic(PanicReason::MemoryGrowthOverlap)); "Insufficient memory")] -#[test_case(20, 10, 0 => Ok(20); "Zero allocation size")] -#[test_case(20, 10, 10 => Ok(10); "Allocation size equal to available memory")] -#[test_case(20, 10, 5 => Ok(15); "Allocation size smaller than available memory")] -fn test_malloc(mut hp: Word, sp: Word, a: Word) -> SimpleResult { - let mut memory = MemoryInstance::new(); - memory.hp = hp as usize; - let mut pc = 4; - malloc( - RegMut::new(&mut hp), - Reg::new(&sp), - RegMut::new(&mut pc), - a, - &mut memory, - )?; - assert_eq!(pc, 8); - - Ok(hp) -} - -#[test_case(true, 1, 10 => Ok(()); "Can clear some bytes")] -#[test_case(false, 1, 10 => Err(PanicOrBug::Panic(PanicReason::MemoryOwnership)); "No ownership")] -#[test_case(true, 0, 10 => Ok(()); "Memory range starts at 0")] -#[test_case(true, MEM_SIZE as Word - 10, 10 => Ok(()); "Memory range ends at last address")] -#[test_case(true, 1, VM_MAX_RAM + 1 => Err(PanicOrBug::Panic(PanicReason::MemoryOverflow)); "Memory range size exceeds limit")] -fn test_memclear(has_ownership: bool, a: Word, b: Word) -> SimpleResult<()> { - let mut memory: MemoryInstance = vec![1u8; MEM_SIZE].try_into().unwrap(); - let mut pc = 4; - let mut owner = OwnershipRegisters { - sp: 0, - ssp: 0, - hp: MEM_SIZE as Word, - prev_hp: MEM_SIZE as Word, - }; - if has_ownership { - owner.ssp = a; - owner.sp = a + b; - } - - memclear(&mut memory, owner, RegMut::new(&mut pc), a, b)?; - - assert_eq!(pc, 8); - let expected = vec![0u8; b as usize]; - let ab = a.checked_add(b).unwrap(); - assert_eq!(memory[a as usize..ab as usize], expected[..]); - - Ok(()) -} - -#[test_case(1, 20, 0 => Ok(()); "Can copy zero bytes")] -#[test_case(1, 20, 10 => Ok(()); "Can copy some bytes")] -#[test_case(10, 20, 10 => Ok(()); "Can copy some bytes in close range")] -#[test_case(21, 20, 10 => Err(PanicReason::MemoryWriteOverlap.into()); "b <= a < bc")] -#[test_case(14, 20, 10 => Err(PanicReason::MemoryWriteOverlap.into()); "b < ac <= bc")] -#[test_case(21, 22, 10 => Err(PanicReason::MemoryWriteOverlap.into()); "a <= b < ac")] -#[test_case(21, 20, 10 => Err(PanicReason::MemoryWriteOverlap.into()); "a < bc <= ac")] -fn test_memcopy(a: Word, b: Word, c: Word) -> SimpleResult<()> { - let mut memory: MemoryInstance = vec![1u8; MEM_SIZE].try_into().unwrap(); - memory[b as usize..b as usize + c as usize].copy_from_slice(&vec![2u8; c as usize]); - let mut pc = 4; - let owner = OwnershipRegisters::test_full_stack(); - - memcopy(&mut memory, owner, RegMut::new(&mut pc), a, b, c)?; - - assert_eq!(pc, 8); - let expected = vec![2u8; c as usize]; - assert_eq!(memory[a as usize..a as usize + c as usize], expected[..]); - - Ok(()) -} - -#[test_case(1, 20, 10 => Ok(()); "Can compare some bytes")] -#[test_case(MEM_SIZE as Word, 1, 2 => Err(PanicOrBug::Panic(PanicReason::MemoryOverflow)); "b+d > MAX_RAM")] -#[test_case(1, MEM_SIZE as Word, 2 => Err(PanicOrBug::Panic(PanicReason::MemoryOverflow)); "c+d > MAX_RAM")] -#[test_case(1, 1, VM_MAX_RAM + 1 => Err(PanicOrBug::Panic(PanicReason::MemoryOverflow)); "d > VM_MAX_RAM")] -#[test_case(u64::MAX/2, 1, u64::MAX/2 + 1 => Err(PanicOrBug::Panic(PanicReason::MemoryOverflow)); "b+d overflows")] -#[test_case(1, u64::MAX/2, u64::MAX/2 + 1 => Err(PanicOrBug::Panic(PanicReason::MemoryOverflow)); "c+d overflows")] -#[test_case(0, 0, 0 => Ok(()); "smallest input values")] -#[test_case(0, VM_MAX_RAM/2, VM_MAX_RAM/2 => Ok(()); "maximum range of addressable memory")] -fn test_memeq(b: Word, c: Word, d: Word) -> SimpleResult<()> { - let mut memory: MemoryInstance = vec![1u8; MEM_SIZE].try_into().unwrap(); - let r = (b as usize).min(MEM_SIZE) - ..((b as usize).min(MEM_SIZE) + (d as usize).min(MEM_SIZE)).min(MEM_SIZE); - memory[r].fill(2u8); - let r = (c as usize).min(MEM_SIZE) - ..((c as usize).min(MEM_SIZE) + (d as usize).min(MEM_SIZE)).min(MEM_SIZE); - memory[r].fill(2u8); - let mut pc = 4; - let mut result = 0; - - memeq(&mut memory, &mut result, RegMut::new(&mut pc), b, c, d)?; - - assert_eq!(pc, 8); - assert_eq!(result, 1); - - Ok(()) -} - #[test_case(true, 20, 0, 40, 10 => Ok(()); "Can move sp up")] #[test_case(false, 20, 0, 40, 10 => Ok(()); "Can move sp down")] #[test_case(true, u64::MAX - 10, 0, u64::MAX, 20 => Err(PanicOrBug::Panic(PanicReason::MemoryOverflow)); "Panics on overflowing addition")] @@ -149,138 +44,3 @@ fn test_stack_pointer_overflow( } Ok(()) } - -#[test_case(20, 20 => Ok(()); "Can load a byte")] -#[test_case(0, 0 => Ok(()); "handles memory loading at address 0")] -#[test_case(VM_MAX_RAM + 1, 0 => Err(PanicOrBug::Panic(PanicReason::MemoryOverflow)); "b > VM_MAX_RAM")] -#[test_case(VM_MAX_RAM - 1, 0 => Ok(()); "b eq VM_MAX_RAM - 1")] -#[test_case(0, VM_MAX_RAM + 1 => Err(PanicOrBug::Panic(PanicReason::MemoryOverflow)); "c > VM_MAX_RAM")] -#[test_case(0, VM_MAX_RAM - 1 => Ok(()); "c eq VM_MAX_RAM - 1")] -#[test_case(u32::MAX as u64, u32::MAX as u64 => Err(PanicOrBug::Panic(PanicReason::MemoryOverflow)); "b + c overflow")] -fn test_load_byte(b: Word, c: Word) -> SimpleResult<()> { - let mut memory = vec![1u8; MEM_SIZE]; - memory[((b + c) as usize).min(MEM_SIZE - 1)] = 2; - let memory: MemoryInstance = memory.into(); - let mut pc = 4; - let mut result = 0; - - load_byte(&memory, RegMut::new(&mut pc), &mut result, b, c)?; - - assert_eq!(pc, 8); - assert_eq!(result, 2); - - Ok(()) -} - -#[test_case(20, Imm12::from(20) => Ok(()); "Can load a word")] -#[test_case(VM_MAX_RAM, Imm12::from(1) => Err(PanicOrBug::Panic(PanicReason::MemoryOverflow)); "b + 8 * c gteq VM_MAX_RAM")] -fn test_load_word(b: Word, c: Imm12) -> SimpleResult<()> { - // create a mutable memory with size `MEM_SIZE` - let mut memory: MemoryInstance = vec![1u8; MEM_SIZE].try_into().unwrap(); - - // calculate start location where 8 bytes of value will be stored based on `b` and `c` - // values. - let start = (b as usize + (u64::from(c) * 8) as usize).min(MEM_SIZE - 8); - - // write 2u8 to a slice of memory (starting at the 'start' location with a length of - // 8) - memory[start..start + 8].copy_from_slice(&[2u8; 8]); - - // initialize pc to 4 and result to 0 - let mut pc = 4; - let mut result = 0; - - // read the memory from the calculated location and store it in `result`, also - // increment the `pc` by one word(8 bytes). - load_word(&memory, RegMut::new(&mut pc), &mut result, b, c)?; - - // ensure that `pc` is 8 now and the result matches [2u8; 8] i.e., 2 bytes repeated 8 - // times. - assert_eq!(pc, 8); - assert_eq!(result, Word::from_be_bytes([2u8; 8])); - - Ok(()) -} - -#[test_case(true, 20, 30, 40 => Ok(()); "Can store a byte")] -#[test_case(false, VM_MAX_RAM - 1, 100, 2 => Err(PanicOrBug::Panic(PanicReason::MemoryOverflow)); "Memory overflow on heap")] -#[test_case(false, 0, 100, VM_MAX_RAM - 1 => Err(PanicOrBug::Panic(PanicReason::MemoryOwnership)); "Memory overflow on stack")] -#[test_case(true, VM_MAX_RAM, 1, 1 => Err(PanicOrBug::Panic(PanicReason::MemoryOverflow)); "Memory overflow by address range")] -fn test_store_byte(has_ownership: bool, a: Word, b: Word, c: Word) -> SimpleResult<()> { - let mut memory: MemoryInstance = vec![1u8; MEM_SIZE].try_into().unwrap(); - let mut pc = 4; - let mut owner = OwnershipRegisters { - sp: 0, - ssp: 0, - hp: VM_MAX_RAM, - prev_hp: VM_MAX_RAM, - }; - if has_ownership { - owner.ssp = b; - owner.sp = b + c; - } - - store_byte(&mut memory, owner, RegMut::new(&mut pc), a, b, c)?; - - assert_eq!(pc, 8); - assert_eq!(memory.read_bytes(a + c), Ok([b as u8])); - - Ok(()) -} - -#[rstest::rstest] -fn test_store_byte_more( - #[values(0, 1, VM_MAX_RAM - 1, VM_MAX_RAM)] a: Word, - #[values(0, 1, 0xff, 0x100)] b: Word, - #[values(0, 1, 2)] c: Word, -) -> SimpleResult<()> { - let mut memory: MemoryInstance = vec![1u8; MEM_SIZE].try_into().unwrap(); - let mut pc = 4; - - // Full ownership in heap - let owner = OwnershipRegisters { - sp: 0, - ssp: 0, - hp: 0, - prev_hp: VM_MAX_RAM, - }; - - let is_error = a + c >= MEM_SIZE as u64; - match store_byte(&mut memory, owner, RegMut::new(&mut pc), a, b, c) { - Ok(_) => { - assert!(!is_error); - assert_eq!(memory.read_bytes(a + c), Ok([b as u8])); - } - Err(e) => { - assert!(is_error); - assert_eq!(e, PanicOrBug::Panic(PanicReason::MemoryOverflow)); - } - } - - Ok(()) -} - -#[test_case(true, 20, 30, Imm12::from(40) => Ok(()); "Can store a word")] -#[test_case(false, 20, 30, Imm12::from(40) => Err(PanicOrBug::Panic(PanicReason::MemoryOwnership)); "Fails due to not having ownership of the range")] -fn test_store_word(has_ownership: bool, a: Word, b: Word, c: Imm12) -> SimpleResult<()> { - let mut memory: MemoryInstance = vec![1u8; MEM_SIZE].try_into().unwrap(); - let mut pc = 4; - let mut owner = OwnershipRegisters { - sp: 0, - ssp: 0, - hp: VM_MAX_RAM, - prev_hp: VM_MAX_RAM, - }; - if has_ownership { - owner.ssp = b; - owner.sp = b + 8 * u64::from(c); - } - - store_word(&mut memory, owner, RegMut::new(&mut pc), a, b, c)?; - - assert_eq!(pc, 8); - let start = (a + u64::from(c) * 8) as usize; - assert_eq!(memory[start..start + 8], b.to_be_bytes()[..]); - - Ok(()) -} diff --git a/fuel-vm/src/interpreter/memory/stack_tests.rs b/fuel-vm/src/interpreter/memory/stack_tests.rs deleted file mode 100644 index c7d941303e..0000000000 --- a/fuel-vm/src/interpreter/memory/stack_tests.rs +++ /dev/null @@ -1,167 +0,0 @@ -#![allow(clippy::arithmetic_side_effects, clippy::cast_possible_truncation)] - -use alloc::vec; - -use fuel_asm::{ - Imm24, - PanicReason, -}; - -use crate::{ - constraints::reg_key::*, - consts::*, - error::PanicOrBug, - interpreter::memory::{ - pop_selected_registers, - push_selected_registers, - MemoryInstance, - }, -}; - -#[rstest::rstest] -fn test_push_pop( - #[values(ProgramRegistersSegment::Low, ProgramRegistersSegment::High)] - segment: ProgramRegistersSegment, - #[values( - 0, - 0b01, - 0b10, - 0b11, - 0b_100000_000000_000000_000000, - 0b_111100_011101_101011_001100, - 0b_111100_011101_101011_001101, - Imm24::MAX.into(), - )] - bitmask: u32, -) { - let mut memory: MemoryInstance = vec![1u8; MEM_SIZE].try_into().unwrap(); - let mut pc = 0; - let mut sp = 0; - - let orig_regs = [2; VM_REGISTER_PROGRAM_COUNT]; - let mut reg_values = orig_regs; - let mut regs = ProgramRegisters(&mut reg_values); - - let popcnt = bitmask.count_ones() as u64; - let bitmask = Imm24::new_checked(bitmask).unwrap(); - - // Save registers to stack - push_selected_registers( - &mut memory, - RegMut::new(&mut sp), - Reg::new(&0), - Reg::new(&VM_MAX_RAM), - RegMut::new(&mut pc), - ®s, - segment, - bitmask, - ) - .expect("Push failed unexpectedly"); - - assert_eq!(sp, popcnt * 8); - assert_eq!(pc, 4); - - // Clear registers - regs.0.fill(0); - - // Restore registers - pop_selected_registers( - &mut memory, - RegMut::new(&mut sp), - Reg::new(&0), - Reg::new(&VM_MAX_RAM), - RegMut::new(&mut pc), - &mut regs, - segment, - bitmask, - ) - .expect("Pop failed unexpectedly"); - - assert_eq!(sp, 0); - assert_eq!(pc, 8); - - // Make sure that the correct number of registers were restored - assert_eq!( - orig_regs - .iter() - .zip(reg_values.iter()) - .filter(|(a, b)| a == b) - .count() as u64, - popcnt - ); -} - -#[test] -fn test_push_stack_overflow() { - let mut memory: MemoryInstance = vec![1u8; MEM_SIZE].try_into().unwrap(); - let mut pc = 0; - let mut sp = 10; - let hp = 14; - - let mut reg_values = [0; VM_REGISTER_PROGRAM_COUNT]; - let regs = ProgramRegisters(&mut reg_values); - - let result = push_selected_registers( - &mut memory, - RegMut::new(&mut sp), - Reg::new(&10), - Reg::new(&hp), - RegMut::new(&mut pc), - ®s, - ProgramRegistersSegment::Low, - Imm24::new(1), - ); - - assert_eq!( - result, - Err(PanicOrBug::Panic(PanicReason::MemoryGrowthOverlap)) - ); -} - -#[test] -fn test_pop_from_empty_stack() { - let mut memory: MemoryInstance = vec![1u8; MEM_SIZE].try_into().unwrap(); - let mut pc = 0; - let mut sp = 32; - let ssp = 16; - - let mut reg_values = [0; VM_REGISTER_PROGRAM_COUNT]; - let mut regs = ProgramRegisters(&mut reg_values); - - let result = pop_selected_registers( - &mut memory, - RegMut::new(&mut sp), - Reg::new(&ssp), - Reg::new(&VM_MAX_RAM), - RegMut::new(&mut pc), - &mut regs, - ProgramRegistersSegment::Low, - Imm24::new(0b111), - ); - - assert_eq!(result, Err(PanicOrBug::Panic(PanicReason::MemoryOverflow))); -} - -#[test] -fn test_pop_sp_overflow() { - let mut memory: MemoryInstance = vec![1u8; MEM_SIZE].try_into().unwrap(); - let mut pc = 0; - let mut sp = 16; - let ssp = 0; - - let mut reg_values = [0; VM_REGISTER_PROGRAM_COUNT]; - let mut regs = ProgramRegisters(&mut reg_values); - - let result = pop_selected_registers( - &mut memory, - RegMut::new(&mut sp), - Reg::new(&ssp), - Reg::new(&VM_MAX_RAM), - RegMut::new(&mut pc), - &mut regs, - ProgramRegistersSegment::Low, - Imm24::new(0b111), - ); - - assert_eq!(result, Err(PanicOrBug::Panic(PanicReason::MemoryOverflow))); -} diff --git a/fuel-vm/src/interpreter/metadata.rs b/fuel-vm/src/interpreter/metadata.rs index 9a9955c213..3fb7a30da0 100644 --- a/fuel-vm/src/interpreter/metadata.rs +++ b/fuel-vm/src/interpreter/metadata.rs @@ -39,7 +39,6 @@ use fuel_types::{ ChainId, Immediate12, Immediate18, - RegisterId, Word, }; @@ -51,11 +50,7 @@ where M: Memory, Tx: ExecutableTransaction, { - pub(crate) fn metadata( - &mut self, - ra: RegisterId, - imm: Immediate18, - ) -> SimpleResult<()> { + pub(crate) fn metadata(&mut self, ra: RegId, imm: Immediate18) -> SimpleResult<()> { let tx_offset = self.tx_offset() as Word; let chain_id = self.chain_id(); let (SystemRegisters { pc, .. }, mut w) = split_registers(&mut self.registers); @@ -73,7 +68,7 @@ where pub(crate) fn get_transaction_field( &mut self, - ra: RegisterId, + ra: RegId, b: Word, imm: Immediate12, ) -> SimpleResult<()> { diff --git a/fuel-vm/src/interpreter/register.rs b/fuel-vm/src/interpreter/register.rs new file mode 100644 index 0000000000..f6f3e2bacb --- /dev/null +++ b/fuel-vm/src/interpreter/register.rs @@ -0,0 +1,46 @@ +use fuel_asm::{ + Instruction, + RegId, + Word, +}; +use fuel_tx::PanicReason; + +use crate::error::SimpleResult; + +use super::Interpreter; + +/// Check that the register is a general-purpose user-writable register. +pub(crate) fn verify_register_user_writable(r: RegId) -> Result<(), PanicReason> { + if r >= RegId::WRITABLE { + Ok(()) + } else { + Err(PanicReason::ReservedRegisterNotWritable) + } +} + +impl Interpreter { + /// Returns the current state of the registers + pub const fn registers(&self) -> &[Word] { + &self.registers + } + + /// Returns mutable access to the registers + pub fn registers_mut(&mut self) -> &mut [Word] { + &mut self.registers + } + + /// Writes a value to an user-writable register, causing a vm panic otherwise. + pub fn write_user_register(&mut self, reg: RegId) -> SimpleResult<&mut Word> { + verify_register_user_writable(reg)?; + Ok(&mut self.registers_mut()[reg.to_u8() as usize]) + } + + /// Increase program counter, causing a vm panic if the new value is out of bounds. + pub fn inc_pc(&mut self) -> SimpleResult<()> { + let pc = &mut self.registers[RegId::PC]; + *pc = pc + .checked_add(Instruction::SIZE as Word) + .ok_or(PanicReason::MemoryOverflow)?; + Ok(()) + } +} From 00d69486b41c00ae1769f85204bb5e4b9d1dea0c Mon Sep 17 00:00:00 2001 From: Hannes Karppila Date: Tue, 20 Aug 2024 19:43:39 +0200 Subject: [PATCH 2/2] Get rid of more WriteRegKey instances --- fuel-vm/src/interpreter/blockchain.rs | 182 +++++------- .../src/interpreter/blockchain/other_tests.rs | 49 ---- fuel-vm/src/interpreter/blockchain/test.rs | 1 - .../src/interpreter/blockchain/test/swwq.rs | 261 ------------------ fuel-vm/src/interpreter/contract.rs | 55 +--- fuel-vm/src/interpreter/metadata.rs | 132 +++------ fuel-vm/src/interpreter/metadata/tests.rs | 83 ------ fuel-vm/src/tests/blob.rs | 20 ++ fuel-vm/src/tests/memory.rs | 32 +++ 9 files changed, 170 insertions(+), 645 deletions(-) delete mode 100644 fuel-vm/src/interpreter/blockchain/test/swwq.rs delete mode 100644 fuel-vm/src/interpreter/metadata/tests.rs diff --git a/fuel-vm/src/interpreter/blockchain.rs b/fuel-vm/src/interpreter/blockchain.rs index 586cbd01c9..cf4d2fc3d6 100644 --- a/fuel-vm/src/interpreter/blockchain.rs +++ b/fuel-vm/src/interpreter/blockchain.rs @@ -72,12 +72,13 @@ use fuel_types::{ }, Address, AssetId, - BlockHeight, Bytes32, ContractId, Word, }; +use super::register::verify_register_user_writable; + #[cfg(test)] mod code_tests; #[cfg(test)] @@ -255,9 +256,13 @@ where } pub(crate) fn block_height(&mut self, ra: RegId) -> IoResult<(), S::DataError> { - let (SystemRegisters { pc, .. }, mut w) = split_registers(&mut self.registers); - let result = &mut w[WriteRegKey::try_from(ra)?]; - Ok(block_height(&self.context, pc, result)?) + verify_register_user_writable(ra)?; + let block_height = self + .context + .block_height() + .ok_or(PanicReason::TransactionValidity)?; + *self.write_user_register(ra)? = *block_height as Word; + Ok(self.inc_pc()?) } pub(crate) fn block_proposer(&mut self, a: Word) -> IoResult<(), S::DataError> { @@ -476,13 +481,6 @@ where ) -> IoResult<(), S::DataError> { let new_storage_per_byte = self.gas_costs().new_storage_per_byte(); let contract_id = self.internal_contract(); - let ( - SystemRegisters { - is, cgas, ggas, pc, .. - }, - mut w, - ) = split_registers(&mut self.registers); - let result = &mut w[WriteRegKey::try_from(rb)?]; let input = StateWriteQWord { starting_storage_key_pointer: a, @@ -496,27 +494,68 @@ where .. } = self; - state_write_qword( - &contract_id?, - storage, - memory.as_ref(), - &mut self.profiler, - new_storage_per_byte, - self.frames.last().map(|frame| frame.to()).copied(), - cgas, - ggas, - is.as_ref(), - pc, - result, - input, - ) + let contract_id: &ContractId = &contract_id?; + let memory: &MemoryInstance = memory.as_ref(); + let current_contract = self.frames.last().map(|frame| frame.to()).copied(); + let destination_key = + Bytes32::new(memory.read_bytes(input.starting_storage_key_pointer)?); + + let values = memory + .read( + input.source_pointer, + (Bytes32::LEN as Word).saturating_mul(input.num_slots), + )? + .chunks_exact(Bytes32::LEN); + + let unset_count = storage + .contract_state_insert_range(contract_id, &destination_key, values) + .map_err(RuntimeError::Storage)?; + *self.write_user_register(rb)? = unset_count as Word; + + if unset_count > 0 { + let ( + SystemRegisters { + is, cgas, ggas, pc, .. + }, + _, + ) = split_registers(&mut self.registers); + + // New data was written, charge gas for it + let profiler = ProfileGas { + pc: pc.as_ref(), + is: is.as_ref(), + current_contract, + profiler: &mut self.profiler, + }; + gas_charge( + cgas, + ggas, + profiler, + (unset_count as u64) + .saturating_mul(2) + .saturating_mul(Bytes32::LEN as u64) + .saturating_mul(new_storage_per_byte), + )?; + } + + Ok(self.inc_pc()?) } pub(crate) fn timestamp(&mut self, ra: RegId, b: Word) -> IoResult<(), S::DataError> { let block_height = self.get_block_height()?; - let (SystemRegisters { pc, .. }, mut w) = split_registers(&mut self.registers); - let result = &mut w[WriteRegKey::try_from(ra)?]; - timestamp(&self.storage, block_height, pc, result, b) + verify_register_user_writable(ra)?; + let storage: &S = &self.storage; + let b = u32::try_from(b) + .map_err(|_| PanicReason::InvalidBlockHeight)? + .into(); + (b <= block_height) + .then_some(()) + .ok_or(PanicReason::TransactionValidity)?; + + *self.write_user_register(ra)? = + storage.timestamp(b).map_err(RuntimeError::Storage)?; + + Ok(self.inc_pc()?) } pub(crate) fn message_output( @@ -942,21 +981,6 @@ pub(crate) fn block_hash( Ok(()) } -pub(crate) fn block_height( - context: &Context, - pc: RegMut, - result: &mut Word, -) -> SimpleResult<()> { - context - .block_height() - .map(|h| *h as Word) - .map(|h| *result = h) - .ok_or(PanicReason::TransactionValidity)?; - - inc_pc(pc)?; - Ok(()) -} - pub(crate) fn coinbase( storage: &S, memory: &mut MemoryInstance, @@ -1175,24 +1199,6 @@ pub(crate) fn state_write_word( Ok(inc_pc(pc)?) } -pub(crate) fn timestamp( - storage: &S, - block_height: BlockHeight, - pc: RegMut, - result: &mut Word, - b: Word, -) -> IoResult<(), S::DataError> { - let b = u32::try_from(b) - .map_err(|_| PanicReason::InvalidBlockHeight)? - .into(); - (b <= block_height) - .then_some(()) - .ok_or(PanicReason::TransactionValidity)?; - - *result = storage.timestamp(b).map_err(RuntimeError::Storage)?; - - Ok(inc_pc(pc)?) -} struct MessageOutputCtx<'vm, S> where S: ContractsAssetsStorage + ?Sized, @@ -1327,60 +1333,6 @@ struct StateWriteQWord { num_slots: Word, } -#[allow(clippy::too_many_arguments)] -fn state_write_qword<'vm, S: InterpreterStorage>( - contract_id: &ContractId, - storage: &mut S, - memory: &MemoryInstance, - profiler: &'vm mut Profiler, - new_storage_gas_per_byte: Word, - current_contract: Option, - cgas: RegMut<'vm, CGAS>, - ggas: RegMut<'vm, GGAS>, - is: Reg<'vm, IS>, - pc: RegMut, - result_register: &mut Word, - input: StateWriteQWord, -) -> IoResult<(), S::DataError> { - let destination_key = - Bytes32::new(memory.read_bytes(input.starting_storage_key_pointer)?); - - let values = memory - .read( - input.source_pointer, - (Bytes32::LEN as Word).saturating_mul(input.num_slots), - )? - .chunks_exact(Bytes32::LEN); - - let unset_count = storage - .contract_state_insert_range(contract_id, &destination_key, values) - .map_err(RuntimeError::Storage)?; - *result_register = unset_count as Word; - - if unset_count > 0 { - // New data was written, charge gas for it - let profiler = ProfileGas { - pc: pc.as_ref(), - is, - current_contract, - profiler, - }; - gas_charge( - cgas, - ggas, - profiler, - (unset_count as u64) - .saturating_mul(2) - .saturating_mul(Bytes32::LEN as u64) - .saturating_mul(new_storage_gas_per_byte), - )?; - } - - inc_pc(pc)?; - - Ok(()) -} - struct StateClearQWord { /// The starting storage key location is stored in this address. start_storage_key_pointer: Word, diff --git a/fuel-vm/src/interpreter/blockchain/other_tests.rs b/fuel-vm/src/interpreter/blockchain/other_tests.rs index 0189b4138c..895e576e85 100644 --- a/fuel-vm/src/interpreter/blockchain/other_tests.rs +++ b/fuel-vm/src/interpreter/blockchain/other_tests.rs @@ -186,18 +186,6 @@ fn test_block_hash() { assert_ne!(memory[20..20 + 32], [1u8; 32]); } -#[test] -fn test_block_height() { - let context = Context::Script { - block_height: 20.into(), - }; - let mut pc = 4; - let mut result = 0; - block_height(&context, RegMut::new(&mut pc), &mut result).unwrap(); - assert_eq!(pc, 8); - assert_eq!(result, 20); -} - #[test] fn test_coinbase() { let storage = MemoryStorage::new(Default::default(), ContractId::zeroed()); @@ -283,40 +271,3 @@ fn test_code_size() { .code_size(&mut result, 0) .expect_err("The contract is not in the input"); } - -#[test] -fn test_timestamp() { - let storage = MemoryStorage::default(); - let mut pc = 4; - let mut result = 0; - let _ = timestamp( - &storage, - Default::default(), - RegMut::new(&mut pc), - &mut result, - 1, - ) - .expect_err("Height is greater then current block height"); - let _ = timestamp( - &storage, - u32::MAX.into(), - RegMut::new(&mut pc), - &mut result, - u32::MAX as Word + 1, - ) - .expect_err("Height doesn't fit into a u32"); - assert_eq!(pc, 4); - - timestamp( - &storage, - Default::default(), - RegMut::new(&mut pc), - &mut result, - 0, - ) - .unwrap(); - assert_eq!(pc, 8); - - timestamp(&storage, 20.into(), RegMut::new(&mut pc), &mut result, 19).unwrap(); - assert_eq!(pc, 12); -} diff --git a/fuel-vm/src/interpreter/blockchain/test.rs b/fuel-vm/src/interpreter/blockchain/test.rs index 7bbe2599a7..dabec607e3 100644 --- a/fuel-vm/src/interpreter/blockchain/test.rs +++ b/fuel-vm/src/interpreter/blockchain/test.rs @@ -22,7 +22,6 @@ use super::*; mod scwq; mod srwq; -mod swwq; fn mem(chains: &[&[u8]]) -> MemoryInstance { let mut vec: Vec<_> = chains.iter().flat_map(|i| i.iter().copied()).collect(); diff --git a/fuel-vm/src/interpreter/blockchain/test/swwq.rs b/fuel-vm/src/interpreter/blockchain/test/swwq.rs deleted file mode 100644 index fdc86fee2e..0000000000 --- a/fuel-vm/src/interpreter/blockchain/test/swwq.rs +++ /dev/null @@ -1,261 +0,0 @@ -#![allow(clippy::type_complexity)] - -use alloc::{ - vec, - vec::Vec, -}; - -use crate::storage::{ - ContractsState, - ContractsStateData, - MemoryStorage, -}; - -use super::*; -use fuel_storage::StorageAsMut; -use test_case::test_case; - -struct SWWQInput { - input: StateWriteQWord, - storage_slots: Vec<([u8; 32], ContractsStateData)>, - memory: MemoryInstance, -} - -#[test_case( - SWWQInput{ - input: StateWriteQWord { - starting_storage_key_pointer: u64::MAX, - source_pointer: 0, - num_slots: 1, - }, - storage_slots: vec![], - memory: mem(&[]), - } => matches Err(_) - ; "Fail when rA + 32 overflows" -)] -#[test_case( - SWWQInput{ - input: StateWriteQWord { - starting_storage_key_pointer: 0, - source_pointer: u64::MAX, - num_slots: 1, - }, - storage_slots: vec![], - memory: mem(&[]), - } => matches Err(_) - ; "Fail when rC + 32 * d overflows (rC too high)" -)] -#[test_case( - SWWQInput{ - input: StateWriteQWord { - starting_storage_key_pointer: 0, - source_pointer: 0, - num_slots: u64::MAX, - }, - storage_slots: vec![], - memory: mem(&[]), - } => matches Err(_) - ; "Fail when rC + 32 * d overflows (rD too high)" -)] -#[test_case( - SWWQInput{ - input: StateWriteQWord { - starting_storage_key_pointer: VM_MAX_RAM - 1, - source_pointer: 0, - num_slots: 1, - }, - storage_slots: vec![], - memory: mem(&[]), - } => matches Err(_) - ; "Fail when rA + 32 > VM_MAX_RAM" -)] -#[test_case( - SWWQInput{ - input: StateWriteQWord { - starting_storage_key_pointer: 0, - source_pointer: VM_MAX_RAM - 1, - num_slots: 1, - }, - storage_slots: vec![], - memory: mem(&[]), - } => matches Err(_) - ; "Fail when rC + 32 * rD > VM_MAX_RAM" -)] -#[test_case( - SWWQInput{ - input: StateWriteQWord { - starting_storage_key_pointer: 0, - source_pointer: VM_MAX_RAM - 63, - num_slots: 2, - }, - storage_slots: vec![], - memory: mem(&[]), - } => matches Err(_) - ; "Fail when rC + 32 * rD == VM_MAX_RAM (rD too high)" -)] -#[test_case( - SWWQInput{ - input: StateWriteQWord { - starting_storage_key_pointer: VM_MAX_RAM - 32, - source_pointer: 0, - num_slots: 1, - }, - storage_slots: vec![], - memory: mem(&[&[0; MEM_SIZE]]), - } => matches Ok(_) - ; "Pass when rA + 32 == VM_MAX_RAM" -)] -#[test_case( - SWWQInput{ - input: StateWriteQWord { - starting_storage_key_pointer: 2, - source_pointer: 34, - num_slots: 1, - }, - storage_slots: vec![], - memory: mem(&[&[0; 2], &key(27), &[5; 32]]), - } => Ok((vec![(key(27), data(&[5; 32]))], 1)) - ; "Single slot write w/ offset key in memory" -)] -#[test_case( - SWWQInput{ - input: StateWriteQWord { - starting_storage_key_pointer: 0, - source_pointer: 32, - num_slots: 2, - }, - storage_slots: vec![], - memory: mem(&[&key(27), &[5; 32], &[6; 32]]), - } => Ok((vec![(key(27), data(&[5; 32])), (key(28), data(&[6; 32]))], 2)) - ; "Two slot write" -)] -#[test_case( - SWWQInput{ - input: StateWriteQWord { - starting_storage_key_pointer: 0, - source_pointer: 32, - num_slots: 2, - }, - storage_slots: vec![(key(27), data(&[2; 32]))], - memory: mem(&[&key(27), &[5; 32], &[6; 32]]), - } => Ok((vec![(key(27), data(&[5; 32])), (key(28), data(&[6; 32]))], 1)) - ; "Two slot writes with one pre-existing slot set" -)] -#[test_case( - SWWQInput{ - input: StateWriteQWord { - starting_storage_key_pointer: 0, - source_pointer: 32, - num_slots: 2, - }, - storage_slots: vec![], - memory: mem(&[&key(27), &[5; 32], &[6; 32], &[7; 32]]), - } => Ok((vec![(key(27), data(&[5; 32])), (key(28), data(&[6; 32]))], 2)) - ; "Only writes two slots when memory has more data available" -)] -#[test_case( - SWWQInput{ - input: StateWriteQWord { - starting_storage_key_pointer: 0, - source_pointer: 32, - num_slots: 3, - }, - storage_slots: vec![], - memory: mem(&[&key(27), &[5; 32], &[6; 32], &[7; 32]]), - } => Ok((vec![(key(27), data(&[5; 32])), (key(28), data(&[6; 32])), (key(29), data(&[7; 32]))], 3)) - ; "Three slot write" -)] -#[test_case( - SWWQInput{ - input: StateWriteQWord { - starting_storage_key_pointer: 0, - source_pointer: 32, - num_slots: 3, - }, - storage_slots: vec![(key(29), data(&[8; 32]))], - memory: mem(&[&key(27), &[5; 32], &[6; 32], &[7; 32]]), - } => Ok((vec![(key(27), data(&[5; 32])), (key(28), data(&[6; 32])), (key(29), data(&[7; 32]))], 2)) - ; "Three slot write with one pre-existing slot set" -)] -#[test_case( - SWWQInput{ - input: StateWriteQWord { - starting_storage_key_pointer: 0, - source_pointer: 32, - num_slots: 3, - }, - storage_slots: vec![(key(27), data(&[5; 32])), (key(28), data(&[6; 32])), (key(29), data(&[7; 32]))], - memory: mem(&[&key(27), &[5; 32], &[6; 32], &[7; 32]]), - } => Ok((vec![(key(27), data(&[5; 32])), (key(28), data(&[6; 32])), (key(29), data(&[7; 32]))], 0)) - ; "Three slot write with all slots previously set" -)] -#[test_case( - SWWQInput{ - input: StateWriteQWord { - starting_storage_key_pointer: 0, - source_pointer: 32, - num_slots: 2, - }, - storage_slots: vec![(key(29), data(&[8; 32]))], - memory: mem(&[&key(27), &[5; 32], &[6; 32], &[7; 32]]), - } => Ok((vec![(key(27), data(&[5; 32])), (key(28), data(&[6; 32])), (key(29), data(&[8; 32]))], 2)) - ; "Does not override slots that aren't being written to (adjacent)" -)] -#[test_case( - SWWQInput{ - input: StateWriteQWord { - starting_storage_key_pointer: 0, - source_pointer: 32, - num_slots: 3, - }, - storage_slots: vec![(key(100), data(&[8; 32]))], - memory: mem(&[&key(27), &[5; 32], &[6; 32], &[7; 32]]), - } => Ok((vec![(key(27), data(&[5; 32])), (key(28), data(&[6; 32])), (key(29), data(&[7; 32])), (key(100), data(&[8; 32]))], 3)) - ; "Does not override slots that aren't being written to (non-adjacent)" -)] -fn test_state_write_qword( - input: SWWQInput, -) -> Result<(Vec<([u8; 32], ContractsStateData)>, u64), RuntimeError> { - let SWWQInput { - input, - storage_slots, - memory, - } = input; - let mut storage = MemoryStorage::default(); - - for (k, v) in storage_slots { - storage - .storage::() - .insert( - &(&ContractId::default(), &Bytes32::new(k)).into(), - v.as_ref(), - ) - .unwrap(); - } - - let mut result_register = 0u64; - let is = 0; - let mut cgas = 10_000; - let mut ggas = 10_000; - let mut pc = 0; - state_write_qword( - &Default::default(), - &mut storage, - &memory, - &mut Profiler::default(), - 1, - None, - RegMut::new(&mut cgas), - RegMut::new(&mut ggas), - Reg::new(&is), - RegMut::new(&mut pc), - &mut result_register, - input, - )?; - - let results = storage - .all_contract_state() - .map(|(key, v)| (**key.state_key(), v.clone())) - .collect(); - Ok((results, result_register)) -} diff --git a/fuel-vm/src/interpreter/contract.rs b/fuel-vm/src/interpreter/contract.rs index c9074a1408..ba6cc7a34e 100644 --- a/fuel-vm/src/interpreter/contract.rs +++ b/fuel-vm/src/interpreter/contract.rs @@ -11,6 +11,7 @@ use super::{ internal_contract, set_variable_output, }, + register::verify_register_user_writable, ExecutableTransaction, Interpreter, Memory, @@ -69,19 +70,18 @@ where b: Word, c: Word, ) -> Result<(), RuntimeError> { - let (SystemRegisters { pc, .. }, mut w) = split_registers(&mut self.registers); - let result = &mut w[WriteRegKey::try_from(ra)?]; - let input = ContractBalanceCtx { - storage: &self.storage, - memory: self.memory.as_mut(), - pc, - input_contracts: InputContracts::new( - &self.input_contracts, - &mut self.panic_context, - ), - }; - input.contract_balance(result, b, c)?; - Ok(()) + verify_register_user_writable(ra)?; + let asset_id = AssetId::new(self.memory.as_ref().read_bytes(b)?); + let contract = ContractId::new(self.memory.as_ref().read_bytes(c)?); + + let mut input_contracts = + InputContracts::new(&self.input_contracts, &mut self.panic_context); + input_contracts.check(&contract)?; + + let balance = balance(&self.storage, &contract, &asset_id)?; + *self.write_user_register(ra)? = balance; + + Ok(self.inc_pc()?) } pub(crate) fn transfer( @@ -192,35 +192,6 @@ where .ok_or_else(|| PanicReason::ContractNotFound.into()) } -struct ContractBalanceCtx<'vm, S> { - storage: &'vm S, - memory: &'vm mut MemoryInstance, - pc: RegMut<'vm, PC>, - input_contracts: InputContracts<'vm>, -} - -impl<'vm, S> ContractBalanceCtx<'vm, S> { - pub(crate) fn contract_balance( - mut self, - result: &mut Word, - b: Word, - c: Word, - ) -> IoResult<(), S::Error> - where - S: ContractsAssetsStorage, - { - let asset_id = AssetId::new(self.memory.read_bytes(b)?); - let contract = ContractId::new(self.memory.read_bytes(c)?); - - self.input_contracts.check(&contract)?; - - let balance = balance(self.storage, &contract, &asset_id)?; - - *result = balance; - - Ok(inc_pc(self.pc)?) - } -} struct TransferCtx<'vm, S, Tx> { storage: &'vm mut S, memory: &'vm mut MemoryInstance, diff --git a/fuel-vm/src/interpreter/metadata.rs b/fuel-vm/src/interpreter/metadata.rs index 3fb7a30da0..12c7c9d6d7 100644 --- a/fuel-vm/src/interpreter/metadata.rs +++ b/fuel-vm/src/interpreter/metadata.rs @@ -1,12 +1,11 @@ use super::{ - internal::inc_pc, + register::verify_register_user_writable, ExecutableTransaction, Interpreter, Memory, }; use crate::{ call::CallFrame, - constraints::reg_key::*, consts::*, context::Context, convert, @@ -36,15 +35,11 @@ use fuel_tx::{ UtxoId, }; use fuel_types::{ - ChainId, Immediate12, Immediate18, Word, }; -#[cfg(test)] -mod tests; - impl Interpreter where M: Memory, @@ -53,17 +48,36 @@ where pub(crate) fn metadata(&mut self, ra: RegId, imm: Immediate18) -> SimpleResult<()> { let tx_offset = self.tx_offset() as Word; let chain_id = self.chain_id(); - let (SystemRegisters { pc, .. }, mut w) = split_registers(&mut self.registers); - let result = &mut w[WriteRegKey::try_from(ra)?]; - metadata( - &self.context, - &self.frames, - pc, - result, - imm, - chain_id, - tx_offset, - ) + verify_register_user_writable(ra)?; + + let context: &Context = &self.context; + let frames: &[CallFrame] = &self.frames; + let parent = context + .is_internal() + .then(|| frames.last().map(|f| f.registers()[RegId::FP])) + .flatten(); + + let result = match GMArgs::try_from(imm)? { + GMArgs::GetVerifyingPredicate => context + .predicate() + .map(|p| p.idx() as Word) + .ok_or(PanicReason::TransactionValidity)?, + GMArgs::GetChainId => chain_id.into(), + GMArgs::BaseAssetId => VM_MEMORY_BASE_ASSET_ID_OFFSET as Word, + GMArgs::TxStart => tx_offset, + GMArgs::GetCaller => match parent { + Some(0) => return Err(PanicReason::ExpectedNestedCaller.into()), + Some(parent) => parent, + None => return Err(PanicReason::ExpectedInternalContext.into()), + }, + GMArgs::IsCallerExternal => match parent { + Some(p) => (p == 0) as Word, + None => return Err(PanicReason::ExpectedInternalContext.into()), + }, + }; + *self.write_user_register(ra)? = result; + + self.inc_pc() } pub(crate) fn get_transaction_field( @@ -80,80 +94,12 @@ where .read_bytes(tx_size_ptr) .expect("Tx length not in memory"), ); - let (SystemRegisters { pc, .. }, mut w) = split_registers(&mut self.registers); - let result = &mut w[WriteRegKey::try_from(ra)?]; - let input = GTFInput { - tx: &self.tx, - input_contracts_index_to_output_index: &self - .input_contracts_index_to_output_index, - tx_offset, - tx_size, - pc, - }; - input.get_transaction_field(result, b, imm) - } -} - -pub(crate) fn metadata( - context: &Context, - frames: &[CallFrame], - pc: RegMut, - result: &mut Word, - imm: Immediate18, - chain_id: ChainId, - tx_offset: Word, -) -> SimpleResult<()> { - let parent = context - .is_internal() - .then(|| frames.last().map(|f| f.registers()[RegId::FP])) - .flatten(); - - *result = match GMArgs::try_from(imm)? { - GMArgs::GetVerifyingPredicate => context - .predicate() - .map(|p| p.idx() as Word) - .ok_or(PanicReason::TransactionValidity)?, - GMArgs::GetChainId => chain_id.into(), - GMArgs::BaseAssetId => VM_MEMORY_BASE_ASSET_ID_OFFSET as Word, - GMArgs::TxStart => tx_offset, - GMArgs::GetCaller => match parent { - Some(0) => return Err(PanicReason::ExpectedNestedCaller.into()), - Some(parent) => parent, - None => return Err(PanicReason::ExpectedInternalContext.into()), - }, - GMArgs::IsCallerExternal => match parent { - Some(p) => (p == 0) as Word, - None => return Err(PanicReason::ExpectedInternalContext.into()), - }, - }; - - inc_pc(pc)?; - Ok(()) -} - -struct GTFInput<'vm, Tx> { - tx: &'vm Tx, - input_contracts_index_to_output_index: &'vm alloc::collections::BTreeMap, - tx_offset: usize, - tx_size: Word, - pc: RegMut<'vm, PC>, -} - -impl GTFInput<'_, Tx> { - pub(crate) fn get_transaction_field( - self, - result: &mut Word, - b: Word, - imm: Immediate12, - ) -> SimpleResult<()> - where - Tx: ExecutableTransaction, - { + verify_register_user_writable(ra)?; let b = convert::to_usize(b).ok_or(PanicReason::InvalidMetadataIdentifier)?; let args = GTFArgs::try_from(imm)?; - let tx = self.tx; - let input_contract_to_output_index = self.input_contracts_index_to_output_index; - let ofs = self.tx_offset; + let input_contract_to_output_index = &self.input_contracts_index_to_output_index; + let tx = &self.tx; + let ofs = self.tx_offset(); // We use saturating_add with tx offset below. // In case any addition overflows, this function returns value @@ -207,7 +153,7 @@ impl GTFInput<'_, Tx> { .ok_or(PanicReason::WitnessNotFound)?, ) as Word } - GTFArgs::TxLength => self.tx_size, + GTFArgs::TxLength => tx_size, // Input GTFArgs::InputType => { @@ -550,9 +496,7 @@ impl GTFInput<'_, Tx> { } }; - *result = a; - - inc_pc(self.pc)?; - Ok(()) + *self.write_user_register(ra)? = a; + self.inc_pc() } } diff --git a/fuel-vm/src/interpreter/metadata/tests.rs b/fuel-vm/src/interpreter/metadata/tests.rs deleted file mode 100644 index 74cc19573c..0000000000 --- a/fuel-vm/src/interpreter/metadata/tests.rs +++ /dev/null @@ -1,83 +0,0 @@ -use alloc::vec; - -use fuel_tx::{ - Script, - TxParameters, -}; -use fuel_types::BlockHeight; -use test_case::test_case; - -use crate::prelude::RuntimePredicate; - -use super::*; - -#[test] -fn test_metadata() { - let context = Context::PredicateVerification { - program: RuntimePredicate::empty(), - }; - let frames = vec![]; - let mut pc = 4; - let mut result = 1; - let imm = 0x03; - metadata( - &context, - &frames, - RegMut::new(&mut pc), - &mut result, - imm, - ChainId::default(), - TxParameters::default().tx_offset() as Word, - ) - .unwrap(); - assert_eq!(pc, 8); - assert_eq!(result, 0); -} - -#[test] -fn test_get_transaction_field() { - let mut pc = 4; - let tx = Script::default(); - let input_contracts_index_to_output_index = Default::default(); - let input = GTFInput { - tx: &tx, - input_contracts_index_to_output_index: &input_contracts_index_to_output_index, - tx_offset: 0, - tx_size: fuel_tx::TxParameters::DEFAULT.tx_offset() as Word, - pc: RegMut::new(&mut pc), - }; - let mut result = 1; - let b = 0; - input - .get_transaction_field(&mut result, b, GTFArgs::ScriptGasLimit as Immediate12) - .unwrap(); - assert_eq!(pc, 8); - assert_eq!(result, *tx.script_gas_limit()); -} - -#[test_case(Context::PredicateEstimation { program: RuntimePredicate::empty() }, 2 => (); "can fetch inside predicate estimation")] -#[test_case(Context::PredicateVerification { program: RuntimePredicate::empty() }, 2 => (); "can fetch inside predicate verification")] -#[test_case(Context::Script { block_height: BlockHeight::default() }, 3 => (); "can fetch inside script")] -#[test_case(Context::Call { block_height: BlockHeight::default() }, 4 => (); "can fetch inside call")] -fn get_chain_id(context: Context, chain_id: u64) { - let mut frames = vec![]; - let mut pc = 4; - let mut result = 1; - let imm = GMArgs::GetChainId as Immediate18; - - if context.is_internal() { - frames.push(CallFrame::default()); - } - metadata( - &context, - &frames, - RegMut::new(&mut pc), - &mut result, - imm, - chain_id.into(), - TxParameters::default().tx_offset() as Word, - ) - .unwrap(); - - assert_eq!(result, chain_id); -} diff --git a/fuel-vm/src/tests/blob.rs b/fuel-vm/src/tests/blob.rs index ab6ff50587..ad8ff0b84f 100644 --- a/fuel-vm/src/tests/blob.rs +++ b/fuel-vm/src/tests/blob.rs @@ -26,6 +26,7 @@ use rand::{ use test_case::test_case; use super::test_helpers::{ + assert_panics, assert_success, RunResult, }; @@ -387,3 +388,22 @@ fn blob_load_data__bounds( .next() }) } + +#[test] +fn blob_size__write_to_reserved_register_fails() { + let (mut test_context, blob_id) = test_ctx_with_random_blob(16); + + let state = test_context + .start_script( + vec![ + op::gtf_args(0x11, RegId::ZERO, GTFArgs::ScriptData), + op::bsiz(RegId::HP, 0x11), + op::ret(RegId::ONE), + ], + blob_id.to_bytes(), + ) + .script_gas_limit(1_000_000) + .fee_input() + .execute(); + assert_panics(state.receipts(), PanicReason::ReservedRegisterNotWritable); +} diff --git a/fuel-vm/src/tests/memory.rs b/fuel-vm/src/tests/memory.rs index bef297146e..cf0bf835c6 100644 --- a/fuel-vm/src/tests/memory.rs +++ b/fuel-vm/src/tests/memory.rs @@ -19,6 +19,8 @@ use fuel_vm::{ prelude::*, }; +use crate::prelude::TestBuilder; + use super::test_helpers::{ assert_panics, assert_success, @@ -86,6 +88,19 @@ fn test_lw_unaglined() { assert_eq!(1, result); } +#[test] +fn test_lw_write_to_reserved_register_fails() { + let receipts = run_script(vec![ + op::movi(0x10, 8), + op::aloc(0x10), + op::move_(0x10, RegId::HP), + op::sw(0x10, RegId::ONE, 0), + op::lw(RegId::HP, 0x10, 0), // This should fail + op::ret(RegId::ONE), + ]); + assert_panics(&receipts, PanicReason::ReservedRegisterNotWritable); +} + #[test] fn test_lb() { let ops = vec![ @@ -102,6 +117,23 @@ fn test_lb() { assert_eq!(1, result); } +#[test] +fn test_aloc_input_too_large() { + let result = TestBuilder::new(1234) + .with_free_gas_costs() + .start_script( + vec![ + op::not(0x20, RegId::ZERO), + op::aloc(0x20), + op::ret(RegId::ONE), + ], + vec![], + ) + .fee_input() + .execute(); + assert_panics(result.receipts(), PanicReason::MemoryOverflow); +} + #[test] fn test_aloc_sb_lb_last_byte_of_memory() { let ops = vec![