diff --git a/src/context.rs b/src/context.rs index 3086180c31..9eeeec22f2 100644 --- a/src/context.rs +++ b/src/context.rs @@ -1,15 +1,28 @@ //! VM runtime context definitions -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +use crate::predicate::RuntimePredicate; + +use fuel_asm::Word; + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] /// Runtime context description. pub enum Context { /// Current context is a predicate verification. - Predicate, + Predicate { + /// Predicate program to be executed + program: RuntimePredicate, + }, /// Current context is a script execution. - Script, + Script { + /// Block height of the context + block_height: u32, + }, /// Current context is under a `CALL` scop.e - Call, + Call { + /// Block height of the context + block_height: u32, + }, /// No transaction initialized/invalid context. NotInitialized, } @@ -23,6 +36,40 @@ impl Default for Context { impl Context { /// Return `true` if the context is external; `false` otherwise. pub const fn is_external(&self) -> bool { - matches!(self, Self::Predicate | Self::Script) + matches!(self, Self::Predicate { .. } | Self::Script { .. }) + } + + /// Return the program to be executed, if its a predicate + pub const fn predicate(&self) -> Option<&RuntimePredicate> { + match self { + Context::Predicate { program } => Some(program), + _ => None, + } + } + + /// Return the block height from the context, if either script or call + pub const fn block_height(&self) -> Option { + match self { + Context::Script { block_height } | Context::Call { block_height } => Some(*block_height), + _ => None, + } + } + + /// Update the context according to the provided frame pointer + pub fn update_from_frame_pointer(&mut self, fp: Word) { + match self { + Context::Script { block_height } if fp != 0 => { + *self = Self::Call { + block_height: *block_height, + } + } + + Context::Call { block_height } if fp == 0 => { + *self = Self::Script { + block_height: *block_height, + } + } + _ => (), + } } } diff --git a/src/interpreter.rs b/src/interpreter.rs index c3db002d51..dad3bbc908 100644 --- a/src/interpreter.rs +++ b/src/interpreter.rs @@ -56,7 +56,6 @@ pub struct Interpreter { storage: S, debugger: Debugger, context: Context, - block_height: u32, balances: RuntimeBalances, #[cfg(feature = "profile-any")] profiler: Profiler, diff --git a/src/interpreter/blockchain.rs b/src/interpreter/blockchain.rs index 62827ab0af..276ab28231 100644 --- a/src/interpreter/blockchain.rs +++ b/src/interpreter/blockchain.rs @@ -173,6 +173,18 @@ where self.inc_pc() } + pub(crate) fn set_block_height(&mut self, ra: RegisterId) -> Result<(), RuntimeError> { + Self::is_register_writable(ra)?; + + self.context + .block_height() + .map(|h| h as Word) + .map(|h| self.registers[ra] = h) + .ok_or(PanicReason::TransactionValidity)?; + + self.inc_pc() + } + pub(crate) fn block_proposer(&mut self, a: Word) -> Result<(), RuntimeError> { self.coinbase() .and_then(|data| self.try_mem_write(a as usize, data.as_ref()))?; diff --git a/src/interpreter/constructors.rs b/src/interpreter/constructors.rs index 08a1b6f272..1f393b58fc 100644 --- a/src/interpreter/constructors.rs +++ b/src/interpreter/constructors.rs @@ -27,7 +27,6 @@ impl Interpreter { storage, debugger: Debugger::default(), context: Context::default(), - block_height: 0, balances: RuntimeBalances::default(), #[cfg(feature = "profile-any")] profiler: Profiler::default(), diff --git a/src/interpreter/executors/instruction.rs b/src/interpreter/executors/instruction.rs index 39644e10ad..7b2aaf9912 100644 --- a/src/interpreter/executors/instruction.rs +++ b/src/interpreter/executors/instruction.rs @@ -359,7 +359,7 @@ where OpcodeRepr::BHEI => { self.gas_charge(GAS_BHEI)?; - self.alu_set(ra, self.block_height() as Word)?; + self.set_block_height(ra)?; } OpcodeRepr::BHSH => { diff --git a/src/interpreter/executors/main.rs b/src/interpreter/executors/main.rs index fe33a2b6aa..336c3dbe7b 100644 --- a/src/interpreter/executors/main.rs +++ b/src/interpreter/executors/main.rs @@ -1,7 +1,8 @@ use crate::consts::*; use crate::crypto; use crate::error::InterpreterError; -use crate::interpreter::{Interpreter, MemoryRange}; +use crate::interpreter::Interpreter; +use crate::predicate::RuntimePredicate; use crate::prelude::*; use crate::state::{ExecuteState, ProgramState, StateTransitionRef}; use crate::storage::{InterpreterStorage, PredicateStorage}; @@ -32,12 +33,12 @@ impl Interpreter { return false; } - let predicates: Vec = tx + let predicates: Vec = tx .as_ref() .inputs() .iter() .enumerate() - .filter_map(|(idx, _)| vm.input_to_predicate(&tx, idx)) + .filter_map(|(idx, _)| RuntimePredicate::from_tx(¶ms, tx.as_ref(), idx)) .collect(); predicates @@ -56,28 +57,17 @@ impl Interpreter { pub fn check_predicate(&mut self, tx: CheckedTransaction, idx: usize) -> bool { tx.as_ref() .check_predicate_owner(idx) - .then(|| self.input_to_predicate(&tx, idx)) + .then(|| RuntimePredicate::from_tx(self.params(), tx.as_ref(), idx)) .flatten() .map(|predicate| self.init_predicate(tx) && self._check_predicate(predicate)) .unwrap_or(false) } - fn init_predicate(&mut self, tx: CheckedTransaction) -> bool { - let block_height = 0; - - self.init(true, block_height, tx).is_ok() - } - - fn input_to_predicate(&self, tx: &CheckedTransaction, idx: usize) -> Option { - tx.as_ref() - .input_coin_predicate_offset(idx) - .map(|(ofs, len)| (ofs as Word + self.tx_offset() as Word, len as Word)) - .map(|(ofs, len)| MemoryRange::new(ofs, len)) - } - /// Validate the predicate, assuming the interpreter is initialized - fn _check_predicate(&mut self, predicate: MemoryRange) -> bool { - match self.verify_predicate(&predicate) { + fn _check_predicate(&mut self, predicate: RuntimePredicate) -> bool { + self.context = Context::Predicate { program: predicate }; + + match self.verify_predicate() { Ok(ProgramState::Return(0x01)) => true, _ => false, } diff --git a/src/interpreter/executors/predicate.rs b/src/interpreter/executors/predicate.rs index 8eff06c410..bddf72c21f 100644 --- a/src/interpreter/executors/predicate.rs +++ b/src/interpreter/executors/predicate.rs @@ -1,16 +1,18 @@ use crate::consts::*; use crate::error::InterpreterError; -use crate::interpreter::{Interpreter, MemoryRange}; +use crate::interpreter::Interpreter; use crate::state::{ExecuteState, ProgramState}; use crate::storage::PredicateStorage; use fuel_asm::PanicReason; impl Interpreter { - pub(crate) fn verify_predicate(&mut self, predicate: &MemoryRange) -> Result { - debug_assert!(self.is_predicate()); - - let (start, end) = predicate.boundaries(self); + pub(crate) fn verify_predicate(&mut self) -> Result { + let (start, end) = self + .context + .predicate() + .map(|p| p.program().boundaries(self)) + .ok_or(InterpreterError::PredicateFailure)?; self.registers[REG_PC] = start; self.registers[REG_IS] = start; diff --git a/src/interpreter/flow.rs b/src/interpreter/flow.rs index 88228972e9..8c5764e2b8 100644 --- a/src/interpreter/flow.rs +++ b/src/interpreter/flow.rs @@ -50,16 +50,19 @@ impl Interpreter { .checked_add(frame.context_gas()) .ok_or(RuntimeError::halt_on_bug("CGAS would overflow"))?; - frame - .registers() - .iter() - .enumerate() - .zip(self.registers.iter_mut()) - .for_each(|((i, frame), current)| { - if i != REG_CGAS && i != REG_GGAS && i != REG_RET && i != REG_RETL { - *current = *frame; - } - }); + let cgas = self.registers[REG_CGAS]; + let ggas = self.registers[REG_GGAS]; + let ret = self.registers[REG_RET]; + let retl = self.registers[REG_RETL]; + + self.registers.copy_from_slice(&frame.registers()); + + self.registers[REG_CGAS] = cgas; + self.registers[REG_GGAS] = ggas; + self.registers[REG_RET] = ret; + self.registers[REG_RETL] = retl; + + self.set_frame_pointer(self.registers[REG_FP]); } self.append_receipt(receipt); @@ -182,7 +185,8 @@ where let id = self.internal_contract_or_default(); - self.registers[REG_FP] = self.registers[REG_SP]; + self.set_frame_pointer(self.registers[REG_SP]); + self.registers[REG_SP] += len; self.registers[REG_SSP] = self.registers[REG_SP]; diff --git a/src/interpreter/initialization.rs b/src/interpreter/initialization.rs index e87c5e41d2..43f5ddd6dd 100644 --- a/src/interpreter/initialization.rs +++ b/src/interpreter/initialization.rs @@ -13,12 +13,9 @@ use std::io; impl Interpreter { /// Initialize the VM with a given transaction - pub fn init(&mut self, predicate: bool, block_height: u32, tx: CheckedTransaction) -> Result<(), InterpreterError> { + pub fn init(&mut self, tx: CheckedTransaction) -> Result<(), InterpreterError> { self.tx = tx; - self.block_height = block_height; - self.context = if predicate { Context::Predicate } else { Context::Script }; - self.frames.clear(); self.receipts.clear(); @@ -53,6 +50,15 @@ impl Interpreter { Ok(()) } + + /// Initialize the VM for a predicate context + pub fn init_predicate(&mut self, tx: CheckedTransaction) -> bool { + self.context = Context::Predicate { + program: Default::default(), + }; + + self.init(tx).is_ok() + } } impl Interpreter @@ -64,9 +70,10 @@ where /// /// For predicate verification, check [`Self::init`] pub fn init_with_storage(&mut self, tx: CheckedTransaction) -> Result<(), InterpreterError> { - let predicate = false; let block_height = self.storage.block_height().map_err(InterpreterError::from_io)?; - self.init(predicate, block_height, tx) + self.context = Context::Script { block_height }; + + self.init(tx) } } diff --git a/src/interpreter/internal.rs b/src/interpreter/internal.rs index ad26c99ce6..78915001bd 100644 --- a/src/interpreter/internal.rs +++ b/src/interpreter/internal.rs @@ -30,10 +30,6 @@ impl Interpreter { Ok(()) } - pub(crate) const fn block_height(&self) -> u32 { - self.block_height - } - pub(crate) fn set_flag(&mut self, a: Word) -> Result<(), RuntimeError> { self.registers[REG_FLAG] = a; @@ -55,12 +51,8 @@ impl Interpreter { .map(|pc| self.registers[REG_PC] = pc) } - pub(crate) const fn context(&self) -> Context { - if self.registers[REG_FP] == 0 { - self.context - } else { - Context::Call - } + pub(crate) const fn context(&self) -> &Context { + &self.context } pub(crate) const fn is_external_context(&self) -> bool { @@ -72,7 +64,7 @@ impl Interpreter { } pub(crate) const fn is_predicate(&self) -> bool { - matches!(self.context, Context::Predicate) + matches!(self.context, Context::Predicate { .. }) } pub(crate) const fn is_register_writable(ra: RegisterId) -> Result<(), RuntimeError> { @@ -194,6 +186,12 @@ impl Interpreter { // Safety: vm parameters guarantees enough space for txid unsafe { Bytes32::as_ref_unchecked(&self.memory[..Bytes32::LEN]) } } + + pub(crate) fn set_frame_pointer(&mut self, fp: Word) { + self.context.update_from_frame_pointer(fp); + + self.registers[REG_FP] = fp; + } } #[cfg(all(test, feature = "random"))] diff --git a/src/interpreter/memory.rs b/src/interpreter/memory.rs index a9e75bedcd..1d6981ba28 100644 --- a/src/interpreter/memory.rs +++ b/src/interpreter/memory.rs @@ -18,6 +18,16 @@ pub struct MemoryRange { len: Word, } +impl Default for MemoryRange { + fn default() -> Self { + Self { + start: ops::Bound::Included(0), + end: ops::Bound::Excluded(0), + len: 0, + } + } +} + impl MemoryRange { /// Create a new memory range represented as `[address, address + size[`. pub const fn new(address: Word, size: Word) -> Self { diff --git a/src/interpreter/metadata.rs b/src/interpreter/metadata.rs index 694fe0716b..5a798e8613 100644 --- a/src/interpreter/metadata.rs +++ b/src/interpreter/metadata.rs @@ -9,31 +9,39 @@ impl Interpreter { pub(crate) fn metadata(&mut self, ra: RegisterId, imm: Immediate18) -> Result<(), RuntimeError> { Self::is_register_writable(ra)?; - // Both metadata implementations should panic if external context - if self.is_external_context() { - return Err(PanicReason::ExpectedInternalContext.into()); - } - - let parent = self - .frames - .last() - .map(|f| f.registers()[REG_FP]) - .expect("External context will always have a frame"); - - match GMArgs::try_from(imm)? { - GMArgs::IsCallerExternal => { - self.registers[ra] = (parent == 0) as Word; + let external = self.is_external_context(); + let args = GMArgs::try_from(imm)?; + + if external { + match args { + GMArgs::GetVerifyingPredicate => { + self.registers[ra] = self + .context + .predicate() + .map(|p| p.idx() as Word) + .ok_or(PanicReason::TransactionValidity)?; + } + + _ => return Err(PanicReason::ExpectedInternalContext.into()), } - - GMArgs::GetCaller if parent == 0 => { - return Err(PanicReason::ExpectedInternalContext.into()); + } else { + let parent = self + .frames + .last() + .map(|f| f.registers()[REG_FP]) + .expect("External context will always have a frame"); + + match args { + GMArgs::IsCallerExternal => { + self.registers[ra] = (parent == 0) as Word; + } + + GMArgs::GetCaller if parent != 0 => { + self.registers[ra] = parent; + } + + _ => return Err(PanicReason::ExpectedInternalContext.into()), } - - GMArgs::GetCaller => { - self.registers[ra] = parent; - } - - GMArgs::GetVerifyingPredicate => todo!(), } self.inc_pc() diff --git a/src/lib.rs b/src/lib.rs index 772e9979b6..3f79d93aaf 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -11,6 +11,7 @@ pub mod error; pub mod gas; pub mod interpreter; pub mod memory_client; +pub mod predicate; pub mod state; pub mod storage; pub mod transactor; @@ -59,6 +60,7 @@ pub mod prelude { pub use crate::error::{Infallible, InterpreterError, RuntimeError}; pub use crate::interpreter::{Interpreter, MemoryRange}; pub use crate::memory_client::MemoryClient; + pub use crate::predicate::RuntimePredicate; pub use crate::state::{Debugger, ProgramState, StateTransition, StateTransitionRef}; pub use crate::storage::{InterpreterStorage, MemoryStorage, PredicateStorage}; pub use crate::transactor::Transactor; diff --git a/src/predicate.rs b/src/predicate.rs new file mode 100644 index 0000000000..1c115c834d --- /dev/null +++ b/src/predicate.rs @@ -0,0 +1,112 @@ +//! Predicate representations with required data to be executed during VM runtime + +use crate::interpreter::MemoryRange; + +use fuel_asm::Word; +use fuel_tx::{ConsensusParameters, Transaction}; + +use std::borrow::Borrow; + +/// Runtime representation of a predicate +#[derive(Debug, Default, Clone, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct RuntimePredicate { + program: MemoryRange, + idx: usize, +} + +impl RuntimePredicate { + /// Memory slice with the program representation of the predicate + pub const fn program(&self) -> &MemoryRange { + &self.program + } + + /// Index of the transaction input that maps to this predicate + pub const fn idx(&self) -> usize { + self.idx + } + + /// Create a new runtime predicate from a transaction, given the input index + /// + /// Return `None` if the tx input doesn't map to an input coin with a predicate + pub fn from_tx(params: &ConsensusParameters, tx: T, idx: usize) -> Option + where + T: Borrow, + { + tx.borrow() + .input_coin_predicate_offset(idx) + .map(|(ofs, len)| (ofs as Word + params.tx_offset() as Word, len as Word)) + .map(|(ofs, len)| MemoryRange::new(ofs, len)) + .map(|program| Self { program, idx }) + } +} + +#[test] +fn from_tx_works() { + use fuel_tx::TransactionBuilder; + use fuel_types::bytes; + use fuel_vm::prelude::*; + use rand::rngs::StdRng; + use rand::{Rng, SeedableRng}; + + use std::iter; + + let rng = &mut StdRng::seed_from_u64(2322u64); + + let params = ConsensusParameters::default(); + let height = 1; + + #[rustfmt::skip] + let predicate: Vec = vec![ + Opcode::ADDI(0x10, 0x00, 0x01), + Opcode::ADDI(0x10, 0x10, 0x01), + Opcode::RET(0x01), + ].into_iter().collect(); + + let predicate_data = b"If people do not believe that mathematics is simple, it is only because they do not realize how complicated life is.".to_vec(); + + let owner = (*Contract::root_from_code(&predicate)).into(); + let a = Input::coin_predicate( + rng.gen(), + owner, + rng.gen(), + rng.gen(), + rng.gen(), + predicate.clone(), + predicate_data, + ); + + let tx = TransactionBuilder::script(vec![], vec![]) + .add_input(a) + .finalize_checked_without_signature(height, ¶ms); + + // assert invalid idx wont panic + let idx = 1; + let runtime = RuntimePredicate::from_tx(¶ms, tx.as_ref(), idx); + + assert!(runtime.is_none()); + + // fetch the input predicate + let idx = 0; + let runtime = + RuntimePredicate::from_tx(¶ms, tx.as_ref(), idx).expect("failed to generate predicate from valid tx"); + + assert_eq!(idx, runtime.idx()); + + let mut interpreter = Interpreter::without_storage(); + + assert!(interpreter.init_predicate(tx)); + + let pad = bytes::padded_len(&predicate) - predicate.len(); + + // assert we are testing an edge case + assert_ne!(0, pad); + + let padded_predicate: Vec = predicate.iter().copied().chain(iter::repeat(0u8).take(pad)).collect(); + + let program = runtime.program(); + let program = &interpreter.memory()[program.start() as usize..program.end() as usize]; + + // assert the program in the vm memory is the same of the input + assert_eq!(program, &padded_predicate); +} diff --git a/tests/predicate.rs b/tests/predicate.rs index 1e011ce828..231e758af7 100644 --- a/tests/predicate.rs +++ b/tests/predicate.rs @@ -1,3 +1,4 @@ +use fuel_tx::TransactionBuilder; use fuel_types::bytes; use rand::rngs::StdRng; use rand::{Rng, SeedableRng}; @@ -7,7 +8,7 @@ use fuel_vm::prelude::*; use core::iter; -fn execute_predicate

(predicate: P, predicate_data: Vec) -> bool +fn execute_predicate

(predicate: P, predicate_data: Vec, dummy_inputs: usize) -> bool where P: IntoIterator, { @@ -34,18 +35,17 @@ where let script = vec![]; let script_data = vec![]; - let tx = Transaction::script( - gas_price, - gas_limit, - maturity, - script, - script_data, - vec![input], - vec![], - vec![], - ) - .check(height, ¶ms) - .expect("failed to check tx"); + let mut builder = TransactionBuilder::script(script, script_data); + + builder.gas_price(gas_price).gas_limit(gas_limit).maturity(maturity); + + (0..dummy_inputs).for_each(|_| { + builder.add_unsigned_coin_input(rng.gen(), rng.gen(), rng.gen(), rng.gen(), maturity); + }); + + builder.add_input(input); + + let tx = builder.finalize_checked_without_signature(height, ¶ms); Interpreter::::check_predicates(tx, Default::default()) } @@ -55,7 +55,7 @@ fn predicate_minimal() { let predicate = iter::once(Opcode::RET(0x01)); let data = vec![]; - assert!(execute_predicate(predicate, data)); + assert!(execute_predicate(predicate, data, 7)); } #[test] @@ -84,6 +84,23 @@ fn predicate() { predicate.push(Opcode::MEQ(0x10, 0x11, 0x12, 0x10)); predicate.push(Opcode::RET(0x10)); - assert!(execute_predicate(predicate.iter().copied(), expected_data)); - assert!(!execute_predicate(predicate.iter().copied(), wrong_data)); + assert!(execute_predicate(predicate.iter().copied(), expected_data, 0)); + assert!(!execute_predicate(predicate.iter().copied(), wrong_data, 0)); +} + +#[test] +fn get_verifying_predicate() { + let indices = vec![0, 4, 5, 7, 11]; + + for idx in indices { + #[rustfmt::skip] + let predicate = vec![ + Opcode::gm(0x10, GMArgs::GetVerifyingPredicate), + Opcode::MOVI(0x11, idx), + Opcode::EQ(0x10, 0x10, 0x11), + Opcode::RET(0x10), + ]; + + assert!(execute_predicate(predicate, vec![], idx as usize)); + } }