diff --git a/Cargo.toml b/Cargo.toml index 4066083086..da322f7446 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,7 +16,7 @@ fuel-asm = "0.5" fuel-crypto = "0.5" fuel-merkle = "0.1" fuel-storage = "0.1" -fuel-tx = "0.11" +fuel-tx = "0.12" fuel-types = "0.5" itertools = "0.10" secp256k1 = { version = "0.20", features = ["recovery"] } @@ -27,7 +27,7 @@ tracing = "0.1" rand = { version = "0.8", optional = true } [dev-dependencies] -fuel-tx = { version = "0.11", features = ["random"] } +fuel-tx = { version = "0.12", features = ["random"] } fuel-vm = { path = ".", default-features = false, features = ["test-helpers"] } serde_json = "1.0" diff --git a/src/call.rs b/src/call.rs index dc5429a694..6a1817a69f 100644 --- a/src/call.rs +++ b/src/call.rs @@ -1,9 +1,9 @@ //! Inter-contract call supporting structures use crate::consts::*; -use crate::contract::Contract; use fuel_asm::PanicReason; +use fuel_tx::Contract; use fuel_types::bytes::{self, SizedBytes}; use fuel_types::{AssetId, ContractId, Word}; diff --git a/src/consts.rs b/src/consts.rs index 7dff091591..1a8d10e255 100644 --- a/src/consts.rs +++ b/src/consts.rs @@ -1,6 +1,6 @@ //! VM parameters -use fuel_tx::consts::*; +use fuel_tx::default_parameters::*; use fuel_types::{AssetId, Bytes32, Word}; use std::mem; diff --git a/src/context.rs b/src/context.rs index 3a63eaad0c..3086180c31 100644 --- a/src/context.rs +++ b/src/context.rs @@ -1,7 +1,5 @@ //! VM runtime context definitions -use fuel_tx::Transaction; - #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] /// Runtime context description. @@ -28,13 +26,3 @@ impl Context { matches!(self, Self::Predicate | Self::Script) } } - -impl From<&Transaction> for Context { - fn from(tx: &Transaction) -> Self { - if tx.is_script() { - Self::Script - } else { - Self::Predicate - } - } -} diff --git a/src/contract.rs b/src/contract.rs deleted file mode 100644 index 585e9dcf1d..0000000000 --- a/src/contract.rs +++ /dev/null @@ -1,119 +0,0 @@ -//! Chain contract definition - -use crate::crypto; -use crate::error::InterpreterError; - -use fuel_crypto::Hasher; -use fuel_tx::{StorageSlot, Transaction, ValidationError}; -use fuel_types::{Bytes32, ContractId, Salt}; - -use std::cmp; - -#[derive(Debug, Default, Clone, PartialEq, Eq, Hash)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -/// Deployable representation of a contract code. -pub struct Contract(Vec); - -impl Contract { - /// Calculate the code root from a contract. - /// - /// - pub fn root(&self) -> Bytes32 { - let root = self.0.chunks(8).map(|c| { - let mut bytes = [0u8; 8]; - - let l = cmp::min(c.len(), 8); - (&mut bytes[..l]).copy_from_slice(c); - - bytes - }); - - crypto::ephemeral_merkle_root(root) - } - - /// Calculate the root of the initial storage slots for this contract - /// TODO: Use a sparse merkle tree once the implementation is available - pub fn initial_state_root(storage_slots: &[StorageSlot]) -> Bytes32 { - let leaves = storage_slots.iter().map(|slot| { - let mut buf = [0u8; 64]; - buf[..32].copy_from_slice(slot.key().as_slice()); - buf[32..].copy_from_slice(slot.value().as_slice()); - buf - }); - crypto::ephemeral_merkle_root(leaves) - } - - /// The default state root value without any entries - pub fn default_state_root() -> Bytes32 { - Self::initial_state_root(&[]) - } - - /// Calculate and return the contract id, provided a salt, code root and state root. - /// - /// - pub fn id(&self, salt: &Salt, root: &Bytes32, state_root: &Bytes32) -> ContractId { - let mut hasher = Hasher::default(); - - hasher.input(ContractId::SEED); - hasher.input(salt); - hasher.input(root); - hasher.input(state_root); - - ContractId::from(*hasher.digest()) - } -} - -impl From> for Contract { - fn from(c: Vec) -> Self { - Self(c) - } -} - -impl From<&[u8]> for Contract { - fn from(c: &[u8]) -> Self { - Self(c.into()) - } -} - -impl From<&mut [u8]> for Contract { - fn from(c: &mut [u8]) -> Self { - Self(c.into()) - } -} - -impl From for Vec { - fn from(c: Contract) -> Vec { - c.0 - } -} - -impl AsRef<[u8]> for Contract { - fn as_ref(&self) -> &[u8] { - self.0.as_ref() - } -} - -impl AsMut<[u8]> for Contract { - fn as_mut(&mut self) -> &mut [u8] { - self.0.as_mut() - } -} - -impl TryFrom<&Transaction> for Contract { - type Error = InterpreterError; - - fn try_from(tx: &Transaction) -> Result { - match tx { - Transaction::Create { - bytecode_witness_index, - witnesses, - .. - } => witnesses - .get(*bytecode_witness_index as usize) - .map(|c| c.as_ref().into()) - .ok_or_else(|| ValidationError::TransactionCreateBytecodeWitnessIndex.into()), - - _ => Err(ValidationError::TransactionScriptOutputContractCreated { index: 0 }.into()), - } - } -} diff --git a/src/interpreter/blockchain.rs b/src/interpreter/blockchain.rs index 17951c3583..004a47f62c 100644 --- a/src/interpreter/blockchain.rs +++ b/src/interpreter/blockchain.rs @@ -4,7 +4,8 @@ use crate::error::RuntimeError; use crate::storage::InterpreterStorage; use fuel_asm::PanicReason; -use fuel_tx::{consts::CONTRACT_MAX_SIZE, Input}; +use fuel_tx::default_parameters::*; +use fuel_tx::Input; use fuel_types::{bytes, Address, AssetId, Bytes32, Bytes8, ContractId, RegisterId, Word}; use std::mem; diff --git a/src/interpreter/contract.rs b/src/interpreter/contract.rs index f2ea5d5a23..b37a79451e 100644 --- a/src/interpreter/contract.rs +++ b/src/interpreter/contract.rs @@ -1,11 +1,10 @@ use super::Interpreter; use crate::consts::*; -use crate::contract::Contract; use crate::error::RuntimeError; use crate::storage::InterpreterStorage; use fuel_asm::{PanicReason, RegisterId, Word}; -use fuel_tx::Receipt; +use fuel_tx::{Contract, Receipt}; use fuel_types::{Address, AssetId, ContractId}; use std::borrow::Cow; diff --git a/src/interpreter/executors/instruction.rs b/src/interpreter/executors/instruction.rs index 969b96531e..705766c110 100644 --- a/src/interpreter/executors/instruction.rs +++ b/src/interpreter/executors/instruction.rs @@ -13,12 +13,12 @@ use std::ops::Div; const WORD_SIZE: usize = mem::size_of::(); -impl Interpreter -where - S: InterpreterStorage, -{ +impl Interpreter { /// Execute the current instruction pair located in `$m[$pc]`. - pub fn execute(&mut self) -> Result { + pub fn execute(&mut self, instruction_executor: F) -> Result + where + F: Fn(&mut Self, Instruction) -> Result, + { // Safety: `chunks_exact` is guaranteed to return a well-formed slice let (hi, lo) = self.memory[self.registers[REG_PC] as usize..] .chunks_exact(WORD_SIZE) @@ -30,21 +30,28 @@ where // Store the expected `$pc` after executing `hi` let pc = self.registers[REG_PC] + Instruction::LEN as Word; - let state = self.instruction(hi)?; + let state = self.instruction(&instruction_executor, hi)?; // TODO optimize // Should execute `lo` only if there is no rupture in the flow - that means // either a breakpoint or some instruction that would skip `lo` such as // `RET`, `JI` or `CALL` if self.registers[REG_PC] == pc && state.should_continue() { - self.instruction(lo) + self.instruction(&instruction_executor, lo) } else { Ok(state) } } /// Execute a provided instruction - pub fn instruction(&mut self, instruction: Instruction) -> Result { + pub fn instruction( + &mut self, + instruction_executor: F, + instruction: Instruction, + ) -> Result + where + F: FnOnce(&mut Self, Instruction) -> Result, + { #[cfg(feature = "debug")] { let debug = self.eval_debugger_state(); @@ -53,11 +60,349 @@ where } } - self._instruction(instruction) - .map_err(|e| InterpreterError::from_runtime(e, instruction)) + instruction_executor(self, instruction).map_err(|e| InterpreterError::from_runtime(e, instruction)) + } + + pub(crate) fn instruction_predicate(&mut self, instruction: Instruction) -> Result { + let (op, ra, rb, rc, rd, imm) = instruction.into_inner(); + let (a, b, c, d) = ( + self.registers[ra], + self.registers[rb], + self.registers[rc], + self.registers[rd], + ); + + match op { + OpcodeRepr::ADD => { + self.gas_charge(GAS_ADD)?; + self.alu_capture_overflow(ra, u128::overflowing_add, b.into(), c.into())?; + } + + OpcodeRepr::ADDI => { + self.gas_charge(GAS_ADDI)?; + self.alu_capture_overflow(ra, u128::overflowing_add, b.into(), imm.into())?; + } + + OpcodeRepr::AND => { + self.gas_charge(GAS_AND)?; + self.alu_set(ra, b & c)?; + } + + OpcodeRepr::ANDI => { + self.gas_charge(GAS_ANDI)?; + self.alu_set(ra, b & imm)?; + } + + OpcodeRepr::DIV => { + self.gas_charge(GAS_DIV)?; + self.alu_error(ra, Word::div, b, c, c == 0)?; + } + + OpcodeRepr::DIVI => { + self.gas_charge(GAS_DIVI)?; + self.alu_error(ra, Word::div, b, imm, imm == 0)?; + } + + OpcodeRepr::EQ => { + self.gas_charge(GAS_EQ)?; + self.alu_set(ra, (b == c) as Word)?; + } + + OpcodeRepr::EXP => { + self.gas_charge(GAS_EXP)?; + self.alu_boolean_overflow(ra, Word::overflowing_pow, b, c as u32)?; + } + + OpcodeRepr::EXPI => { + self.gas_charge(GAS_EXPI)?; + self.alu_boolean_overflow(ra, Word::overflowing_pow, b, imm as u32)?; + } + + OpcodeRepr::GT => { + self.gas_charge(GAS_GT)?; + self.alu_set(ra, (b > c) as Word)?; + } + + OpcodeRepr::LT => { + self.gas_charge(GAS_LT)?; + self.alu_set(ra, (b < c) as Word)?; + } + + OpcodeRepr::MLOG => { + self.gas_charge(GAS_MLOG)?; + self.alu_error( + ra, + |b, c| (b as f64).log(c as f64).trunc() as Word, + b, + c, + b == 0 || c <= 1, + )?; + } + + OpcodeRepr::MOD => { + self.gas_charge(GAS_MOD)?; + self.alu_error(ra, Word::wrapping_rem, b, c, c == 0)?; + } + + OpcodeRepr::MODI => { + self.gas_charge(GAS_MODI)?; + self.alu_error(ra, Word::wrapping_rem, b, imm, imm == 0)?; + } + + OpcodeRepr::MOVE => { + self.gas_charge(GAS_MOVE)?; + self.alu_set(ra, b)?; + } + + OpcodeRepr::MOVI => { + self.gas_charge(GAS_MOVI)?; + self.alu_set(ra, imm)?; + } + + OpcodeRepr::MROO => { + self.gas_charge(GAS_MROO)?; + self.alu_error( + ra, + |b, c| (b as f64).powf((c as f64).recip()).trunc() as Word, + b, + c, + c == 0, + )?; + } + + OpcodeRepr::MUL => { + self.gas_charge(GAS_MUL)?; + self.alu_capture_overflow(ra, u128::overflowing_mul, b.into(), c.into())?; + } + + OpcodeRepr::MULI => { + self.gas_charge(GAS_MULI)?; + self.alu_capture_overflow(ra, u128::overflowing_mul, b.into(), imm.into())?; + } + + OpcodeRepr::NOOP => { + self.gas_charge(GAS_NOOP)?; + self.alu_clear()?; + } + + OpcodeRepr::NOT => { + self.gas_charge(GAS_NOT)?; + self.alu_set(ra, !b)?; + } + + OpcodeRepr::OR => { + self.gas_charge(GAS_OR)?; + self.alu_set(ra, b | c)?; + } + + OpcodeRepr::ORI => { + self.gas_charge(GAS_ORI)?; + self.alu_set(ra, b | imm)?; + } + + OpcodeRepr::SLL => { + self.gas_charge(GAS_SLL)?; + self.alu_set(ra, b.checked_shl(c as u32).unwrap_or_default())?; + } + + OpcodeRepr::SLLI => { + self.gas_charge(GAS_SLLI)?; + self.alu_set(ra, b.checked_shl(imm as u32).unwrap_or_default())?; + } + + OpcodeRepr::SRL => { + self.gas_charge(GAS_SRL)?; + self.alu_set(ra, b.checked_shr(c as u32).unwrap_or_default())?; + } + + OpcodeRepr::SRLI => { + self.gas_charge(GAS_SRLI)?; + self.alu_set(ra, b.checked_shr(imm as u32).unwrap_or_default())?; + } + + OpcodeRepr::SUB => { + self.gas_charge(GAS_SUB)?; + self.alu_capture_overflow(ra, u128::overflowing_sub, b.into(), c.into())?; + } + + OpcodeRepr::SUBI => { + self.gas_charge(GAS_SUBI)?; + self.alu_capture_overflow(ra, u128::overflowing_sub, b.into(), imm.into())?; + } + + OpcodeRepr::XOR => { + self.gas_charge(GAS_XOR)?; + self.alu_set(ra, b ^ c)?; + } + + OpcodeRepr::XORI => { + self.gas_charge(GAS_XORI)?; + self.alu_set(ra, b ^ imm)?; + } + + OpcodeRepr::CIMV => { + self.gas_charge(GAS_CIMV)?; + self.check_input_maturity(ra, b, c)?; + } + + OpcodeRepr::CTMV => { + self.gas_charge(GAS_CTMV)?; + self.check_tx_maturity(ra, b)?; + } + + OpcodeRepr::JI => { + self.gas_charge(GAS_JI)?; + self.jump(imm)?; + } + + OpcodeRepr::JNEI => { + self.gas_charge(GAS_JNEI)?; + self.jump_not_equal_imm(a, b, imm)?; + } + + OpcodeRepr::JNZI => { + self.gas_charge(GAS_JNZI)?; + self.jump_not_zero_imm(a, imm)?; + } + + OpcodeRepr::RET => { + self.gas_charge(GAS_RET)?; + self.ret(a)?; + + return Ok(ExecuteState::Return(a)); + } + + OpcodeRepr::ALOC => { + self.gas_charge(GAS_ALOC)?; + self.malloc(a)?; + } + + OpcodeRepr::CFEI => { + self.gas_charge(GAS_CFEI)?; + self.stack_pointer_overflow(Word::overflowing_add, imm)?; + } + + OpcodeRepr::CFSI => { + self.gas_charge(GAS_CFSI)?; + self.stack_pointer_overflow(Word::overflowing_sub, imm)?; + } + + OpcodeRepr::LB => { + self.gas_charge(GAS_LB)?; + self.load_byte(ra, rb, imm)?; + } + + OpcodeRepr::LW => { + self.gas_charge(GAS_LW)?; + self.load_word(ra, b, imm)?; + } + + OpcodeRepr::MCL => { + self.gas_charge_monad(GAS_MCL, b)?; + self.memclear(a, b)?; + } + + OpcodeRepr::MCLI => { + self.gas_charge_monad(GAS_MCLI, b)?; + self.memclear(a, imm)?; + } + + OpcodeRepr::MCP => { + self.gas_charge_monad(GAS_MCP, c)?; + self.memcopy(a, b, c)?; + } + + OpcodeRepr::MCPI => { + self.gas_charge_monad(GAS_MCPI, imm)?; + self.memcopy(a, b, imm)?; + } + + OpcodeRepr::MEQ => { + self.gas_charge(GAS_MEQ)?; + self.memeq(ra, b, c, d)?; + } + + OpcodeRepr::SB => { + self.gas_charge(GAS_SB)?; + self.store_byte(a, b, imm)?; + } + + OpcodeRepr::SW => { + self.gas_charge(GAS_SW)?; + self.store_word(a, b, imm)?; + } + + OpcodeRepr::ECR => { + self.gas_charge(GAS_ECR)?; + self.ecrecover(a, b, c)?; + } + + OpcodeRepr::K256 => { + self.gas_charge(GAS_K256)?; + self.keccak256(a, b, c)?; + } + + OpcodeRepr::S256 => { + self.gas_charge(GAS_S256)?; + self.sha256(a, b, c)?; + } + + OpcodeRepr::XIL => { + self.gas_charge(GAS_XIL)?; + self.transaction_input_length(ra, b)?; + } + + OpcodeRepr::XIS => { + self.gas_charge(GAS_XIS)?; + self.transaction_input_start(ra, b)?; + } + + OpcodeRepr::XOL => { + self.gas_charge(GAS_XOL)?; + self.transaction_output_length(ra, b)?; + } + + OpcodeRepr::XOS => { + self.gas_charge(GAS_XOS)?; + self.transaction_output_start(ra, b)?; + } + + OpcodeRepr::XWL => { + self.gas_charge(GAS_XWL)?; + self.transaction_witness_length(ra, b)?; + } + + OpcodeRepr::XWS => { + self.gas_charge(GAS_XWS)?; + self.transaction_witness_start(ra, b)?; + } + + OpcodeRepr::FLAG => { + self.gas_charge(GAS_FLAG)?; + self.set_flag(a)?; + } + + OpcodeRepr::GM => { + self.gas_charge(GAS_GM)?; + self.metadata(ra, imm as Immediate18)?; + } + + _ => { + // TODO use dedicated panic reason variant + // https://github.com/FuelLabs/fuel-asm/issues/69 + return Err(PanicReason::TransactionValidity.into()); + } + } + + Ok(ExecuteState::Proceed) } +} - fn _instruction(&mut self, instruction: Instruction) -> Result { +impl Interpreter +where + S: InterpreterStorage, +{ + pub(crate) fn instruction_script(&mut self, instruction: Instruction) -> Result { let (op, ra, rb, rc, rd, imm) = instruction.into_inner(); let (a, b, c, d) = ( self.registers[ra], diff --git a/src/interpreter/executors/main.rs b/src/interpreter/executors/main.rs index b1e9ad50a8..383c425924 100644 --- a/src/interpreter/executors/main.rs +++ b/src/interpreter/executors/main.rs @@ -1,5 +1,4 @@ use crate::consts::*; -use crate::contract::Contract; use crate::crypto; use crate::error::InterpreterError; use crate::interpreter::{Interpreter, MemoryRange}; @@ -8,18 +7,87 @@ use crate::state::{ExecuteState, ProgramState, StateTransitionRef}; use crate::storage::InterpreterStorage; use fuel_asm::PanicReason; -use fuel_tx::{Input, Output, Receipt, ScriptExecutionResult, Transaction}; +use fuel_tx::{Contract, Input, Output, Receipt, ScriptExecutionResult, Transaction}; use fuel_types::{bytes::SerializableVec, Word}; +impl Interpreter +where + S: Default + Clone, +{ + /// Initialize the VM with the provided transaction and check all predicates defined in the + /// inputs. + /// + /// The storage provider is not used since contract opcodes are not allowed for predicates. + /// This way, its possible, for the sake of simplicity, it is possible to use + /// [unit](https://doc.rust-lang.org/core/primitive.unit.html) as storage provider. + /// + /// # Debug + /// + /// This is not a valid entrypoint for debug calls. It will only return a `bool`, and not the + /// VM state required to trace the execution steps. + pub fn check_predicates(tx: Transaction) -> bool { + let mut vm = Interpreter::with_storage(S::default()); + + if !tx.check_predicate_owners() { + return false; + } + + let predicates: Vec = tx + .inputs() + .iter() + .enumerate() + .filter_map(|(idx, _)| Self::input_to_predicate(&tx, idx)) + .collect(); + + predicates + .into_iter() + .fold(vm.init_predicate(tx), |result, predicate| -> bool { + // VM is cloned because the state should be reset for every predicate verification + result && vm.clone()._check_predicate(predicate) + }) + } + + /// Initialize the VM with the provided transaction and check the input predicate indexed by + /// `idx`. If the input isn't of type [`Input::CoinPredicate`], the function will return + /// `false`. + /// + /// For additional information, check [`Self::check_predicates`] + pub fn check_predicate(&mut self, tx: Transaction, idx: usize) -> bool { + tx.check_predicate_owner(idx) + .then(|| Self::input_to_predicate(&tx, idx)) + .flatten() + .map(|predicate| self.init_predicate(tx) && self._check_predicate(predicate)) + .unwrap_or(false) + } + + fn init_predicate(&mut self, tx: Transaction) -> bool { + let block_height = 0; + + self.init(true, block_height, tx).is_ok() + } + + fn input_to_predicate(tx: &Transaction, idx: usize) -> Option { + tx.input_coin_predicate_offset(idx) + .map(|(ofs, len)| (ofs as Word + VM_TX_MEMORY 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) { + Ok(ProgramState::Return(0x01)) => true, + _ => false, + } + } +} + impl Interpreter where S: InterpreterStorage, { // TODO maybe infallible? pub(crate) fn run(&mut self) -> Result { - let mut state: ProgramState; - - match &self.tx { + let state = match &self.tx { Transaction::Create { salt, static_contracts, @@ -35,7 +103,7 @@ where let contract = Contract::try_from(&self.tx)?; let root = contract.root(); - let storage_root = Contract::initial_state_root(storage_slots); + let storage_root = Contract::initial_state_root(storage_slots.iter()); let id = contract.id(salt, &root, &storage_root); if !&self @@ -63,34 +131,12 @@ where // Verify predicates // https://github.com/FuelLabs/fuel-specs/blob/master/specs/protocol/tx_validity.md#predicate-verification - // TODO this should be abstracted with the client - let predicates: Vec = self - .tx - .inputs() - .iter() - .enumerate() - .filter_map(|(i, input)| match input { - Input::CoinPredicate { predicate, .. } => self - .tx - .input_coin_predicate_offset(i) - .map(|ofs| (ofs as Word, predicate.len() as Word)), - _ => None, - }) - .map(|(ofs, len)| (ofs + VM_TX_MEMORY as Word, len)) - .map(|(ofs, len)| MemoryRange::new(ofs, len)) - .collect(); - - state = ProgramState::Return(1); - for predicate in predicates { - state = self.verify_predicate(&predicate)?; - - #[cfg(feature = "debug")] - if state.is_debug() { - // TODO should restore the constructed predicates and continue from current - // predicate - return Ok(state); - } + // TODO implement debug support + if !Interpreter::<()>::check_predicates(self.tx.clone()) { + return Err(InterpreterError::PredicateFailure); } + + ProgramState::Return(1) } Transaction::Script { inputs, .. } => { @@ -145,9 +191,9 @@ where self.receipts.push(receipt); - state = program; + program } - } + }; #[cfg(feature = "debug")] if state.is_debug() { @@ -182,7 +228,7 @@ where } let state = self - .execute() + .execute(Self::instruction_script) .map_err(|e| e.panic_reason().expect("Call routine should return only VM panic"))?; match state { @@ -214,7 +260,7 @@ where return Err(InterpreterError::Panic(PanicReason::MemoryOverflow)); } - match self.execute()? { + match self.execute(Self::instruction_script)? { ExecuteState::Return(r) => { return Ok(ProgramState::Return(r)); } @@ -251,7 +297,7 @@ where /// of the interpreter and will avoid unnecessary copy with the data /// that can be referenced from the interpreter instance itself. pub fn transact(&mut self, tx: Transaction) -> Result, InterpreterError> { - let state_result = self.init(tx).and_then(|_| self.run()); + let state_result = self.init_with_storage(tx).and_then(|_| self.run()); #[cfg(feature = "profile-any")] self.profiler.on_transaction(&state_result); diff --git a/src/interpreter/executors/predicate.rs b/src/interpreter/executors/predicate.rs index 05e2a9c50e..20faebb823 100644 --- a/src/interpreter/executors/predicate.rs +++ b/src/interpreter/executors/predicate.rs @@ -2,25 +2,24 @@ use crate::consts::*; use crate::error::InterpreterError; use crate::interpreter::{Interpreter, MemoryRange}; use crate::state::{ExecuteState, ProgramState}; -use crate::storage::InterpreterStorage; -impl Interpreter -where - S: InterpreterStorage, -{ +use fuel_asm::PanicReason; + +impl Interpreter { pub(crate) fn verify_predicate(&mut self, predicate: &MemoryRange) -> Result { - // TODO initialize VM with tx prepared for sign - // TODO execute should not overflow predicate boundaries. Need to check - // internally if a Jump instruction decrements $pc, or if $pc overflows - // `end` - let (start, _end) = predicate.boundaries(self); + debug_assert!(self.is_predicate()); + + let (start, end) = predicate.boundaries(self); self.registers[REG_PC] = start; self.registers[REG_IS] = start; - // TODO optimize loop { - match self.execute()? { + if end <= self.registers[REG_PC] { + return Err(InterpreterError::Panic(PanicReason::MemoryOverflow)); + } + + match self.execute(Self::instruction_predicate)? { ExecuteState::Return(r) => { if r == 1 { return Ok(ProgramState::Return(r)); diff --git a/src/interpreter/flow.rs b/src/interpreter/flow.rs index c61c9b18c5..c717c1e59b 100644 --- a/src/interpreter/flow.rs +++ b/src/interpreter/flow.rs @@ -13,10 +13,7 @@ use fuel_types::{AssetId, Bytes32, RegisterId, Word}; use std::cmp; -impl Interpreter -where - S: InterpreterStorage, -{ +impl Interpreter { // TODO add CIMV tests pub(crate) fn check_input_maturity(&mut self, ra: RegisterId, b: Word, c: Word) -> Result<(), RuntimeError> { Self::is_register_writable(ra)?; @@ -52,6 +49,8 @@ where if j > VM_MAX_RAM - 1 { Err(PanicReason::MemoryOverflow.into()) + } else if self.is_predicate() && j <= self.registers[REG_PC] { + Err(PanicReason::IllegalJump.into()) } else { self.registers[REG_PC] = j; @@ -75,71 +74,6 @@ where } } - pub(crate) fn call(&mut self, a: Word, b: Word, c: Word, d: Word) -> Result { - let (ax, overflow) = a.overflowing_add(32); - let (cx, of) = c.overflowing_add(32); - let overflow = overflow || of; - - if overflow || ax > VM_MAX_RAM || cx > VM_MAX_RAM { - return Err(PanicReason::MemoryOverflow.into()); - } - - let call = Call::try_from(&self.memory[a as usize..])?; - let asset_id = - AssetId::try_from(&self.memory[c as usize..cx as usize]).expect("Unreachable! Checked memory range"); - - if self.is_external_context() { - self.external_asset_id_balance_sub(&asset_id, b)?; - } else { - let source_contract = *self.internal_contract()?; - self.balance_decrease(&source_contract, &asset_id, b)?; - } - - if !self.tx.input_contracts().any(|contract| call.to() == contract) { - return Err(PanicReason::ContractNotInInputs.into()); - } - - // credit contract asset_id balance - self.balance_increase(call.to(), &asset_id, b)?; - - let mut frame = self.call_frame(call, asset_id)?; - - let stack = frame.to_bytes(); - let len = stack.len() as Word; - - if len > self.registers[REG_HP] || self.registers[REG_SP] > self.registers[REG_HP] - len { - return Err(PanicReason::MemoryOverflow.into()); - } - - self.registers[REG_FP] = self.registers[REG_SP]; - self.registers[REG_SP] += len; - self.registers[REG_SSP] = self.registers[REG_SP]; - - self.memory[self.registers[REG_FP] as usize..self.registers[REG_SP] as usize].copy_from_slice(stack.as_slice()); - - self.registers[REG_BAL] = b; - self.registers[REG_PC] = self.registers[REG_FP].saturating_add(CallFrame::code_offset() as Word); - self.registers[REG_IS] = self.registers[REG_PC]; - self.registers[REG_CGAS] = cmp::min(self.registers[REG_GGAS], d); - - let receipt = Receipt::call( - self.internal_contract_or_default(), - *frame.to(), - b, - *frame.asset_id(), - d, - frame.a(), - frame.b(), - self.registers[REG_PC], - self.registers[REG_IS], - ); - - self.receipts.push(receipt); - self.frames.push(frame); - - self.run_call() - } - pub(crate) fn return_from_context(&mut self, receipt: Receipt) -> Result<(), RuntimeError> { if let Some(frame) = self.frames.pop() { self.registers[REG_CGAS] += frame.context_gas(); @@ -222,3 +156,73 @@ where self.receipts.push(receipt); } } + +impl Interpreter +where + S: InterpreterStorage, +{ + pub(crate) fn call(&mut self, a: Word, b: Word, c: Word, d: Word) -> Result { + let (ax, overflow) = a.overflowing_add(32); + let (cx, of) = c.overflowing_add(32); + let overflow = overflow || of; + + if overflow || ax > VM_MAX_RAM || cx > VM_MAX_RAM { + return Err(PanicReason::MemoryOverflow.into()); + } + + let call = Call::try_from(&self.memory[a as usize..])?; + let asset_id = + AssetId::try_from(&self.memory[c as usize..cx as usize]).expect("Unreachable! Checked memory range"); + + if self.is_external_context() { + self.external_asset_id_balance_sub(&asset_id, b)?; + } else { + let source_contract = *self.internal_contract()?; + self.balance_decrease(&source_contract, &asset_id, b)?; + } + + if !self.tx.input_contracts().any(|contract| call.to() == contract) { + return Err(PanicReason::ContractNotInInputs.into()); + } + + // credit contract asset_id balance + self.balance_increase(call.to(), &asset_id, b)?; + + let mut frame = self.call_frame(call, asset_id)?; + + let stack = frame.to_bytes(); + let len = stack.len() as Word; + + if len > self.registers[REG_HP] || self.registers[REG_SP] > self.registers[REG_HP] - len { + return Err(PanicReason::MemoryOverflow.into()); + } + + self.registers[REG_FP] = self.registers[REG_SP]; + self.registers[REG_SP] += len; + self.registers[REG_SSP] = self.registers[REG_SP]; + + self.memory[self.registers[REG_FP] as usize..self.registers[REG_SP] as usize].copy_from_slice(stack.as_slice()); + + self.registers[REG_BAL] = b; + self.registers[REG_PC] = self.registers[REG_FP].saturating_add(CallFrame::code_offset() as Word); + self.registers[REG_IS] = self.registers[REG_PC]; + self.registers[REG_CGAS] = cmp::min(self.registers[REG_GGAS], d); + + let receipt = Receipt::call( + self.internal_contract_or_default(), + *frame.to(), + b, + *frame.asset_id(), + d, + frame.a(), + frame.b(), + self.registers[REG_PC], + self.registers[REG_IS], + ); + + self.receipts.push(receipt); + self.frames.push(frame); + + self.run_call() + } +} diff --git a/src/interpreter/initialization.rs b/src/interpreter/initialization.rs index d4178c48ab..1847accc2e 100644 --- a/src/interpreter/initialization.rs +++ b/src/interpreter/initialization.rs @@ -4,24 +4,25 @@ use crate::context::Context; use crate::error::InterpreterError; use crate::storage::InterpreterStorage; -use fuel_tx::consts::MAX_INPUTS; -use fuel_tx::{Input, Output, Transaction, ValidationError}; +use fuel_tx::default_parameters::*; +use fuel_tx::{ConsensusParameters, Input, Output, Transaction, ValidationError}; use fuel_types::bytes::{SerializableVec, SizedBytes}; use fuel_types::{AssetId, Word}; use itertools::Itertools; + use std::collections::HashMap; use std::io; -impl Interpreter -where - S: InterpreterStorage, -{ - pub(crate) fn init(&mut self, mut tx: Transaction) -> Result<(), InterpreterError> { - tx.validate_without_signature(self.block_height() as Word)?; +impl Interpreter { + /// Initialize the VM with a given transaction + pub fn init(&mut self, predicate: bool, block_height: u32, mut tx: Transaction) -> Result<(), InterpreterError> { + let params = ConsensusParameters::default(); + + tx.validate_without_signature(self.block_height() as Word, ¶ms)?; tx.precompute_metadata(); - self.block_height = self.storage.block_height().map_err(InterpreterError::from_io)?; - self.context = Context::from(&tx); + self.block_height = block_height; + self.context = if predicate { Context::Predicate } else { Context::Script }; self.frames.clear(); self.receipts.clear(); @@ -38,26 +39,39 @@ where self.push_stack(tx.id().as_ref()) .map_err(|e| io::Error::new(io::ErrorKind::Other, e))?; - // Set initial unused balances - let free_balances = Self::initial_free_balances(&tx)?; - for (asset_id, amount) in free_balances.iter().sorted_by_key(|i| i.0) { - // push asset ID - self.push_stack(asset_id.as_ref()) - .map_err(|e| io::Error::new(io::ErrorKind::Other, e))?; - // stack position - let asset_id_offset = self.registers[REG_SSP] as usize; - self.unused_balance_index.insert(*asset_id, asset_id_offset); - // push spendable amount - self.push_stack(&amount.to_be_bytes()) - .map_err(|e| io::Error::new(io::ErrorKind::Other, e))?; - } + let free_balances = if predicate { + // predicate verification should zero asset ids + 0 + } else { + // Set initial unused balances + let free_balances = Self::initial_free_balances(&tx)?; + + for (asset_id, amount) in free_balances.iter().sorted_by_key(|i| i.0) { + // push asset ID + self.push_stack(asset_id.as_ref()) + .map_err(|e| io::Error::new(io::ErrorKind::Other, e))?; + + // stack position + let asset_id_offset = self.registers[REG_SSP] as usize; + self.unused_balance_index.insert(*asset_id, asset_id_offset); + + // push spendable amount + self.push_stack(&amount.to_be_bytes()) + .map_err(|e| io::Error::new(io::ErrorKind::Other, e))?; + } + + free_balances.len() as Word + }; + // zero out remaining unused balance types - for _i in free_balances.len()..(MAX_INPUTS as usize) { - self.push_stack(&[0; AssetId::LEN + WORD_SIZE]) - .map_err(|e| io::Error::new(io::ErrorKind::Other, e))?; - } + let unused_balances = MAX_INPUTS as Word - free_balances; + let unused_balances = unused_balances * (AssetId::LEN + WORD_SIZE) as Word; + + // Its safe to just reserve since the memory was properly zeroed before in this routine + self.reserve_stack(unused_balances)?; let tx_size = tx.serialized_size() as Word; + self.registers[REG_GGAS] = tx.gas_limit(); self.registers[REG_CGAS] = tx.gas_limit(); @@ -139,3 +153,19 @@ where Ok(balances) } } + +impl Interpreter +where + S: InterpreterStorage, +{ + /// Initialize the VM with a given transaction, backed by a storage provider that allows + /// execution of contract opcodes. + /// + /// For predicate verification, check [`Self::init`] + pub fn init_with_storage(&mut self, tx: Transaction) -> Result<(), InterpreterError> { + let predicate = false; + let block_height = self.storage.block_height().map_err(InterpreterError::from_io)?; + + self.init(predicate, block_height, tx) + } +} diff --git a/src/interpreter/internal.rs b/src/interpreter/internal.rs index 3b31c6aaba..f145cccc46 100644 --- a/src/interpreter/internal.rs +++ b/src/interpreter/internal.rs @@ -6,22 +6,29 @@ use crate::error::RuntimeError; use fuel_asm::{Instruction, PanicReason}; use fuel_tx::{Output, Transaction}; use fuel_types::{Address, AssetId, ContractId, RegisterId, Word}; + +use core::mem; use std::io::{self, ErrorKind, Read}; impl Interpreter { - pub(crate) fn push_stack(&mut self, data: &[u8]) -> Result<(), RuntimeError> { - let (ssp, overflow) = self.registers[REG_SSP].overflowing_add(data.len() as Word); + pub(crate) fn reserve_stack(&mut self, len: Word) -> Result { + let (ssp, overflow) = self.registers[REG_SSP].overflowing_add(len); if overflow || !self.is_external_context() && ssp > self.registers[REG_SP] { Err(PanicReason::MemoryOverflow.into()) } else { - self.memory[self.registers[REG_SSP] as usize..ssp as usize].copy_from_slice(data); - self.registers[REG_SSP] = ssp; - - Ok(()) + Ok(mem::replace(&mut self.registers[REG_SSP], ssp)) } } + pub(crate) fn push_stack(&mut self, data: &[u8]) -> Result<(), RuntimeError> { + let ssp = self.reserve_stack(data.len() as Word)?; + + self.memory[ssp as usize..self.registers[REG_SSP] as usize].copy_from_slice(data); + + Ok(()) + } + pub(crate) const fn block_height(&self) -> u32 { self.block_height } @@ -243,7 +250,7 @@ mod tests { vec![vec![].into()], ); - vm.init(tx).expect("Failed to init VM!"); + vm.init_with_storage(tx).expect("Failed to init VM!"); for (asset_id, amount) in balances { assert!(vm.external_asset_id_balance_sub(&asset_id, amount + 1).is_err()); @@ -286,7 +293,7 @@ mod tests { vec![Witness::default()], ); - vm.init(tx).expect("Failed to init VM!"); + vm.init_with_storage(tx).expect("Failed to init VM!"); // increase variable output vm.set_variable_output(0, asset_id_to_update, amount_to_set, owner) diff --git a/src/interpreter/memory.rs b/src/interpreter/memory.rs index fb02403728..df144cd607 100644 --- a/src/interpreter/memory.rs +++ b/src/interpreter/memory.rs @@ -422,47 +422,79 @@ mod tests { #[test] fn memcopy() { let mut vm = Interpreter::with_memory_storage(); - vm.init(Transaction::default()).expect("Failed to init VM"); + + vm.init_with_storage(Transaction::default()).expect("Failed to init VM"); let alloc = 1024; // r[0x10] := 1024 - vm.instruction(Opcode::ADDI(0x10, REG_ZERO, alloc).into()).unwrap(); - vm.instruction(Opcode::ALOC(0x10).into()).unwrap(); + vm.instruction( + Interpreter::instruction_script, + Opcode::ADDI(0x10, REG_ZERO, alloc).into(), + ) + .unwrap(); + vm.instruction(Interpreter::instruction_script, Opcode::ALOC(0x10).into()) + .unwrap(); // r[0x20] := 128 - vm.instruction(Opcode::ADDI(0x20, 0x20, 128).into()).unwrap(); + vm.instruction(Interpreter::instruction_script, Opcode::ADDI(0x20, 0x20, 128).into()) + .unwrap(); for i in 0..alloc { - vm.instruction(Opcode::ADDI(0x21, REG_ZERO, i).into()).unwrap(); - vm.instruction(Opcode::SB(REG_HP, 0x21, (i + 1) as Immediate12).into()) + vm.instruction(Interpreter::instruction_script, Opcode::ADDI(0x21, REG_ZERO, i).into()) .unwrap(); + vm.instruction( + Interpreter::instruction_script, + Opcode::SB(REG_HP, 0x21, (i + 1) as Immediate12).into(), + ) + .unwrap(); } // r[0x23] := m[$hp, 0x20] == m[0x12, 0x20] - vm.instruction(Opcode::MEQ(0x23, REG_HP, 0x12, 0x20).into()).unwrap(); + vm.instruction( + Interpreter::instruction_script, + Opcode::MEQ(0x23, REG_HP, 0x12, 0x20).into(), + ) + .unwrap(); + assert_eq!(0, vm.registers()[0x23]); // r[0x12] := $hp + r[0x20] - vm.instruction(Opcode::ADD(0x12, REG_HP, 0x20).into()).unwrap(); - vm.instruction(Opcode::ADD(0x12, REG_ONE, 0x12).into()).unwrap(); + vm.instruction(Interpreter::instruction_script, Opcode::ADD(0x12, REG_HP, 0x20).into()) + .unwrap(); + vm.instruction(Interpreter::instruction_script, Opcode::ADD(0x12, REG_ONE, 0x12).into()) + .unwrap(); // Test ownership - vm.instruction(Opcode::ADD(0x30, REG_HP, REG_ONE).into()).unwrap(); - vm.instruction(Opcode::MCP(0x30, 0x12, 0x20).into()).unwrap(); + vm.instruction( + Interpreter::instruction_script, + Opcode::ADD(0x30, REG_HP, REG_ONE).into(), + ) + .unwrap(); + vm.instruction(Interpreter::instruction_script, Opcode::MCP(0x30, 0x12, 0x20).into()) + .unwrap(); // r[0x23] := m[0x30, 0x20] == m[0x12, 0x20] - vm.instruction(Opcode::MEQ(0x23, 0x30, 0x12, 0x20).into()).unwrap(); + vm.instruction( + Interpreter::instruction_script, + Opcode::MEQ(0x23, 0x30, 0x12, 0x20).into(), + ) + .unwrap(); + assert_eq!(1, vm.registers()[0x23]); // Assert ownership - vm.instruction(Opcode::SUBI(0x24, REG_HP, 1).into()).unwrap(); - let ownership_violated = vm.instruction(Opcode::MCP(0x24, 0x12, 0x20).into()); + vm.instruction(Interpreter::instruction_script, Opcode::SUBI(0x24, REG_HP, 1).into()) + .unwrap(); + let ownership_violated = vm.instruction(Interpreter::instruction_script, Opcode::MCP(0x24, 0x12, 0x20).into()); + assert!(ownership_violated.is_err()); // Assert no panic on overlapping - vm.instruction(Opcode::SUBI(0x25, 0x12, 1).into()).unwrap(); - let overlapping = vm.instruction(Opcode::MCP(REG_HP, 0x25, 0x20).into()); + vm.instruction(Interpreter::instruction_script, Opcode::SUBI(0x25, 0x12, 1).into()) + .unwrap(); + let overlapping = vm.instruction(Interpreter::instruction_script, Opcode::MCP(REG_HP, 0x25, 0x20).into()); + assert!(overlapping.is_err()); } @@ -473,12 +505,16 @@ mod tests { assert_eq!(m, m_p); let mut vm = Interpreter::with_memory_storage(); - vm.init(Transaction::default()).expect("Failed to init VM"); + vm.init_with_storage(Transaction::default()).expect("Failed to init VM"); let bytes = 1024; - vm.instruction(Opcode::ADDI(0x10, REG_ZERO, bytes as Immediate12).into()) + vm.instruction( + Interpreter::instruction_script, + Opcode::ADDI(0x10, REG_ZERO, bytes as Immediate12).into(), + ) + .unwrap(); + vm.instruction(Interpreter::instruction_script, Opcode::ALOC(0x10).into()) .unwrap(); - vm.instruction(Opcode::ALOC(0x10).into()).unwrap(); let m = MemoryRange::new(vm.registers()[REG_HP], bytes); assert!(!vm.has_ownership_range(&m)); @@ -499,12 +535,16 @@ mod tests { #[test] fn stack_alloc_ownership() { let mut vm = Interpreter::with_memory_storage(); - vm.init(Transaction::default()).expect("Failed to init VM"); - vm.instruction(Opcode::MOVE(0x10, REG_SP).into()).unwrap(); - vm.instruction(Opcode::CFEI(2).into()).unwrap(); + vm.init_with_storage(Transaction::default()).expect("Failed to init VM"); + + vm.instruction(Interpreter::instruction_script, Opcode::MOVE(0x10, REG_SP).into()) + .unwrap(); + vm.instruction(Interpreter::instruction_script, Opcode::CFEI(2).into()) + .unwrap(); // Assert allocated stack is writable - vm.instruction(Opcode::MCLI(0x10, 2).into()).unwrap(); + vm.instruction(Interpreter::instruction_script, Opcode::MCLI(0x10, 2).into()) + .unwrap(); } } diff --git a/src/lib.rs b/src/lib.rs index 40d8d94710..5284b4aa10 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -6,7 +6,6 @@ pub mod backtrace; pub mod call; pub mod consts; pub mod context; -pub mod contract; pub mod crypto; pub mod error; pub mod gas; @@ -25,7 +24,7 @@ pub mod prelude { pub use fuel_asm::{Instruction, InstructionResult, Opcode, OpcodeRepr, PanicReason}; pub use fuel_storage::{MerkleRoot, MerkleStorage, Storage}; - pub use fuel_tx::{Input, Output, Receipt, Transaction, UtxoId, ValidationError, Witness}; + pub use fuel_tx::{Contract, Input, Output, Receipt, Transaction, UtxoId, ValidationError, Witness}; pub use fuel_types::{ bytes::{Deserializable, SerializableVec, SizedBytes}, Address, AssetId, Bytes32, Bytes4, Bytes64, Bytes8, ContractId, Immediate06, Immediate12, Immediate18, @@ -35,7 +34,6 @@ pub mod prelude { pub use crate::backtrace::Backtrace; pub use crate::call::{Call, CallFrame}; pub use crate::context::Context; - pub use crate::contract::Contract; pub use crate::error::{Infallible, InterpreterError, RuntimeError}; pub use crate::interpreter::{Interpreter, InterpreterMetadata, MemoryRange}; pub use crate::memory_client::{MemoryClient, MemoryStorage}; diff --git a/src/memory_client/mod.rs b/src/memory_client.rs similarity index 100% rename from src/memory_client/mod.rs rename to src/memory_client.rs diff --git a/src/memory_client/storage.rs b/src/memory_client/storage.rs index cf084c155d..66bb66ede3 100644 --- a/src/memory_client/storage.rs +++ b/src/memory_client/storage.rs @@ -1,10 +1,10 @@ -use crate::contract::Contract; use crate::crypto; use crate::error::Infallible; use crate::storage::InterpreterStorage; use fuel_crypto::Hasher; use fuel_storage::{MerkleRoot, MerkleStorage, Storage}; +use fuel_tx::Contract; use fuel_types::{Address, AssetId, Bytes32, ContractId, Salt, Word}; use itertools::Itertools; diff --git a/src/storage.rs b/src/storage.rs index d427c35b79..aa9d8047aa 100644 --- a/src/storage.rs +++ b/src/storage.rs @@ -1,8 +1,7 @@ //! Trait definitions for storage backend -use crate::contract::Contract; - use fuel_storage::{MerkleStorage, Storage}; +use fuel_tx::Contract; use fuel_types::{Address, AssetId, Bytes32, ContractId, Salt, Word}; use std::borrow::Cow; diff --git a/src/util.rs b/src/util.rs index c592cabf86..aca9b6218d 100644 --- a/src/util.rs +++ b/src/util.rs @@ -56,11 +56,14 @@ macro_rules! script_with_data_offset { #[cfg(any(test, feature = "test-helpers"))] /// Testing utilities pub mod test_helpers { - use crate::consts::{REG_ONE, REG_ZERO, VM_TX_MEMORY}; - use crate::prelude::{InterpreterStorage, MemoryClient, MemoryStorage, Transactor}; + use crate::consts::*; + use crate::memory_client::{MemoryClient, MemoryStorage}; use crate::state::StateTransition; + use crate::storage::InterpreterStorage; + use crate::transactor::Transactor; + use fuel_asm::Opcode; - use fuel_tx::{Input, Output, StorageSlot, Transaction, Witness}; + use fuel_tx::{Contract, Input, Output, StorageSlot, Transaction, Witness}; use fuel_types::bytes::{Deserializable, SizedBytes}; use fuel_types::{AssetId, ContractId, Immediate12, Salt, Word}; use itertools::Itertools; @@ -244,10 +247,11 @@ pub mod test_helpers { } else { Default::default() }; + let salt: Salt = self.rng.gen(); let program: Witness = contract.iter().copied().collect::>().into(); - let storage_root = crate::contract::Contract::initial_state_root(&storage_slots); - let contract = crate::contract::Contract::from(program.as_ref()); + let storage_root = Contract::initial_state_root(storage_slots.iter()); + let contract = Contract::from(program.as_ref()); let contract_root = contract.root(); let contract_id = contract.id(&salt, &contract_root, &storage_root); diff --git a/tests/predicate.rs b/tests/predicate.rs index 3f01e8b7f5..c75f8aa21a 100644 --- a/tests/predicate.rs +++ b/tests/predicate.rs @@ -1,35 +1,17 @@ -use fuel_crypto::Hasher; use fuel_types::bytes; -use fuel_vm::consts::*; -use fuel_vm::prelude::*; use rand::rngs::StdRng; use rand::{Rng, SeedableRng}; -#[test] -fn predicate() { - let rng = &mut StdRng::seed_from_u64(2322u64); - - let mut vm = Interpreter::with_memory_storage(); - - let predicate_data = 0x23 as Word; - let predicate_data = predicate_data.to_be_bytes().to_vec(); - let predicate_data_len = bytes::padded_len(predicate_data.as_slice()) as Immediate12; +use fuel_vm::consts::*; +use fuel_vm::prelude::*; - let mut predicate = vec![]; +use core::iter; - predicate.push(Opcode::MOVI(0x10, 0x11)); - predicate.push(Opcode::ADDI(0x11, 0x10, 0x12)); - predicate.push(Opcode::MOVI(0x12, 0x08)); - predicate.push(Opcode::ALOC(0x12)); - predicate.push(Opcode::ADDI(0x12, REG_HP, 0x01)); - predicate.push(Opcode::SW(0x12, 0x11, 0)); - predicate.push(Opcode::MOVI(0x10, 0x08)); - predicate.push(Opcode::XIL(0x20, 0)); - predicate.push(Opcode::XIS(0x11, 0)); - predicate.push(Opcode::ADD(0x11, 0x11, 0x20)); - predicate.push(Opcode::SUBI(0x11, 0x11, predicate_data_len)); - predicate.push(Opcode::MEQ(0x10, 0x11, 0x12, 0x10)); - predicate.push(Opcode::RET(0x10)); +fn execute_predicate

(predicate: P, predicate_data: Vec) -> bool +where + P: IntoIterator, +{ + let rng = &mut StdRng::seed_from_u64(2322u64); let predicate: Vec = predicate .into_iter() @@ -37,64 +19,53 @@ fn predicate() { .flatten() .collect(); + let utxo_id = rng.gen(); + let amount = 0; + let asset_id = rng.gen(); let maturity = 0; - let salt: Salt = rng.gen(); - let witness = vec![]; - - let owner = *Hasher::hash(predicate.as_slice()); - let contract = Contract::from(witness.as_ref()); - let contract_root = contract.root(); - let state_root = Contract::default_state_root(); - let contract = contract.id(&salt, &contract_root, &state_root); - - let input = Input::coin_predicate( - rng.gen(), - owner.into(), - 0, - rng.gen(), - maturity, - predicate, - predicate_data, - ); - let output = Output::contract_created(contract, state_root); + + let owner = Input::predicate_owner(&predicate); + let input = Input::coin_predicate(utxo_id, owner, amount, asset_id, maturity, predicate, predicate_data); let gas_price = 0; let gas_limit = 1_000_000; let byte_price = 0; - let bytecode_witness_index = 0; - let static_contracts = vec![]; - let storage_slots = vec![]; - let inputs = vec![input]; - let outputs = vec![output]; - let witnesses = vec![witness.into()]; - - let tx = Transaction::create( + let script = vec![]; + let script_data = vec![]; + + let tx = Transaction::script( gas_price, gas_limit, byte_price, maturity, - bytecode_witness_index, - salt, - static_contracts, - storage_slots, - inputs, - outputs, - witnesses, + script, + script_data, + vec![input], + vec![], + vec![], ); - vm.transact(tx).expect("Failed to transact!"); + Interpreter::<()>::check_predicates(tx) } #[test] -fn predicate_false() { - let rng = &mut StdRng::seed_from_u64(2322u64); +fn predicate_minimal() { + let predicate = iter::once(Opcode::RET(0x01)); + let data = vec![]; - let mut vm = Interpreter::with_memory_storage(); + assert!(execute_predicate(predicate, data)); +} + +#[test] +fn predicate() { + let expected_data = 0x23 as Word; + let expected_data = expected_data.to_be_bytes().to_vec(); + let expected_data_len = bytes::padded_len(expected_data.as_slice()) as Immediate12; - let predicate_data = 0x24 as Word; - let predicate_data = predicate_data.to_be_bytes().to_vec(); - let predicate_data_len = bytes::padded_len(predicate_data.as_slice()) as Immediate12; + let wrong_data = 0x24 as Word; + let wrong_data = wrong_data.to_be_bytes().to_vec(); + // A script that will succeed only if the argument is 0x23 let mut predicate = vec![]; predicate.push(Opcode::MOVI(0x10, 0x11)); @@ -107,51 +78,10 @@ fn predicate_false() { predicate.push(Opcode::XIL(0x20, 0)); predicate.push(Opcode::XIS(0x11, 0)); predicate.push(Opcode::ADD(0x11, 0x11, 0x20)); - predicate.push(Opcode::SUBI(0x11, 0x11, predicate_data_len)); + predicate.push(Opcode::SUBI(0x11, 0x11, expected_data_len)); predicate.push(Opcode::MEQ(0x10, 0x11, 0x12, 0x10)); predicate.push(Opcode::RET(0x10)); - let predicate = predicate - .into_iter() - .map(|op| u32::from(op).to_be_bytes()) - .flatten() - .collect(); - - let maturity = 0; - let salt: Salt = rng.gen(); - let witness = vec![]; - - let contract = Contract::from(witness.as_ref()); - let contract_root = contract.root(); - let state_root = Contract::default_state_root(); - let contract = contract.id(&salt, &contract_root, &state_root); - - let input = Input::coin_predicate(rng.gen(), rng.gen(), 0, rng.gen(), maturity, predicate, predicate_data); - let output = Output::contract_created(contract, state_root); - - let gas_price = 0; - let gas_limit = 1_000_000; - let byte_price = 0; - let bytecode_witness_index = 0; - let static_contracts = vec![]; - let storage_slots = vec![]; - let inputs = vec![input]; - let outputs = vec![output]; - let witnesses = vec![witness.into()]; - - let tx = Transaction::create( - gas_price, - gas_limit, - byte_price, - maturity, - bytecode_witness_index, - salt, - static_contracts, - storage_slots, - inputs, - outputs, - witnesses, - ); - - assert!(vm.transact(tx).is_err()); + assert!(execute_predicate(predicate.iter().copied(), expected_data)); + assert!(!execute_predicate(predicate.iter().copied(), wrong_data)); }