From 6bdf83d578f6974ff763050e7328f7a20190659e Mon Sep 17 00:00:00 2001 From: Victor Lopez Date: Fri, 11 Jun 2021 13:27:41 +0200 Subject: [PATCH 1/4] Merge executors module --- src/interpreter.rs | 1 - src/interpreter/contract.rs | 198 +------------- src/interpreter/execution.rs | 291 --------------------- src/interpreter/executors.rs | 481 ++++++++++++++++++++++++++++++++++- 4 files changed, 482 insertions(+), 489 deletions(-) delete mode 100644 src/interpreter/execution.rs diff --git a/src/interpreter.rs b/src/interpreter.rs index af2e2caa1f..bd7001d0eb 100644 --- a/src/interpreter.rs +++ b/src/interpreter.rs @@ -11,7 +11,6 @@ mod blockchain; mod contract; mod crypto; mod error; -mod execution; mod executors; mod flow; mod frame; diff --git a/src/interpreter/contract.rs b/src/interpreter/contract.rs index 47f3fddb71..3d99b203c9 100644 --- a/src/interpreter/contract.rs +++ b/src/interpreter/contract.rs @@ -1,19 +1,13 @@ -use super::{ExecuteError, Interpreter, MemoryRange}; +use super::{ExecuteError, Interpreter}; use crate::consts::*; use crate::crypto; use crate::data::InterpreterStorage; -use fuel_asm::{Opcode, Word}; -use fuel_tx::bytes::{SerializableVec, SizedBytes}; -use fuel_tx::consts::*; +use fuel_asm::Word; use fuel_tx::crypto as tx_crypto; -use fuel_tx::{Color, ContractAddress, Input, Output, Transaction, ValidationError}; -use itertools::Itertools; +use fuel_tx::{Color, ContractAddress, Transaction, ValidationError}; use std::convert::TryFrom; -use std::mem; - -const WORD_SIZE: usize = mem::size_of::(); #[derive(Debug, Default, Clone, PartialEq, Eq, Hash)] pub struct Contract(Vec); @@ -105,190 +99,4 @@ where pub fn color_balance(&self, color: &Color) -> Result { Ok(self.storage.get(color)?.unwrap_or(0)) } - - pub fn init(&mut self, tx: Transaction) -> Result<(), ExecuteError> { - self._init(tx, false) - } - - fn _init(&mut self, mut tx: Transaction, predicate: bool) -> Result<(), ExecuteError> { - tx.validate(self.block_height() as Word)?; - - self.frames.clear(); - self.log.clear(); - - // Optimized for memset - self.registers.iter_mut().for_each(|r| *r = 0); - - self.registers[REG_ONE] = 1; - self.registers[REG_SSP] = 0; - - // Set heap area - self.registers[REG_FP] = VM_MAX_RAM - 1; - self.registers[REG_HP] = self.registers[REG_FP]; - - self.push_stack(tx.id().as_ref())?; - - let zeroes = &[0; MAX_INPUTS as usize * (Color::size_of() + WORD_SIZE)]; - let mut ssp = self.registers[REG_SSP] as usize; - - self.push_stack(zeroes)?; - if !predicate { - tx.inputs() - .iter() - .filter_map(|input| match input { - Input::Coin { color, .. } => Some(color), - _ => None, - }) - .sorted() - .take(MAX_INPUTS as usize) - .try_for_each::<_, Result<_, ExecuteError>>(|color| { - let balance = self.color_balance(color)?; - - self.memory[ssp..ssp + Color::size_of()].copy_from_slice(color.as_ref()); - ssp += Color::size_of(); - - self.memory[ssp..ssp + WORD_SIZE].copy_from_slice(&balance.to_be_bytes()); - ssp += WORD_SIZE; - - Ok(()) - })?; - } - - let tx_size = tx.serialized_size() as Word; - self.push_stack(&tx_size.to_be_bytes())?; - self.push_stack(tx.to_bytes().as_slice())?; - - self.registers[REG_SP] = self.registers[REG_SSP]; - - self.tx = tx; - - Ok(()) - } - - pub fn run(&mut self) -> Result<(), ExecuteError> { - let tx = &self.tx; - - match tx { - Transaction::Create { - salt, static_contracts, .. - } => { - if static_contracts - .iter() - .any(|id| !self.check_contract_exists(id).unwrap_or(false)) - { - Err(ExecuteError::TransactionCreateStaticContractNotFound)? - } - - let contract = Contract::try_from(tx)?; - let id = contract.address(salt.as_ref()); - if !tx - .outputs() - .iter() - .any(|output| matches!(output, Output::ContractCreated { contract_id } if contract_id == &id)) - { - Err(ExecuteError::TransactionCreateIdNotInTx)?; - } - - self.storage.insert(id, contract)?; - - // 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 = tx - .inputs() - .iter() - .enumerate() - .filter_map(|(i, input)| match input { - Input::Coin { predicate, .. } if !predicate.is_empty() => tx - .input_coin_predicate_offset(i) - .map(|ofs| (ofs as Word, predicate.len() as Word)), - _ => None, - }) - .map(|(ofs, len)| (ofs + Self::tx_mem_address() as Word, len)) - .map(|(ofs, len)| MemoryRange::new(ofs, len)) - .collect(); - - predicates - .iter() - .try_for_each(|predicate| self.verify_predicate(predicate))?; - - Ok(()) - } - - Transaction::Script { .. } => { - let offset = (Self::tx_mem_address() + Transaction::script_offset()) as Word; - - self.registers[REG_PC] = offset; - self.registers[REG_IS] = offset; - self.registers[REG_GGAS] = self.tx.gas_limit(); - self.registers[REG_CGAS] = self.tx.gas_limit(); - - // TODO set tree balance - - self.run_program() - } - } - } - - pub fn run_program(&mut self) -> Result<(), ExecuteError> { - loop { - if self.registers[REG_PC] >= VM_MAX_RAM { - return Err(ExecuteError::ProgramOverflow); - } - - let op = self.memory[self.registers[REG_PC] as usize..] - .chunks_exact(4) - .next() - .map(Opcode::from_bytes_unchecked) - .ok_or(ExecuteError::ProgramOverflow)?; - - if let Opcode::RET(ra) = op { - if Self::is_valid_register(ra) && self.ret(ra) && self.inc_pc() { - return Ok(()); - } else { - return Err(ExecuteError::OpcodeFailure(op)); - } - } - - self.execute(op)?; - } - } - - pub fn verify_predicate(&mut self, predicate: &MemoryRange) -> Result<(), ExecuteError> { - // TODO initialize VM with tx prepared for sign - let (start, end) = predicate.boundaries(&self); - - self.registers[REG_PC] = start; - self.registers[REG_IS] = start; - - // TODO optimize - loop { - let pc = self.registers[REG_PC]; - let op = self.memory[pc as usize..] - .chunks_exact(Opcode::BYTES_SIZE) - .next() - .map(Opcode::from_bytes_unchecked) - .ok_or(ExecuteError::PredicateOverflow)?; - - if let Opcode::RET(ra) = op { - return self - .registers - .get(ra) - .ok_or(ExecuteError::OpcodeFailure(op)) - .and_then(|ret| { - if ret == &1 { - Ok(()) - } else { - Err(ExecuteError::PredicateFailure) - } - }); - } - - self.execute(op)?; - - if self.registers[REG_PC] < pc || self.registers[REG_PC] >= end { - return Err(ExecuteError::PredicateOverflow); - } - } - } } diff --git a/src/interpreter/execution.rs b/src/interpreter/execution.rs deleted file mode 100644 index a1420a5e07..0000000000 --- a/src/interpreter/execution.rs +++ /dev/null @@ -1,291 +0,0 @@ -use super::{ExecuteError, Interpreter}; -use crate::data::InterpreterStorage; - -use fuel_asm::{Opcode, RegisterId, Word}; -use tracing::debug; - -use std::ops::Div; - -impl Interpreter -where - S: InterpreterStorage, -{ - pub fn execute(&mut self, op: Opcode) -> Result<(), ExecuteError> { - let mut result = Ok(()); - - debug!("Executing {:?}", op); - debug!( - "Current state: {:?}", - op.registers() - .iter() - .filter_map(|r| *r) - .map(|r| (r, self.registers.get(r).copied())) - .collect::)>>() - ); - - match op { - Opcode::ADD(ra, rb, rc) if Self::is_valid_register_triple_alu(ra, rb, rc) => { - self.alu_overflow(ra, Word::overflowing_add, self.registers[rb], self.registers[rc]) - } - - Opcode::ADDI(ra, rb, imm) if Self::is_valid_register_couple_alu(ra, rb) => { - self.alu_overflow(ra, Word::overflowing_add, self.registers[rb], imm as Word) - } - - Opcode::AND(ra, rb, rc) if Self::is_valid_register_triple_alu(ra, rb, rc) => { - self.alu_set(ra, self.registers[rb] & self.registers[rc]) - } - - Opcode::ANDI(ra, rb, imm) if Self::is_valid_register_couple_alu(ra, rb) => { - self.alu_set(ra, self.registers[rb] & (imm as Word)) - } - - Opcode::DIV(ra, rb, rc) if Self::is_valid_register_triple_alu(ra, rb, rc) => self.alu_error( - ra, - Word::div, - self.registers[rb], - self.registers[rc], - self.registers[rc] == 0, - ), - - Opcode::DIVI(ra, rb, imm) if Self::is_valid_register_couple_alu(ra, rb) => { - self.alu_error(ra, Word::div, self.registers[rb], imm as Word, imm == 0) - } - - Opcode::EQ(ra, rb, rc) if Self::is_valid_register_triple_alu(ra, rb, rc) => { - self.alu_set(ra, (self.registers[rb] == self.registers[rc]) as Word) - } - - Opcode::EXP(ra, rb, rc) if Self::is_valid_register_triple_alu(ra, rb, rc) => { - self.alu_overflow(ra, Word::overflowing_pow, self.registers[rb], self.registers[rc] as u32) - } - - Opcode::EXPI(ra, rb, imm) if Self::is_valid_register_couple_alu(ra, rb) => { - self.alu_overflow(ra, Word::overflowing_pow, self.registers[rb], imm as u32) - } - - Opcode::GT(ra, rb, rc) if Self::is_valid_register_triple_alu(ra, rb, rc) => { - self.alu_set(ra, (self.registers[rb] > self.registers[rc]) as Word) - } - - Opcode::MLOG(ra, rb, rc) if Self::is_valid_register_triple_alu(ra, rb, rc) => self.alu_error( - ra, - |b, c| (b as f64).log(c as f64).trunc() as Word, - self.registers[rb], - self.registers[rc], - self.registers[rb] == 0 || self.registers[rc] <= 1, - ), - - Opcode::MROO(ra, rb, rc) if Self::is_valid_register_triple_alu(ra, rb, rc) => self.alu_error( - ra, - |b, c| (b as f64).powf((c as f64).recip()).trunc() as Word, - self.registers[rb], - self.registers[rc], - self.registers[rc] == 0, - ), - - Opcode::MOD(ra, rb, rc) if Self::is_valid_register_triple_alu(ra, rb, rc) => self.alu_error( - ra, - Word::wrapping_rem, - self.registers[rb], - self.registers[rc], - self.registers[rc] == 0, - ), - - Opcode::MODI(ra, rb, imm) if Self::is_valid_register_couple_alu(ra, rb) => { - self.alu_error(ra, Word::wrapping_rem, self.registers[rb], imm as Word, imm == 0) - } - - Opcode::MOVE(ra, rb) if Self::is_valid_register_couple_alu(ra, rb) => self.alu_set(ra, self.registers[rb]), - - Opcode::MUL(ra, rb, rc) if Self::is_valid_register_triple_alu(ra, rb, rc) => { - self.alu_overflow(ra, Word::overflowing_mul, self.registers[rb], self.registers[rc]) - } - - Opcode::MULI(ra, rb, imm) if Self::is_valid_register_couple_alu(ra, rb) => { - self.alu_overflow(ra, Word::overflowing_mul, self.registers[rb], imm as Word) - } - - Opcode::NOOP => self.alu_clear(), - - Opcode::NOT(ra, rb) if Self::is_valid_register_couple_alu(ra, rb) => self.alu_set(ra, !self.registers[rb]), - - Opcode::OR(ra, rb, rc) if Self::is_valid_register_triple_alu(ra, rb, rc) => { - self.alu_set(ra, self.registers[rb] | self.registers[rc]) - } - - Opcode::ORI(ra, rb, imm) if Self::is_valid_register_couple_alu(ra, rb) => { - self.alu_set(ra, self.registers[rb] | (imm as Word)) - } - - Opcode::SLL(ra, rb, rc) if Self::is_valid_register_triple_alu(ra, rb, rc) => { - self.alu_overflow(ra, Word::overflowing_shl, self.registers[rb], self.registers[rc] as u32) - } - - Opcode::SLLI(ra, rb, imm) if Self::is_valid_register_couple_alu(ra, rb) => { - self.alu_overflow(ra, Word::overflowing_shl, self.registers[rb], imm as u32) - } - - Opcode::SRL(ra, rb, rc) if Self::is_valid_register_triple_alu(ra, rb, rc) => { - self.alu_overflow(ra, Word::overflowing_shr, self.registers[rb], self.registers[rc] as u32) - } - - Opcode::SRLI(ra, rb, imm) if Self::is_valid_register_couple_alu(ra, rb) => { - self.alu_overflow(ra, Word::overflowing_shr, self.registers[rb], imm as u32) - } - - Opcode::SUB(ra, rb, rc) if Self::is_valid_register_triple_alu(ra, rb, rc) => { - self.alu_overflow(ra, Word::overflowing_sub, self.registers[rb], self.registers[rc]) - } - - Opcode::SUBI(ra, rb, imm) if Self::is_valid_register_couple_alu(ra, rb) => { - self.alu_overflow(ra, Word::overflowing_sub, self.registers[rb], imm as Word) - } - - Opcode::XOR(ra, rb, rc) if Self::is_valid_register_triple_alu(ra, rb, rc) => { - self.alu_set(ra, self.registers[rb] ^ self.registers[rc]) - } - - Opcode::XORI(ra, rb, imm) if Self::is_valid_register_couple_alu(ra, rb) => { - self.alu_set(ra, self.registers[rb] ^ (imm as Word)) - } - - Opcode::CIMV(ra, rb, rc) - if Self::is_valid_register_triple_alu(ra, rb, rc) - && self.check_input_maturity(ra, self.registers[rb], self.registers[rc]) - && self.inc_pc() => {} - - Opcode::CTMV(ra, rb) - if Self::is_valid_register_couple_alu(ra, rb) - && self.check_tx_maturity(ra, self.registers[rb]) - && self.inc_pc() => {} - - Opcode::JI(imm) if self.jump(imm as Word) => {} - - Opcode::JNEI(ra, rb, imm) - if Self::is_valid_register_couple(ra, rb) - && self.jump_not_equal_imm(self.registers[ra], self.registers[rb], imm as Word) => {} - - Opcode::RET(ra) if Self::is_valid_register(ra) && self.ret(ra) && self.inc_pc() => {} - - Opcode::ALOC(ra) if Self::is_valid_register(ra) && self.malloc(self.registers[ra]) && self.inc_pc() => {} - - Opcode::CFEI(imm) if self.stack_pointer_overflow(Word::overflowing_add, imm as Word) && self.inc_pc() => {} - - Opcode::CFSI(imm) if self.stack_pointer_overflow(Word::overflowing_sub, imm as Word) && self.inc_pc() => {} - - Opcode::LB(ra, rb, imm) - if Self::is_valid_register_couple_alu(ra, rb) - && self.load_byte(ra, rb, imm as Word) - && self.inc_pc() => {} - - Opcode::LW(ra, rb, imm) - if Self::is_valid_register_couple_alu(ra, rb) - && self.load_word(ra, self.registers[rb], imm as Word) - && self.inc_pc() => {} - - Opcode::MCL(ra, rb) - if Self::is_valid_register_couple(ra, rb) - && self.memclear(self.registers[ra], self.registers[rb]) - && self.inc_pc() => {} - - Opcode::MCLI(ra, imm) - if Self::is_valid_register(ra) && self.memclear(self.registers[ra], imm as Word) && self.inc_pc() => {} - - Opcode::MCP(ra, rb, rc) - if Self::is_valid_register_triple(ra, rb, rc) - && self.memcopy(self.registers[ra], self.registers[rb], self.registers[rc]) - && self.inc_pc() => {} - - Opcode::MEQ(ra, rb, rc, rd) - if Self::is_valid_register_quadruple_alu(ra, rb, rc, rd) - && self.memeq(ra, self.registers[rb], self.registers[rc], self.registers[rd]) - && self.inc_pc() => {} - - Opcode::SB(ra, rb, imm) - if Self::is_valid_register_couple(ra, rb) - && self.store_byte(self.registers[ra], self.registers[rb], imm as Word) - && self.inc_pc() => {} - - Opcode::SW(ra, rb, imm) - if Self::is_valid_register_couple(ra, rb) - && self.store_word(self.registers[ra], self.registers[rb], imm as Word) - && self.inc_pc() => {} - - Opcode::BHEI(ra) if Self::is_valid_register_alu(ra) && self.inc_pc() => { - self.registers[ra] = self.block_height() as Word - } - - // TODO BLOCKHASH: Block hash - Opcode::BURN(ra) if Self::is_valid_register(ra) && self.burn(self.registers[ra]) && self.inc_pc() => {} - - Opcode::CALL(ra, rb, rc, rd) - if Self::is_valid_register_quadruple(ra, rb, rc, rd) - && self.call( - self.registers[ra], - self.registers[rb], - self.registers[rc], - self.registers[rd], - ) => {} - - Opcode::CCP(ra, rb, rc, rd) - if Self::is_valid_register_quadruple(ra, rb, rc, rd) - && self.code_copy( - self.registers[ra], - self.registers[rb], - self.registers[rc], - self.registers[rd], - ) - && self.inc_pc() => {} - - // TODO CODEROOT: Code Merkle root - // TODO CODESIZE: Code size - // TODO COINBASE: Block proposer address - // TODO LOADCODE: Load code from an external contract - Opcode::LOG(ra, rb, rc, rd) - if Self::is_valid_register_quadruple(ra, rb, rc, rd) - && self.log_append(&[ra, rb, rc, rd]) - && self.inc_pc() => {} - - Opcode::MINT(ra) if Self::is_valid_register(ra) && self.mint(self.registers[ra]) && self.inc_pc() => {} - - // TODO REVERT: Revert - // TODO SLOADCODE: Load code from static list - // TODO SRW: State read word - // TODO SRWQ: State read 32 bytes - // TODO SWW: State write word - // TODO SWWQ: State write 32 bytes - // TODO TRANSFER: Transfer coins to contract - // TODO TRANSFEROUT: Transfer coins to output - Opcode::ECR(ra, rb, rc) - if Self::is_valid_register_triple(ra, rb, rc) - && self.ecrecover(self.registers[ra], self.registers[rb], self.registers[rc]) - && self.inc_pc() => {} - - Opcode::K256(ra, rb, rc) - if Self::is_valid_register_triple(ra, rb, rc) - && self.keccak256(self.registers[ra], self.registers[rb], self.registers[rc]) - && self.inc_pc() => {} - - Opcode::S256(ra, rb, rc) - if Self::is_valid_register_triple(ra, rb, rc) - && self.sha256(self.registers[ra], self.registers[rb], self.registers[rc]) - && self.inc_pc() => {} - - Opcode::FLAG(ra) if Self::is_valid_register(ra) && self.inc_pc() => self.set_flag(self.registers[ra]), - - _ => result = Err(ExecuteError::OpcodeFailure(op)), - } - - debug!( - "After state: {:?}", - op.registers() - .iter() - .filter_map(|r| *r) - .map(|r| (r, self.registers.get(r).copied())) - .collect::)>>() - ); - - result - } -} diff --git a/src/interpreter/executors.rs b/src/interpreter/executors.rs index b2da429d16..cbda19eeea 100644 --- a/src/interpreter/executors.rs +++ b/src/interpreter/executors.rs @@ -1,13 +1,211 @@ -use super::{ExecuteError, Interpreter}; +use super::{Contract, ExecuteError, Interpreter, MemoryRange}; +use crate::consts::*; use crate::data::InterpreterStorage; +use fuel_asm::{Opcode, RegisterId, Word}; use fuel_tx::bytes::Deserializable; -use fuel_tx::Transaction; +use fuel_tx::bytes::{SerializableVec, SizedBytes}; +use fuel_tx::consts::*; +use fuel_tx::{Color, Input, Output, Transaction}; +use itertools::Itertools; +use tracing::debug; + +use std::convert::TryFrom; +use std::mem; +use std::ops::Div; + +const WORD_SIZE: usize = mem::size_of::(); impl Interpreter where S: InterpreterStorage, { + pub fn init(&mut self, tx: Transaction) -> Result<(), ExecuteError> { + self._init(tx, false) + } + + fn _init(&mut self, mut tx: Transaction, predicate: bool) -> Result<(), ExecuteError> { + tx.validate(self.block_height() as Word)?; + + self.frames.clear(); + self.log.clear(); + + // Optimized for memset + self.registers.iter_mut().for_each(|r| *r = 0); + + self.registers[REG_ONE] = 1; + self.registers[REG_SSP] = 0; + + // Set heap area + self.registers[REG_FP] = VM_MAX_RAM - 1; + self.registers[REG_HP] = self.registers[REG_FP]; + + self.push_stack(tx.id().as_ref())?; + + let zeroes = &[0; MAX_INPUTS as usize * (Color::size_of() + WORD_SIZE)]; + let mut ssp = self.registers[REG_SSP] as usize; + + self.push_stack(zeroes)?; + if !predicate { + tx.inputs() + .iter() + .filter_map(|input| match input { + Input::Coin { color, .. } => Some(color), + _ => None, + }) + .sorted() + .take(MAX_INPUTS as usize) + .try_for_each::<_, Result<_, ExecuteError>>(|color| { + let balance = self.color_balance(color)?; + + self.memory[ssp..ssp + Color::size_of()].copy_from_slice(color.as_ref()); + ssp += Color::size_of(); + + self.memory[ssp..ssp + WORD_SIZE].copy_from_slice(&balance.to_be_bytes()); + ssp += WORD_SIZE; + + Ok(()) + })?; + } + + let tx_size = tx.serialized_size() as Word; + self.push_stack(&tx_size.to_be_bytes())?; + self.push_stack(tx.to_bytes().as_slice())?; + + self.registers[REG_SP] = self.registers[REG_SSP]; + + self.tx = tx; + + Ok(()) + } + + pub fn run(&mut self) -> Result<(), ExecuteError> { + let tx = &self.tx; + + match tx { + Transaction::Create { + salt, static_contracts, .. + } => { + if static_contracts + .iter() + .any(|id| !self.check_contract_exists(id).unwrap_or(false)) + { + Err(ExecuteError::TransactionCreateStaticContractNotFound)? + } + + let contract = Contract::try_from(tx)?; + let id = contract.address(salt.as_ref()); + if !tx + .outputs() + .iter() + .any(|output| matches!(output, Output::ContractCreated { contract_id } if contract_id == &id)) + { + Err(ExecuteError::TransactionCreateIdNotInTx)?; + } + + self.storage.insert(id, contract)?; + + // 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 = tx + .inputs() + .iter() + .enumerate() + .filter_map(|(i, input)| match input { + Input::Coin { predicate, .. } if !predicate.is_empty() => tx + .input_coin_predicate_offset(i) + .map(|ofs| (ofs as Word, predicate.len() as Word)), + _ => None, + }) + .map(|(ofs, len)| (ofs + Self::tx_mem_address() as Word, len)) + .map(|(ofs, len)| MemoryRange::new(ofs, len)) + .collect(); + + predicates + .iter() + .try_for_each(|predicate| self.verify_predicate(predicate))?; + + Ok(()) + } + + Transaction::Script { .. } => { + let offset = (Self::tx_mem_address() + Transaction::script_offset()) as Word; + + self.registers[REG_PC] = offset; + self.registers[REG_IS] = offset; + self.registers[REG_GGAS] = self.tx.gas_limit(); + self.registers[REG_CGAS] = self.tx.gas_limit(); + + // TODO set tree balance + + self.run_program() + } + } + } + + pub fn run_program(&mut self) -> Result<(), ExecuteError> { + loop { + if self.registers[REG_PC] >= VM_MAX_RAM { + return Err(ExecuteError::ProgramOverflow); + } + + let op = self.memory[self.registers[REG_PC] as usize..] + .chunks_exact(4) + .next() + .map(Opcode::from_bytes_unchecked) + .ok_or(ExecuteError::ProgramOverflow)?; + + if let Opcode::RET(ra) = op { + if Self::is_valid_register(ra) && self.ret(ra) && self.inc_pc() { + return Ok(()); + } else { + return Err(ExecuteError::OpcodeFailure(op)); + } + } + + self.execute(op)?; + } + } + + pub fn verify_predicate(&mut self, predicate: &MemoryRange) -> Result<(), ExecuteError> { + // TODO initialize VM with tx prepared for sign + let (start, end) = predicate.boundaries(&self); + + self.registers[REG_PC] = start; + self.registers[REG_IS] = start; + + // TODO optimize + loop { + let pc = self.registers[REG_PC]; + let op = self.memory[pc as usize..] + .chunks_exact(Opcode::BYTES_SIZE) + .next() + .map(Opcode::from_bytes_unchecked) + .ok_or(ExecuteError::PredicateOverflow)?; + + if let Opcode::RET(ra) = op { + return self + .registers + .get(ra) + .ok_or(ExecuteError::OpcodeFailure(op)) + .and_then(|ret| { + if ret == &1 { + Ok(()) + } else { + Err(ExecuteError::PredicateFailure) + } + }); + } + + self.execute(op)?; + + if self.registers[REG_PC] < pc || self.registers[REG_PC] >= end { + return Err(ExecuteError::PredicateOverflow); + } + } + } + pub fn execute_tx_bytes(storage: S, bytes: &[u8]) -> Result { let tx = Transaction::from_bytes(bytes)?; @@ -22,4 +220,283 @@ where Ok(vm) } + + pub fn execute(&mut self, op: Opcode) -> Result<(), ExecuteError> { + let mut result = Ok(()); + + debug!("Executing {:?}", op); + debug!( + "Current state: {:?}", + op.registers() + .iter() + .filter_map(|r| *r) + .map(|r| (r, self.registers.get(r).copied())) + .collect::)>>() + ); + + match op { + Opcode::ADD(ra, rb, rc) if Self::is_valid_register_triple_alu(ra, rb, rc) => { + self.alu_overflow(ra, Word::overflowing_add, self.registers[rb], self.registers[rc]) + } + + Opcode::ADDI(ra, rb, imm) if Self::is_valid_register_couple_alu(ra, rb) => { + self.alu_overflow(ra, Word::overflowing_add, self.registers[rb], imm as Word) + } + + Opcode::AND(ra, rb, rc) if Self::is_valid_register_triple_alu(ra, rb, rc) => { + self.alu_set(ra, self.registers[rb] & self.registers[rc]) + } + + Opcode::ANDI(ra, rb, imm) if Self::is_valid_register_couple_alu(ra, rb) => { + self.alu_set(ra, self.registers[rb] & (imm as Word)) + } + + Opcode::DIV(ra, rb, rc) if Self::is_valid_register_triple_alu(ra, rb, rc) => self.alu_error( + ra, + Word::div, + self.registers[rb], + self.registers[rc], + self.registers[rc] == 0, + ), + + Opcode::DIVI(ra, rb, imm) if Self::is_valid_register_couple_alu(ra, rb) => { + self.alu_error(ra, Word::div, self.registers[rb], imm as Word, imm == 0) + } + + Opcode::EQ(ra, rb, rc) if Self::is_valid_register_triple_alu(ra, rb, rc) => { + self.alu_set(ra, (self.registers[rb] == self.registers[rc]) as Word) + } + + Opcode::EXP(ra, rb, rc) if Self::is_valid_register_triple_alu(ra, rb, rc) => { + self.alu_overflow(ra, Word::overflowing_pow, self.registers[rb], self.registers[rc] as u32) + } + + Opcode::EXPI(ra, rb, imm) if Self::is_valid_register_couple_alu(ra, rb) => { + self.alu_overflow(ra, Word::overflowing_pow, self.registers[rb], imm as u32) + } + + Opcode::GT(ra, rb, rc) if Self::is_valid_register_triple_alu(ra, rb, rc) => { + self.alu_set(ra, (self.registers[rb] > self.registers[rc]) as Word) + } + + Opcode::MLOG(ra, rb, rc) if Self::is_valid_register_triple_alu(ra, rb, rc) => self.alu_error( + ra, + |b, c| (b as f64).log(c as f64).trunc() as Word, + self.registers[rb], + self.registers[rc], + self.registers[rb] == 0 || self.registers[rc] <= 1, + ), + + Opcode::MROO(ra, rb, rc) if Self::is_valid_register_triple_alu(ra, rb, rc) => self.alu_error( + ra, + |b, c| (b as f64).powf((c as f64).recip()).trunc() as Word, + self.registers[rb], + self.registers[rc], + self.registers[rc] == 0, + ), + + Opcode::MOD(ra, rb, rc) if Self::is_valid_register_triple_alu(ra, rb, rc) => self.alu_error( + ra, + Word::wrapping_rem, + self.registers[rb], + self.registers[rc], + self.registers[rc] == 0, + ), + + Opcode::MODI(ra, rb, imm) if Self::is_valid_register_couple_alu(ra, rb) => { + self.alu_error(ra, Word::wrapping_rem, self.registers[rb], imm as Word, imm == 0) + } + + Opcode::MOVE(ra, rb) if Self::is_valid_register_couple_alu(ra, rb) => self.alu_set(ra, self.registers[rb]), + + Opcode::MUL(ra, rb, rc) if Self::is_valid_register_triple_alu(ra, rb, rc) => { + self.alu_overflow(ra, Word::overflowing_mul, self.registers[rb], self.registers[rc]) + } + + Opcode::MULI(ra, rb, imm) if Self::is_valid_register_couple_alu(ra, rb) => { + self.alu_overflow(ra, Word::overflowing_mul, self.registers[rb], imm as Word) + } + + Opcode::NOOP => self.alu_clear(), + + Opcode::NOT(ra, rb) if Self::is_valid_register_couple_alu(ra, rb) => self.alu_set(ra, !self.registers[rb]), + + Opcode::OR(ra, rb, rc) if Self::is_valid_register_triple_alu(ra, rb, rc) => { + self.alu_set(ra, self.registers[rb] | self.registers[rc]) + } + + Opcode::ORI(ra, rb, imm) if Self::is_valid_register_couple_alu(ra, rb) => { + self.alu_set(ra, self.registers[rb] | (imm as Word)) + } + + Opcode::SLL(ra, rb, rc) if Self::is_valid_register_triple_alu(ra, rb, rc) => { + self.alu_overflow(ra, Word::overflowing_shl, self.registers[rb], self.registers[rc] as u32) + } + + Opcode::SLLI(ra, rb, imm) if Self::is_valid_register_couple_alu(ra, rb) => { + self.alu_overflow(ra, Word::overflowing_shl, self.registers[rb], imm as u32) + } + + Opcode::SRL(ra, rb, rc) if Self::is_valid_register_triple_alu(ra, rb, rc) => { + self.alu_overflow(ra, Word::overflowing_shr, self.registers[rb], self.registers[rc] as u32) + } + + Opcode::SRLI(ra, rb, imm) if Self::is_valid_register_couple_alu(ra, rb) => { + self.alu_overflow(ra, Word::overflowing_shr, self.registers[rb], imm as u32) + } + + Opcode::SUB(ra, rb, rc) if Self::is_valid_register_triple_alu(ra, rb, rc) => { + self.alu_overflow(ra, Word::overflowing_sub, self.registers[rb], self.registers[rc]) + } + + Opcode::SUBI(ra, rb, imm) if Self::is_valid_register_couple_alu(ra, rb) => { + self.alu_overflow(ra, Word::overflowing_sub, self.registers[rb], imm as Word) + } + + Opcode::XOR(ra, rb, rc) if Self::is_valid_register_triple_alu(ra, rb, rc) => { + self.alu_set(ra, self.registers[rb] ^ self.registers[rc]) + } + + Opcode::XORI(ra, rb, imm) if Self::is_valid_register_couple_alu(ra, rb) => { + self.alu_set(ra, self.registers[rb] ^ (imm as Word)) + } + + Opcode::CIMV(ra, rb, rc) + if Self::is_valid_register_triple_alu(ra, rb, rc) + && self.check_input_maturity(ra, self.registers[rb], self.registers[rc]) + && self.inc_pc() => {} + + Opcode::CTMV(ra, rb) + if Self::is_valid_register_couple_alu(ra, rb) + && self.check_tx_maturity(ra, self.registers[rb]) + && self.inc_pc() => {} + + Opcode::JI(imm) if self.jump(imm as Word) => {} + + Opcode::JNEI(ra, rb, imm) + if Self::is_valid_register_couple(ra, rb) + && self.jump_not_equal_imm(self.registers[ra], self.registers[rb], imm as Word) => {} + + Opcode::RET(ra) if Self::is_valid_register(ra) && self.ret(ra) && self.inc_pc() => {} + + Opcode::ALOC(ra) if Self::is_valid_register(ra) && self.malloc(self.registers[ra]) && self.inc_pc() => {} + + Opcode::CFEI(imm) if self.stack_pointer_overflow(Word::overflowing_add, imm as Word) && self.inc_pc() => {} + + Opcode::CFSI(imm) if self.stack_pointer_overflow(Word::overflowing_sub, imm as Word) && self.inc_pc() => {} + + Opcode::LB(ra, rb, imm) + if Self::is_valid_register_couple_alu(ra, rb) + && self.load_byte(ra, rb, imm as Word) + && self.inc_pc() => {} + + Opcode::LW(ra, rb, imm) + if Self::is_valid_register_couple_alu(ra, rb) + && self.load_word(ra, self.registers[rb], imm as Word) + && self.inc_pc() => {} + + Opcode::MCL(ra, rb) + if Self::is_valid_register_couple(ra, rb) + && self.memclear(self.registers[ra], self.registers[rb]) + && self.inc_pc() => {} + + Opcode::MCLI(ra, imm) + if Self::is_valid_register(ra) && self.memclear(self.registers[ra], imm as Word) && self.inc_pc() => {} + + Opcode::MCP(ra, rb, rc) + if Self::is_valid_register_triple(ra, rb, rc) + && self.memcopy(self.registers[ra], self.registers[rb], self.registers[rc]) + && self.inc_pc() => {} + + Opcode::MEQ(ra, rb, rc, rd) + if Self::is_valid_register_quadruple_alu(ra, rb, rc, rd) + && self.memeq(ra, self.registers[rb], self.registers[rc], self.registers[rd]) + && self.inc_pc() => {} + + Opcode::SB(ra, rb, imm) + if Self::is_valid_register_couple(ra, rb) + && self.store_byte(self.registers[ra], self.registers[rb], imm as Word) + && self.inc_pc() => {} + + Opcode::SW(ra, rb, imm) + if Self::is_valid_register_couple(ra, rb) + && self.store_word(self.registers[ra], self.registers[rb], imm as Word) + && self.inc_pc() => {} + + Opcode::BHEI(ra) if Self::is_valid_register_alu(ra) && self.inc_pc() => { + self.registers[ra] = self.block_height() as Word + } + + // TODO BLOCKHASH: Block hash + Opcode::BURN(ra) if Self::is_valid_register(ra) && self.burn(self.registers[ra]) && self.inc_pc() => {} + + Opcode::CALL(ra, rb, rc, rd) + if Self::is_valid_register_quadruple(ra, rb, rc, rd) + && self.call( + self.registers[ra], + self.registers[rb], + self.registers[rc], + self.registers[rd], + ) => {} + + Opcode::CCP(ra, rb, rc, rd) + if Self::is_valid_register_quadruple(ra, rb, rc, rd) + && self.code_copy( + self.registers[ra], + self.registers[rb], + self.registers[rc], + self.registers[rd], + ) + && self.inc_pc() => {} + + // TODO CODEROOT: Code Merkle root + // TODO CODESIZE: Code size + // TODO COINBASE: Block proposer address + // TODO LOADCODE: Load code from an external contract + Opcode::LOG(ra, rb, rc, rd) + if Self::is_valid_register_quadruple(ra, rb, rc, rd) + && self.log_append(&[ra, rb, rc, rd]) + && self.inc_pc() => {} + + Opcode::MINT(ra) if Self::is_valid_register(ra) && self.mint(self.registers[ra]) && self.inc_pc() => {} + + // TODO REVERT: Revert + // TODO SLOADCODE: Load code from static list + // TODO SRW: State read word + // TODO SRWQ: State read 32 bytes + // TODO SWW: State write word + // TODO SWWQ: State write 32 bytes + // TODO TRANSFER: Transfer coins to contract + // TODO TRANSFEROUT: Transfer coins to output + Opcode::ECR(ra, rb, rc) + if Self::is_valid_register_triple(ra, rb, rc) + && self.ecrecover(self.registers[ra], self.registers[rb], self.registers[rc]) + && self.inc_pc() => {} + + Opcode::K256(ra, rb, rc) + if Self::is_valid_register_triple(ra, rb, rc) + && self.keccak256(self.registers[ra], self.registers[rb], self.registers[rc]) + && self.inc_pc() => {} + + Opcode::S256(ra, rb, rc) + if Self::is_valid_register_triple(ra, rb, rc) + && self.sha256(self.registers[ra], self.registers[rb], self.registers[rc]) + && self.inc_pc() => {} + + Opcode::FLAG(ra) if Self::is_valid_register(ra) && self.inc_pc() => self.set_flag(self.registers[ra]), + + _ => result = Err(ExecuteError::OpcodeFailure(op)), + } + + debug!( + "After state: {:?}", + op.registers() + .iter() + .filter_map(|r| *r) + .map(|r| (r, self.registers.get(r).copied())) + .collect::)>>() + ); + + result + } } From 15aae2d6c910d12140cc151b5b42c8b71e4764d7 Mon Sep 17 00:00:00 2001 From: Victor Lopez Date: Thu, 17 Jun 2021 22:39:43 +0200 Subject: [PATCH 2/4] Add debug feature The internal debugger of the interpreter will behave as a state machine with idempotent methods - except for `eval_debugger_state`. This one will pop the last state and continue the execution of programs. All debug functionalities are wrapped inside this new feature, and no additional overhead will occur with the execution outside the debug environment. --- .github/workflows/cargo_test.yml | 6 ++ Cargo.toml | 3 + src/debug.rs | 11 ++ src/debug/debugger.rs | 105 +++++++++++++++++++ src/debug/dummy.rs | 2 + src/interpreter.rs | 7 ++ src/interpreter/debug.rs | 43 ++++++++ src/interpreter/error.rs | 3 + src/interpreter/executors.rs | 169 +++++++++++++++++++++++++------ src/interpreter/frame.rs | 4 + src/lib.rs | 2 + tests/interpreter/alu.rs | 4 +- 12 files changed, 326 insertions(+), 33 deletions(-) create mode 100644 src/debug.rs create mode 100644 src/debug/debugger.rs create mode 100644 src/debug/dummy.rs create mode 100644 src/interpreter/debug.rs diff --git a/.github/workflows/cargo_test.yml b/.github/workflows/cargo_test.yml index 90478bdaed..0e6a4b0d86 100644 --- a/.github/workflows/cargo_test.yml +++ b/.github/workflows/cargo_test.yml @@ -54,3 +54,9 @@ jobs: with: command: test args: --verbose + + - name: Run debug tests + uses: actions-rs/cargo@v1 + with: + command: test + args: --verbose --features debug diff --git a/Cargo.toml b/Cargo.toml index a6b3d17478..818e73b2d8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,3 +19,6 @@ fuel-tx = {git="ssh://git@github.com/FuelLabs/fuel-tx.git"} [dev-dependencies] rand = "0.8" + +[features] +debug = [] diff --git a/src/debug.rs b/src/debug.rs new file mode 100644 index 0000000000..858a8f72e9 --- /dev/null +++ b/src/debug.rs @@ -0,0 +1,11 @@ +#[cfg(not(feature = "debug"))] +mod dummy; + +#[cfg(feature = "debug")] +mod debugger; + +#[cfg(not(feature = "debug"))] +pub use dummy::DummyDebugger as Debugger; + +#[cfg(feature = "debug")] +pub use debugger::{Breakpoint, DebugEval, Debugger}; diff --git a/src/debug/debugger.rs b/src/debug/debugger.rs new file mode 100644 index 0000000000..f9eecf1846 --- /dev/null +++ b/src/debug/debugger.rs @@ -0,0 +1,105 @@ +use crate::interpreter::ProgramState; + +use fuel_asm::Word; +use fuel_tx::ContractAddress; + +use std::collections::{HashMap, HashSet}; + +#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)] +pub struct Breakpoint { + contract: ContractAddress, + pc: Word, +} + +impl Breakpoint { + pub const fn new(contract: ContractAddress, pc: Word) -> Self { + Self { contract, pc } + } + + pub const fn contract(&self) -> &ContractAddress { + &self.contract + } + + pub const fn pc(&self) -> Word { + self.pc + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum DebugEval { + Breakpoint(Breakpoint), + Continue, +} + +impl Default for DebugEval { + fn default() -> Self { + Self::Continue + } +} + +impl From for DebugEval { + fn from(b: Breakpoint) -> Self { + Self::Breakpoint(b) + } +} + +impl DebugEval { + pub const fn should_continue(&self) -> bool { + match self { + Self::Continue => true, + _ => false, + } + } +} + +#[derive(Debug, Default, Clone)] +pub struct Debugger { + breakpoints: HashMap>, + last_state: Option, +} + +impl Debugger { + pub fn set_breakpoint(&mut self, contract: ContractAddress, pc: Word) { + self.breakpoints + .get_mut(&contract) + .map(|set| set.insert(pc)) + .map(|_| ()) + .unwrap_or_else(|| { + let mut set = HashSet::new(); + + set.insert(pc); + + self.breakpoints.insert(contract, set); + }); + } + + pub fn remove_breakpoint(&mut self, contract: &ContractAddress, pc: Word) { + self.breakpoints.get_mut(contract).map(|set| set.remove(&pc)); + } + + pub fn eval_state(&mut self, contract: ContractAddress, pc: Word) -> DebugEval { + let last_state = self.last_state.take(); + + self.breakpoints + .get(&contract) + .map(|set| set.get(&pc)) + .flatten() + .map(|_| { + let breakpoint = Breakpoint::new(contract, pc); + + match last_state { + Some(s) if s == breakpoint => DebugEval::Continue, + _ => breakpoint.into(), + } + }) + .unwrap_or_default() + } + + pub fn set_last_state(&mut self, state: ProgramState) { + self.last_state.replace(state); + } + + pub const fn last_state(&self) -> &Option { + &self.last_state + } +} diff --git a/src/debug/dummy.rs b/src/debug/dummy.rs new file mode 100644 index 0000000000..370c67f8d9 --- /dev/null +++ b/src/debug/dummy.rs @@ -0,0 +1,2 @@ +#[derive(Debug, Default, Clone)] +pub struct DummyDebugger {} diff --git a/src/interpreter.rs b/src/interpreter.rs index bd7001d0eb..17df2fde5a 100644 --- a/src/interpreter.rs +++ b/src/interpreter.rs @@ -1,4 +1,5 @@ use crate::consts::*; +use crate::debug::Debugger; use fuel_asm::{RegisterId, Word}; use fuel_tx::consts::*; @@ -17,8 +18,12 @@ mod frame; mod log; mod memory; +#[cfg(feature = "debug")] +mod debug; + pub use contract::Contract; pub use error::ExecuteError; +pub use executors::ProgramState; pub use frame::{Call, CallFrame}; pub use log::LogEvent; pub use memory::MemoryRange; @@ -34,6 +39,7 @@ pub struct Interpreter { // TODO review all opcodes that mutates the tx in the stack and keep this one sync tx: Transaction, storage: S, + debugger: Debugger, } impl Interpreter { @@ -45,6 +51,7 @@ impl Interpreter { log: vec![], tx: Transaction::default(), storage, + debugger: Debugger::default(), } } diff --git a/src/interpreter/debug.rs b/src/interpreter/debug.rs new file mode 100644 index 0000000000..61c60abb16 --- /dev/null +++ b/src/interpreter/debug.rs @@ -0,0 +1,43 @@ +use super::{Interpreter, ProgramState}; +use crate::consts::*; +use crate::debug::DebugEval; + +use fuel_asm::Word; +use fuel_tx::ContractAddress; + +impl Interpreter { + pub fn set_breakpoint(&mut self, contract: Option, pc: Word) { + let contract = contract.unwrap_or_default(); + + self.debugger.set_breakpoint(contract, pc) + } + + pub fn remove_breakpoint(&mut self, contract: Option, pc: Word) { + let contract = contract.unwrap_or_default(); + + self.debugger.remove_breakpoint(&contract, pc) + } + + pub fn eval_debugger_state(&mut self) -> DebugEval { + let debugger = &mut self.debugger; + + let contract = self.frames.last().map(|f| f.to()).copied(); + let pc = match &contract { + Some(_) => self.registers[REG_PC].saturating_sub(self.registers[REG_IS]), + None => self.registers[REG_PC], + }; + + // Default contract address maps to unset contract target + let contract = contract.unwrap_or_default(); + + debugger.eval_state(contract, pc) + } + + pub fn debugger_set_last_state(&mut self, state: ProgramState) { + self.debugger.set_last_state(state) + } + + pub const fn debugger_last_state(&self) -> &Option { + self.debugger.last_state() + } +} diff --git a/src/interpreter/error.rs b/src/interpreter/error.rs index 2d4a9f9c66..05ecb30acb 100644 --- a/src/interpreter/error.rs +++ b/src/interpreter/error.rs @@ -18,6 +18,9 @@ pub enum ExecuteError { ProgramOverflow, PredicateFailure, ContractNotFound, + + #[cfg(feature = "debug")] + DebugStateNotInitialized, } impl fmt::Display for ExecuteError { diff --git a/src/interpreter/executors.rs b/src/interpreter/executors.rs index cbda19eeea..f3251af2da 100644 --- a/src/interpreter/executors.rs +++ b/src/interpreter/executors.rs @@ -14,8 +14,68 @@ use std::convert::TryFrom; use std::mem; use std::ops::Div; +#[cfg(feature = "debug")] +use crate::debug::{Breakpoint, DebugEval}; + const WORD_SIZE: usize = mem::size_of::(); +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum ExecuteState { + Proceed, + Return(Word), + + #[cfg(feature = "debug")] + DebugEvent(DebugEval), +} + +impl Default for ExecuteState { + fn default() -> Self { + Self::Proceed + } +} + +#[cfg(feature = "debug")] +impl From for ExecuteState { + fn from(d: DebugEval) -> Self { + Self::DebugEvent(d) + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum ProgramState { + Return(Word), + + #[cfg(feature = "debug")] + RunProgram(DebugEval), + + #[cfg(feature = "debug")] + VerifyPredicate(DebugEval), +} + +#[cfg(feature = "debug")] +impl PartialEq for ProgramState { + fn eq(&self, other: &Breakpoint) -> bool { + match self.debug_ref() { + Some(&DebugEval::Breakpoint(b)) => &b == other, + _ => false, + } + } +} + +#[cfg(feature = "debug")] +impl ProgramState { + pub const fn debug_ref(&self) -> Option<&DebugEval> { + match self { + Self::RunProgram(d) | Self::VerifyPredicate(d) => Some(d), + _ => None, + } + } + + pub const fn is_debug(&self) -> bool { + self.debug_ref().is_some() + } +} + impl Interpreter where S: InterpreterStorage, @@ -79,7 +139,18 @@ where Ok(()) } - pub fn run(&mut self) -> Result<(), ExecuteError> { + pub fn run(&mut self) -> Result { + let state = self._run()?; + + #[cfg(feature = "debug")] + if state.is_debug() { + self.debugger_set_last_state(state.clone()); + } + + Ok(state) + } + + fn _run(&mut self) -> Result { let tx = &self.tx; match tx { @@ -122,11 +193,19 @@ where .map(|(ofs, len)| MemoryRange::new(ofs, len)) .collect(); - predicates - .iter() - .try_for_each(|predicate| self.verify_predicate(predicate))?; + let mut 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); + } + } - Ok(()) + Ok(state) } Transaction::Script { .. } => { @@ -144,7 +223,21 @@ where } } - pub fn run_program(&mut self) -> Result<(), ExecuteError> { + #[cfg(feature = "debug")] + pub fn resume(&mut self) -> Result { + match self + .debugger_last_state() + .ok_or(ExecuteError::DebugStateNotInitialized)? + { + ProgramState::Return(w) => Ok(ProgramState::Return(w)), + + ProgramState::RunProgram(_) => self.run_program(), + + ProgramState::VerifyPredicate(_) => unimplemented!(), + } + } + + pub fn run_program(&mut self) -> Result { loop { if self.registers[REG_PC] >= VM_MAX_RAM { return Err(ExecuteError::ProgramOverflow); @@ -156,19 +249,22 @@ where .map(Opcode::from_bytes_unchecked) .ok_or(ExecuteError::ProgramOverflow)?; - if let Opcode::RET(ra) = op { - if Self::is_valid_register(ra) && self.ret(ra) && self.inc_pc() { - return Ok(()); - } else { - return Err(ExecuteError::OpcodeFailure(op)); + match self.execute(op)? { + ExecuteState::Return(r) => { + return Ok(ProgramState::Return(r)); } - } - self.execute(op)?; + #[cfg(feature = "debug")] + ExecuteState::DebugEvent(d) => { + return Ok(ProgramState::RunProgram(d)); + } + + _ => (), + } } } - pub fn verify_predicate(&mut self, predicate: &MemoryRange) -> Result<(), ExecuteError> { + pub fn verify_predicate(&mut self, predicate: &MemoryRange) -> Result { // TODO initialize VM with tx prepared for sign let (start, end) = predicate.boundaries(&self); @@ -184,21 +280,22 @@ where .map(Opcode::from_bytes_unchecked) .ok_or(ExecuteError::PredicateOverflow)?; - if let Opcode::RET(ra) = op { - return self - .registers - .get(ra) - .ok_or(ExecuteError::OpcodeFailure(op)) - .and_then(|ret| { - if ret == &1 { - Ok(()) - } else { - Err(ExecuteError::PredicateFailure) - } - }); - } + match self.execute(op)? { + ExecuteState::Return(r) => { + if r == 1 { + return Ok(ProgramState::Return(r)); + } else { + return Err(ExecuteError::PredicateFailure); + } + } + + #[cfg(feature = "debug")] + ExecuteState::DebugEvent(d) => { + return Ok(ProgramState::VerifyPredicate(d)); + } - self.execute(op)?; + _ => (), + } if self.registers[REG_PC] < pc || self.registers[REG_PC] >= end { return Err(ExecuteError::PredicateOverflow); @@ -221,8 +318,16 @@ where Ok(vm) } - pub fn execute(&mut self, op: Opcode) -> Result<(), ExecuteError> { - let mut result = Ok(()); + pub fn execute(&mut self, op: Opcode) -> Result { + let mut result = Ok(ExecuteState::Proceed); + + #[cfg(feature = "debug")] + { + let debug = self.eval_debugger_state(); + if !debug.should_continue() { + return Ok(debug.into()); + } + } debug!("Executing {:?}", op); debug!( @@ -377,7 +482,9 @@ where if Self::is_valid_register_couple(ra, rb) && self.jump_not_equal_imm(self.registers[ra], self.registers[rb], imm as Word) => {} - Opcode::RET(ra) if Self::is_valid_register(ra) && self.ret(ra) && self.inc_pc() => {} + Opcode::RET(ra) if Self::is_valid_register(ra) && self.ret(ra) && self.inc_pc() => { + result = Ok(ExecuteState::Return(self.registers[ra])); + } Opcode::ALOC(ra) if Self::is_valid_register(ra) && self.malloc(self.registers[ra]) && self.inc_pc() => {} diff --git a/src/interpreter/frame.rs b/src/interpreter/frame.rs index b7fa74781f..e4694fc519 100644 --- a/src/interpreter/frame.rs +++ b/src/interpreter/frame.rs @@ -172,6 +172,10 @@ impl CallFrame { pub const fn registers(&self) -> &[Word] { &self.registers } + + pub const fn to(&self) -> &ContractAddress { + &self.to + } } impl SizedBytes for CallFrame { diff --git a/src/lib.rs b/src/lib.rs index bdd62c3821..f98376df3d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -6,10 +6,12 @@ pub mod consts; pub mod crypto; pub mod data; +pub mod debug; pub mod interpreter; pub mod prelude { pub use crate::data::{InterpreterStorage, MemoryStorage, Storage}; + pub use crate::debug::Debugger; pub use crate::interpreter::{Call, CallFrame, Contract, ExecuteError, Interpreter, LogEvent, MemoryRange}; pub use fuel_asm::{Immediate06, Immediate12, Immediate18, Immediate24, Opcode, RegisterId, Word}; pub use fuel_tx::{ diff --git a/tests/interpreter/alu.rs b/tests/interpreter/alu.rs index 9f111c9ece..0278177e3c 100644 --- a/tests/interpreter/alu.rs +++ b/tests/interpreter/alu.rs @@ -8,7 +8,7 @@ fn alu(registers_init: &[(RegisterId, Immediate12)], op: Opcode, reg: RegisterId registers_init.iter().for_each(|(r, v)| { vm.execute(Opcode::ADDI(*r, *r, *v)) - .expect("Failed to execute the provided opcode!") + .expect("Failed to execute the provided opcode!"); }); vm.execute(op).expect("Failed to execute the final opcode!"); @@ -28,7 +28,7 @@ fn alu_err(registers_init: &[(RegisterId, Immediate12)], op: Opcode) { registers_init.iter().for_each(|(r, v)| { vm.execute(Opcode::ADDI(*r, *r, *v)) - .expect("Failed to execute the provided opcode!") + .expect("Failed to execute the provided opcode!"); }); let result = vm.execute(op); From 530ff38fec862cebc49b5a6270fa36838c6e5ee2 Mon Sep 17 00:00:00 2001 From: Victor Lopez Date: Fri, 18 Jun 2021 22:52:49 +0200 Subject: [PATCH 3/4] Add debug test case for tx script --- src/debug/debugger.rs | 45 ++++++++++++++++++++++---- src/interpreter/debug.rs | 22 ++++--------- src/interpreter/executors.rs | 13 ++++++-- src/lib.rs | 7 ++++- tests/interpreter/debug.rs | 61 ++++++++++++++++++++++++++++++++++++ tests/interpreter/mod.rs | 3 ++ 6 files changed, 125 insertions(+), 26 deletions(-) create mode 100644 tests/interpreter/debug.rs diff --git a/src/debug/debugger.rs b/src/debug/debugger.rs index f9eecf1846..5af1a7a55f 100644 --- a/src/debug/debugger.rs +++ b/src/debug/debugger.rs @@ -1,6 +1,6 @@ use crate::interpreter::ProgramState; -use fuel_asm::Word; +use fuel_asm::{Opcode, Word}; use fuel_tx::ContractAddress; use std::collections::{HashMap, HashSet}; @@ -12,10 +12,31 @@ pub struct Breakpoint { } impl Breakpoint { - pub const fn new(contract: ContractAddress, pc: Word) -> Self { + const fn raw(contract: ContractAddress, pc: Word) -> Self { Self { contract, pc } } + /// Create a new contract breakpoint + /// + /// The `$pc` is provided in op count and internally is multiplied by the op + /// size. Also, the op count is always relative to `$is` so it should + /// consider only the bytecode of the contract. + pub const fn new(contract: ContractAddress, pc: Word) -> Self { + let pc = pc * (Opcode::BYTES_SIZE as Word); + + Self::raw(contract, pc) + } + + /// Create a new script breakpoint + /// + /// The `$pc` is provided in op count and internally is multiplied by the op + /// size + pub fn script(pc: Word) -> Self { + let contract = Default::default(); + + Self::new(contract, pc) + } + pub const fn contract(&self) -> &ContractAddress { &self.contract } @@ -50,6 +71,13 @@ impl DebugEval { _ => false, } } + + pub const fn breakpoint(&self) -> Option<&Breakpoint> { + match self { + Self::Breakpoint(b) => Some(b), + _ => None, + } + } } #[derive(Debug, Default, Clone)] @@ -59,7 +87,10 @@ pub struct Debugger { } impl Debugger { - pub fn set_breakpoint(&mut self, contract: ContractAddress, pc: Word) { + pub fn set_breakpoint(&mut self, breakpoint: Breakpoint) { + let contract = *breakpoint.contract(); + let pc = breakpoint.pc(); + self.breakpoints .get_mut(&contract) .map(|set| set.insert(pc)) @@ -73,8 +104,10 @@ impl Debugger { }); } - pub fn remove_breakpoint(&mut self, contract: &ContractAddress, pc: Word) { - self.breakpoints.get_mut(contract).map(|set| set.remove(&pc)); + pub fn remove_breakpoint(&mut self, breakpoint: &Breakpoint) { + self.breakpoints + .get_mut(breakpoint.contract()) + .map(|set| set.remove(&breakpoint.pc())); } pub fn eval_state(&mut self, contract: ContractAddress, pc: Word) -> DebugEval { @@ -85,7 +118,7 @@ impl Debugger { .map(|set| set.get(&pc)) .flatten() .map(|_| { - let breakpoint = Breakpoint::new(contract, pc); + let breakpoint = Breakpoint::raw(contract, pc); match last_state { Some(s) if s == breakpoint => DebugEval::Continue, diff --git a/src/interpreter/debug.rs b/src/interpreter/debug.rs index 61c60abb16..b9440ec907 100644 --- a/src/interpreter/debug.rs +++ b/src/interpreter/debug.rs @@ -1,31 +1,21 @@ use super::{Interpreter, ProgramState}; use crate::consts::*; -use crate::debug::DebugEval; - -use fuel_asm::Word; -use fuel_tx::ContractAddress; +use crate::debug::{Breakpoint, DebugEval}; impl Interpreter { - pub fn set_breakpoint(&mut self, contract: Option, pc: Word) { - let contract = contract.unwrap_or_default(); - - self.debugger.set_breakpoint(contract, pc) + pub fn set_breakpoint(&mut self, breakpoint: Breakpoint) { + self.debugger.set_breakpoint(breakpoint) } - pub fn remove_breakpoint(&mut self, contract: Option, pc: Word) { - let contract = contract.unwrap_or_default(); - - self.debugger.remove_breakpoint(&contract, pc) + pub fn remove_breakpoint(&mut self, breakpoint: &Breakpoint) { + self.debugger.remove_breakpoint(breakpoint) } pub fn eval_debugger_state(&mut self) -> DebugEval { let debugger = &mut self.debugger; let contract = self.frames.last().map(|f| f.to()).copied(); - let pc = match &contract { - Some(_) => self.registers[REG_PC].saturating_sub(self.registers[REG_IS]), - None => self.registers[REG_PC], - }; + let pc = self.registers[REG_PC].saturating_sub(self.registers[REG_IS]); // Default contract address maps to unset contract target let contract = contract.unwrap_or_default(); diff --git a/src/interpreter/executors.rs b/src/interpreter/executors.rs index f3251af2da..d5edcbcce3 100644 --- a/src/interpreter/executors.rs +++ b/src/interpreter/executors.rs @@ -225,16 +225,23 @@ where #[cfg(feature = "debug")] pub fn resume(&mut self) -> Result { - match self + let state = self .debugger_last_state() - .ok_or(ExecuteError::DebugStateNotInitialized)? - { + .ok_or(ExecuteError::DebugStateNotInitialized)?; + + let state = match state { ProgramState::Return(w) => Ok(ProgramState::Return(w)), ProgramState::RunProgram(_) => self.run_program(), ProgramState::VerifyPredicate(_) => unimplemented!(), + }?; + + if state.is_debug() { + self.debugger_set_last_state(state.clone()); } + + Ok(state) } pub fn run_program(&mut self) -> Result { diff --git a/src/lib.rs b/src/lib.rs index f98376df3d..e937308182 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -12,10 +12,15 @@ pub mod interpreter; pub mod prelude { pub use crate::data::{InterpreterStorage, MemoryStorage, Storage}; pub use crate::debug::Debugger; - pub use crate::interpreter::{Call, CallFrame, Contract, ExecuteError, Interpreter, LogEvent, MemoryRange}; + pub use crate::interpreter::{ + Call, CallFrame, Contract, ExecuteError, Interpreter, LogEvent, MemoryRange, ProgramState, + }; pub use fuel_asm::{Immediate06, Immediate12, Immediate18, Immediate24, Opcode, RegisterId, Word}; pub use fuel_tx::{ bytes::{Deserializable, SerializableVec, SizedBytes}, Address, Color, ContractAddress, Hash, Input, Output, Salt, Transaction, ValidationError, Witness, }; + + #[cfg(feature = "debug")] + pub use crate::debug::{Breakpoint, DebugEval}; } diff --git a/tests/interpreter/debug.rs b/tests/interpreter/debug.rs new file mode 100644 index 0000000000..1ea95ce125 --- /dev/null +++ b/tests/interpreter/debug.rs @@ -0,0 +1,61 @@ +use super::program_to_bytes; +use fuel_vm::consts::*; +use fuel_vm::prelude::*; + +#[test] +fn breakpoint_script() { + let storage = MemoryStorage::default(); + let mut vm = Interpreter::with_storage(storage); + + let gas_price = 10; + let gas_limit = 1_000_000; + let maturity = 100; + + let script = vec![ + Opcode::ADDI(0x10, REG_ZERO, 8), + Opcode::ADDI(0x11, REG_ZERO, 16), + Opcode::ADDI(0x12, REG_ZERO, 32), + Opcode::ADDI(0x13, REG_ZERO, 64), + Opcode::ADDI(0x14, REG_ZERO, 128), + Opcode::RET(0x10), + ]; + let script = program_to_bytes(script.as_slice()); + + let tx = Transaction::script(gas_price, gas_limit, maturity, script, vec![], vec![], vec![], vec![]); + + vm.init(tx).expect("Failed to init VM!"); + + let suite = vec![ + ( + Breakpoint::script(0), + vec![(0x10, 0), (0x11, 0), (0x12, 0), (0x13, 0), (0x14, 0)], + ), + ( + Breakpoint::script(2), + vec![(0x10, 8), (0x11, 16), (0x12, 0), (0x13, 0), (0x14, 0)], + ), + ( + Breakpoint::script(3), + vec![(0x10, 8), (0x11, 16), (0x12, 32), (0x13, 0), (0x14, 0)], + ), + ( + Breakpoint::script(5), + vec![(0x10, 8), (0x11, 16), (0x12, 32), (0x13, 64), (0x14, 128)], + ), + ]; + + suite.iter().for_each(|(b, _)| vm.set_breakpoint(*b)); + let state = vm.run().expect("Failed to execute script!"); + + suite.into_iter().fold(state, |state, (breakpoint, registers)| { + let debug = state.debug_ref().expect("Expected breakpoint"); + let b = debug.breakpoint().expect("State without expected breakpoint"); + + assert_eq!(&breakpoint, b); + registers.into_iter().for_each(|(r, w)| { + assert_eq!(w, vm.registers()[r]); + }); + + vm.resume().expect("Failed to resume") + }); +} diff --git a/tests/interpreter/mod.rs b/tests/interpreter/mod.rs index 836e76241c..2ade1ed20a 100644 --- a/tests/interpreter/mod.rs +++ b/tests/interpreter/mod.rs @@ -8,6 +8,9 @@ mod flow; mod memory; mod predicate; +#[cfg(feature = "debug")] +mod debug; + pub use super::common; pub fn program_to_bytes(program: &[Opcode]) -> Vec { From 1c375008dfbd5d739e0868e4c69afc6304a4d588 Mon Sep 17 00:00:00 2001 From: Victor Lopez Date: Mon, 21 Jun 2021 10:59:06 +0200 Subject: [PATCH 4/4] Move all contract addr logic to debugger Currently, when the contract address is not defined, the zeroed address is being used. All implementations of this logic should be internal to the debugger module. --- src/debug/debugger.rs | 4 +++- src/interpreter/debug.rs | 7 ++----- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/debug/debugger.rs b/src/debug/debugger.rs index 5af1a7a55f..30ee71cd44 100644 --- a/src/debug/debugger.rs +++ b/src/debug/debugger.rs @@ -110,7 +110,9 @@ impl Debugger { .map(|set| set.remove(&breakpoint.pc())); } - pub fn eval_state(&mut self, contract: ContractAddress, pc: Word) -> DebugEval { + pub fn eval_state(&mut self, contract: Option<&ContractAddress>, pc: Word) -> DebugEval { + // Default contract address maps to unset contract target + let contract = contract.copied().unwrap_or_default(); let last_state = self.last_state.take(); self.breakpoints diff --git a/src/interpreter/debug.rs b/src/interpreter/debug.rs index b9440ec907..f3bdc09e46 100644 --- a/src/interpreter/debug.rs +++ b/src/interpreter/debug.rs @@ -1,4 +1,4 @@ -use super::{Interpreter, ProgramState}; +use super::{CallFrame, Interpreter, ProgramState}; use crate::consts::*; use crate::debug::{Breakpoint, DebugEval}; @@ -14,12 +14,9 @@ impl Interpreter { pub fn eval_debugger_state(&mut self) -> DebugEval { let debugger = &mut self.debugger; - let contract = self.frames.last().map(|f| f.to()).copied(); + let contract = self.frames.last().map(CallFrame::to); let pc = self.registers[REG_PC].saturating_sub(self.registers[REG_IS]); - // Default contract address maps to unset contract target - let contract = contract.unwrap_or_default(); - debugger.eval_state(contract, pc) }