diff --git a/src/consts.rs b/src/consts.rs index d88face451..f95305ed1f 100644 --- a/src/consts.rs +++ b/src/consts.rs @@ -1,4 +1,8 @@ -use fuel_tx::ContractId; +use fuel_asm::Word; +use fuel_tx::consts::*; +use fuel_tx::{Bytes32, Color}; + +use std::mem; /* MEMORY TYPES */ @@ -80,4 +84,8 @@ pub const FUEL_MAX_PROGRAM_SIZE: u8 = 16; // used for serder pub const VM_REGISTER_WIDTH: u8 = 6; -pub const VM_CONTRACT_ID_BASE: ContractId = ContractId::new([0xab; ContractId::size_of()]); +pub const VM_TX_MEMORY: usize = Bytes32::size_of() // Tx ID + + mem::size_of::() // Tx size + + MAX_INPUTS as usize * ( + Color::size_of() + mem::size_of::() + ); // Color/Balance coin input pairs diff --git a/src/crypto.rs b/src/crypto.rs index bacc374e14..36e12ce00f 100644 --- a/src/crypto.rs +++ b/src/crypto.rs @@ -1,9 +1,11 @@ -use fuel_tx::{crypto as tx_crypto, Bytes32}; +use fuel_tx::crypto::Hasher; +use fuel_tx::Bytes32; use secp256k1::recovery::{RecoverableSignature, RecoveryId}; use secp256k1::Error as Secp256k1Error; use secp256k1::{Message, Secp256k1, SecretKey}; use std::convert::TryFrom; +use std::mem; /// Sign a given message and compress the `v` to the signature /// @@ -40,7 +42,153 @@ pub fn secp256k1_sign_compact_recover(signature: &[u8], message: &[u8]) -> Resul <[u8; 64]>::try_from(&pk[1..]).map_err(|_| Secp256k1Error::InvalidPublicKey) } -pub fn merkle_root(data: &[u8]) -> Bytes32 { - // TODO implement merkle root - tx_crypto::hash(data) +/// Calculate a binary merkle root +/// +/// The space complexity of this operation is O(n). This means it expects small +/// sets. For bigger sets (e.g. blockchain state), use a storage backed merkle +/// implementation +pub fn ephemeral_merkle_root(mut leaves: I) -> Bytes32 +where + L: AsRef<[u8]>, + I: Iterator + ExactSizeIterator, +{ + let mut hasher = Hasher::default(); + let mut width = leaves.len().next_power_of_two(); + let mut len = leaves.len() as f32; + + if width <= 2 { + return leaves.collect::().digest(); + } + + width /= 2; + len /= 2.0; + + let mut current = vec![Bytes32::default(); width]; + + // Drain the leaves + current.iter_mut().for_each(|l| { + hasher.reset(); + + leaves.next().map(|a| hasher.input(a)); + leaves.next().map(|b| hasher.input(b)); + + *l = hasher.digest(); + }); + + let mut next = current.clone(); + + // Cheap loop with no realloc + while width > 1 { + mem::swap(&mut current, &mut next); + + let mut c = current.iter().take(len.ceil() as usize); + + width /= 2; + len /= 2.0; + + next.iter_mut().take(width).for_each(|n| { + hasher.reset(); + + c.next().map(|a| hasher.input(a)); + c.next().map(|b| hasher.input(b)); + + *n = hasher.digest(); + }); + } + + next[0] +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::prelude::*; + + use fuel_tx::crypto::Hasher; + use rand::rngs::StdRng; + use rand::{Rng, RngCore, SeedableRng}; + use secp256k1::PublicKey; + + use std::convert::TryFrom; + + #[test] + fn ecrecover() { + let secp = Secp256k1::new(); + let mut rng = StdRng::seed_from_u64(2322u64); + let mut secret_seed = [0u8; 32]; + let mut message = [0u8; 95]; + + for _ in 0..10 { + rng.fill_bytes(&mut message); + rng.fill_bytes(&mut secret_seed); + + let secret = SecretKey::from_slice(&secret_seed).expect("Failed to generate random secret!"); + let public = PublicKey::from_secret_key(&secp, &secret).serialize_uncompressed(); + let public = <[u8; 64]>::try_from(&public[1..]).expect("Failed to parse public key!"); + + let e = Hasher::hash(&message); + + let sig = + secp256k1_sign_compact_recoverable(secret.as_ref(), e.as_ref()).expect("Failed to generate signature"); + let pk_p = secp256k1_sign_compact_recover(&sig, e.as_ref()).expect("Failed to recover PK from signature"); + + assert_eq!(public, pk_p); + } + } + + #[test] + fn ephemeral_merkle_root_works() { + let mut rng = StdRng::seed_from_u64(2322u64); + + // Test for 0 leaves + let empty: Vec
= vec![]; + + let root = ephemeral_merkle_root(empty.iter()); + let empty = Hasher::default().digest(); + + assert_eq!(empty, root); + + // Test for 5 leaves + let a: Address = rng.gen(); + let b: Address = rng.gen(); + let c: Address = rng.gen(); + let d: Address = rng.gen(); + let e: Address = rng.gen(); + + let initial = [a, b, c, d, e]; + + let a = [a, b].iter().collect::().digest(); + let b = [c, d].iter().collect::().digest(); + let c = [e].iter().collect::().digest(); + + let a = [a, b].iter().collect::().digest(); + let b = [c].iter().collect::().digest(); + + let root = [a, b].iter().collect::().digest(); + let root_p = ephemeral_merkle_root(initial.iter()); + + assert_eq!(root, root_p); + + // Test for n leaves + let mut inputs = vec![Address::default(); 64]; + + inputs.iter_mut().for_each(|i| *i = rng.gen()); + + (0..65).into_iter().for_each(|w| { + let initial: Vec<&Address> = inputs.iter().take(w).collect(); + let mut level: Vec = initial + .chunks(2) + .map(|c| c.iter().collect::().digest()) + .collect(); + + while level.len() > 1 { + level = level.chunks(2).map(|c| c.iter().collect::().digest()).collect(); + } + + let root = level.first().copied().unwrap_or(empty); + let root_p = ephemeral_merkle_root(initial.iter()); + + assert_eq!(root, root_p); + }); + } } diff --git a/src/data.rs b/src/data.rs index 6f920e7831..8d1b0ddc98 100644 --- a/src/data.rs +++ b/src/data.rs @@ -1,7 +1,7 @@ use crate::interpreter::Contract; use fuel_asm::Word; -use fuel_tx::{Bytes32, Color, ContractId}; +use fuel_tx::{Address, Bytes32, Color, ContractId, Salt}; use std::ops::DerefMut; @@ -12,7 +12,6 @@ pub use error::DataError; pub use memory::MemoryStorage; pub trait Key {} - pub trait Value {} pub trait Storage @@ -20,7 +19,7 @@ where K: Key, V: Value, { - fn insert(&mut self, key: K, value: V) -> Result, DataError>; + fn insert(&mut self, key: &K, value: &V) -> Result, DataError>; fn remove(&mut self, key: &K) -> Result, DataError>; // This initial implementation safeguard from the complex scenarios when a @@ -37,7 +36,7 @@ where S: Storage, I: DerefMut, { - fn insert(&mut self, key: K, value: V) -> Result, DataError> { + fn insert(&mut self, key: &K, value: &V) -> Result, DataError> { >::insert(self.deref_mut(), key, value) } @@ -54,11 +53,62 @@ where } } +pub trait MerkleStorage +where + P: Key, + K: Key, + V: Value, +{ + fn insert(&mut self, parent: &P, key: &K, value: &V) -> Result, DataError>; + fn remove(&mut self, parent: &P, key: &K) -> Result, DataError>; + fn get(&self, parent: &P, key: &K) -> Result, DataError>; + fn contains_key(&self, parent: &P, key: &K) -> Result; + fn root(&mut self, parent: &P) -> Result; +} + +impl MerkleStorage for I +where + P: Key, + K: Key, + V: Value, + X: MerkleStorage, + I: DerefMut, +{ + fn insert(&mut self, parent: &P, key: &K, value: &V) -> Result, DataError> { + >::insert(self.deref_mut(), parent, key, value) + } + + fn remove(&mut self, parent: &P, key: &K) -> Result, DataError> { + >::remove(self.deref_mut(), parent, key) + } + + fn get(&self, parent: &P, key: &K) -> Result, DataError> { + >::get(self.deref(), parent, key) + } + + fn contains_key(&self, parent: &P, key: &K) -> Result { + >::contains_key(self.deref(), parent, key) + } + + fn root(&mut self, parent: &P) -> Result { + >::root(self.deref_mut(), parent) + } +} + +// TODO use trait aliases after stable release +// https://github.com/rust-lang/rust/issues/41517 + /// When this trait is implemented, the underlying interpreter is guaranteed to /// have full functionality pub trait InterpreterStorage: - Storage + Storage<(ContractId, Color), Word> + Storage<(ContractId, Bytes32), Bytes32> + Storage + + Storage + + MerkleStorage + + MerkleStorage { + fn block_height(&self) -> Result; + fn block_hash(&self, block_height: u32) -> Result; + fn coinbase(&self) -> Result; } impl InterpreterStorage for I @@ -66,13 +116,33 @@ where S: InterpreterStorage, I: DerefMut, { + fn block_height(&self) -> Result { + ::block_height(self.deref()) + } + + fn block_hash(&self, block_height: u32) -> Result { + ::block_hash(self.deref(), block_height) + } + + fn coinbase(&self) -> Result { + ::coinbase(self.deref()) + } } // Provisory implementation that will cover ID definitions until client backend // is implemented +impl Key for Bytes32 {} +impl Key for Color {} impl Key for ContractId {} -impl Key for (ContractId, Color) {} -impl Key for (ContractId, Bytes32) {} -impl Value for Word {} -impl Value for Contract {} + impl Value for Bytes32 {} +impl Value for Contract {} +impl Value for Salt {} +impl Value for Word {} + +impl Value for (A, B) +where + A: Value, + B: Value, +{ +} diff --git a/src/data/memory.rs b/src/data/memory.rs index a30393eb69..e7b27718f7 100644 --- a/src/data/memory.rs +++ b/src/data/memory.rs @@ -1,21 +1,56 @@ -use super::{DataError, InterpreterStorage, Storage}; +use super::{DataError, InterpreterStorage, MerkleStorage, Storage}; +use crate::crypto; use crate::interpreter::Contract; use fuel_asm::Word; -use fuel_tx::{Bytes32, Color, ContractId}; +use fuel_tx::crypto::Hasher; +use fuel_tx::{Address, Bytes32, Color, ContractId, Salt}; +use itertools::Itertools; use std::collections::HashMap; -#[derive(Debug, Default, Clone)] +#[derive(Debug, Clone)] pub struct MemoryStorage { + block_height: u32, + coinbase: Address, contracts: HashMap, balances: HashMap<(ContractId, Color), Word>, - storage: HashMap<(ContractId, Bytes32), Bytes32>, + contract_state: HashMap<(ContractId, Bytes32), Bytes32>, + contract_code_root: HashMap, +} + +impl Default for MemoryStorage { + fn default() -> Self { + let block_height = 1; + let coinbase = Address::from(*Hasher::hash(b"coinbase")); + + Self::new(block_height, coinbase) + } +} + +impl MemoryStorage { + pub fn new(block_height: u32, coinbase: Address) -> Self { + Self { + block_height, + coinbase, + contracts: Default::default(), + balances: Default::default(), + contract_state: Default::default(), + contract_code_root: Default::default(), + } + } + + pub fn contract_state(&self, contract: &ContractId, key: &Bytes32) -> Bytes32 { + >::get(&self, contract, key) + .ok() + .flatten() + .unwrap_or_default() + } } impl Storage for MemoryStorage { - fn insert(&mut self, key: ContractId, value: Contract) -> Result, DataError> { - Ok(self.contracts.insert(key, value)) + fn insert(&mut self, key: &ContractId, value: &Contract) -> Result, DataError> { + Ok(self.contracts.insert(*key, value.clone())) } fn remove(&mut self, key: &ContractId) -> Result, DataError> { @@ -31,40 +66,93 @@ impl Storage for MemoryStorage { } } -impl Storage<(ContractId, Color), Word> for MemoryStorage { - fn insert(&mut self, key: (ContractId, Color), value: Word) -> Result, DataError> { - Ok(self.balances.insert(key, value)) +impl Storage for MemoryStorage { + fn insert(&mut self, key: &ContractId, value: &(Salt, Bytes32)) -> Result, DataError> { + Ok(self.contract_code_root.insert(*key, *value)) } - fn get(&self, key: &(ContractId, Color)) -> Result, DataError> { - Ok(self.balances.get(key).copied()) + fn remove(&mut self, key: &ContractId) -> Result, DataError> { + Ok(self.contract_code_root.remove(key)) } - fn remove(&mut self, key: &(ContractId, Color)) -> Result, DataError> { - Ok(self.balances.remove(key)) + fn get(&self, key: &ContractId) -> Result, DataError> { + Ok(self.contract_code_root.get(key).cloned()) } - fn contains_key(&self, key: &(ContractId, Color)) -> Result { - Ok(self.balances.contains_key(key)) + fn contains_key(&self, key: &ContractId) -> Result { + Ok(self.contract_code_root.contains_key(key)) } } -impl Storage<(ContractId, Bytes32), Bytes32> for MemoryStorage { - fn insert(&mut self, key: (ContractId, Bytes32), value: Bytes32) -> Result, DataError> { - Ok(self.storage.insert(key, value)) +impl MerkleStorage for MemoryStorage { + fn insert(&mut self, parent: &ContractId, key: &Color, value: &Word) -> Result, DataError> { + Ok(self.balances.insert((*parent, *key), *value)) + } + + fn get(&self, parent: &ContractId, key: &Color) -> Result, DataError> { + Ok(self.balances.get(&(*parent, *key)).copied()) } - fn get(&self, key: &(ContractId, Bytes32)) -> Result, DataError> { - Ok(self.storage.get(key).copied()) + fn remove(&mut self, parent: &ContractId, key: &Color) -> Result, DataError> { + Ok(self.balances.remove(&(*parent, *key))) } - fn remove(&mut self, key: &(ContractId, Bytes32)) -> Result, DataError> { - Ok(self.storage.remove(key)) + fn contains_key(&self, parent: &ContractId, key: &Color) -> Result { + Ok(self.balances.contains_key(&(*parent, *key))) } - fn contains_key(&self, key: &(ContractId, Bytes32)) -> Result { - Ok(self.storage.contains_key(key)) + fn root(&mut self, parent: &ContractId) -> Result { + let root = self + .balances + .iter() + .filter_map(|((contract, color), balance)| (contract == parent).then(|| (color, balance))) + .sorted_by_key(|t| t.0) + .map(|(_, &balance)| balance) + .map(Word::to_be_bytes); + + Ok(crypto::ephemeral_merkle_root(root)) } } -impl InterpreterStorage for MemoryStorage {} +impl MerkleStorage for MemoryStorage { + fn insert(&mut self, parent: &ContractId, key: &Bytes32, value: &Bytes32) -> Result, DataError> { + Ok(self.contract_state.insert((*parent, *key), *value)) + } + + fn get(&self, parent: &ContractId, key: &Bytes32) -> Result, DataError> { + Ok(self.contract_state.get(&(*parent, *key)).copied()) + } + + fn remove(&mut self, parent: &ContractId, key: &Bytes32) -> Result, DataError> { + Ok(self.contract_state.remove(&(*parent, *key))) + } + + fn contains_key(&self, parent: &ContractId, key: &Bytes32) -> Result { + Ok(self.contract_state.contains_key(&(*parent, *key))) + } + + fn root(&mut self, parent: &ContractId) -> Result { + let root = self + .contract_state + .iter() + .filter_map(|((contract, key), value)| (contract == parent).then(|| (key, value))) + .sorted_by_key(|t| t.0) + .map(|(_, value)| value); + + Ok(crypto::ephemeral_merkle_root(root)) + } +} + +impl InterpreterStorage for MemoryStorage { + fn block_height(&self) -> Result { + Ok(self.block_height) + } + + fn block_hash(&self, block_height: u32) -> Result { + Ok(Hasher::hash(&block_height.to_be_bytes())) + } + + fn coinbase(&self) -> Result { + Ok(self.coinbase) + } +} diff --git a/src/interpreter.rs b/src/interpreter.rs index d1e6515924..d17481f0ec 100644 --- a/src/interpreter.rs +++ b/src/interpreter.rs @@ -2,11 +2,7 @@ use crate::consts::*; use crate::debug::Debugger; use fuel_asm::{RegisterId, Word}; -use fuel_tx::consts::*; -use fuel_tx::{Bytes32, Color, ContractId, Transaction}; - -use std::convert::TryFrom; -use std::mem; +use fuel_tx::Transaction; mod alu; mod blockchain; @@ -17,6 +13,7 @@ mod executors; mod flow; mod frame; mod gas; +mod internal; mod log; mod memory; mod transaction; @@ -29,56 +26,21 @@ pub use error::ExecuteError; pub use executors::{ProgramState, StateTransition, StateTransitionRef}; pub use frame::{Call, CallFrame}; pub use gas::GasUnit; +pub use internal::Context; pub use log::LogEvent; pub use memory::MemoryRange; -const WORD_SIZE: usize = mem::size_of::(); - -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] -#[cfg_attr(feature = "serde-types", derive(serde::Serialize, serde::Deserialize))] -pub enum Context { - Predicate, - Script, - Call, - NotInitialized, -} - -impl Default for Context { - fn default() -> Self { - Self::NotInitialized - } -} - -impl Context { - pub const fn is_external(&self) -> bool { - match self { - Self::Predicate | Self::Script => true, - _ => false, - } - } -} - -impl From<&Transaction> for Context { - fn from(tx: &Transaction) -> Self { - if tx.is_script() { - Self::Script - } else { - Self::Predicate - } - } -} - #[derive(Debug, Clone)] pub struct Interpreter { registers: [Word; VM_REGISTER_COUNT], memory: Vec, frames: Vec, log: Vec, - // TODO review all opcodes that mutates the tx in the stack and keep this one sync tx: Transaction, storage: S, debugger: Debugger, context: Context, + block_height: u32, } impl Interpreter { @@ -92,55 +54,10 @@ impl Interpreter { storage, debugger: Debugger::default(), context: Context::default(), + block_height: 0, } } - pub(crate) fn push_stack(&mut self, data: &[u8]) -> Result<(), ExecuteError> { - let (ssp, overflow) = self.registers[REG_SSP].overflowing_add(data.len() as Word); - - if overflow || !self.is_external_context() && ssp > self.registers[REG_FP] { - Err(ExecuteError::StackOverflow) - } else { - self.memory[self.registers[REG_SSP] as usize..ssp as usize].copy_from_slice(data); - self.registers[REG_SSP] = ssp; - - Ok(()) - } - } - - pub const fn tx_mem_address() -> usize { - Bytes32::size_of() // Tx ID - + WORD_SIZE // Tx size - + MAX_INPUTS as usize * (Color::size_of() + WORD_SIZE) // Color/Balance - // coin input - // pairs - } - - pub(crate) const fn block_height(&self) -> u32 { - // TODO fetch block height - u32::MAX >> 1 - } - - pub(crate) fn set_flag(&mut self, a: Word) { - self.registers[REG_FLAG] = a; - } - - pub(crate) fn clear_err(&mut self) { - self.registers[REG_ERR] = 0; - } - - pub(crate) fn set_err(&mut self) { - self.registers[REG_ERR] = 1; - } - - pub(crate) fn inc_pc(&mut self) -> bool { - let (result, overflow) = self.registers[REG_PC].overflowing_add(4); - - self.registers[REG_PC] = result; - - !overflow - } - pub fn memory(&self) -> &[u8] { self.memory.as_slice() } @@ -149,22 +66,6 @@ impl Interpreter { &self.registers } - pub(crate) const fn context(&self) -> Context { - if self.registers[REG_FP] == 0 { - self.context - } else { - Context::Call - } - } - - pub(crate) const fn is_external_context(&self) -> bool { - self.context().is_external() - } - - pub(crate) const fn is_predicate(&self) -> bool { - matches!(self.context, Context::Predicate) - } - // TODO convert to private scope after using internally pub const fn is_unsafe_math(&self) -> bool { self.registers[REG_FLAG] & 0x01 == 0x01 @@ -175,68 +76,6 @@ impl Interpreter { self.registers[REG_FLAG] & 0x02 == 0x02 } - pub(crate) const fn is_valid_register_alu(ra: RegisterId) -> bool { - ra > REG_FLAG && ra < VM_REGISTER_COUNT - } - - pub(crate) const fn is_valid_register_couple_alu(ra: RegisterId, rb: RegisterId) -> bool { - ra > REG_FLAG && ra < VM_REGISTER_COUNT && rb < VM_REGISTER_COUNT - } - - pub(crate) const fn is_valid_register_triple_alu(ra: RegisterId, rb: RegisterId, rc: RegisterId) -> bool { - ra > REG_FLAG && ra < VM_REGISTER_COUNT && rb < VM_REGISTER_COUNT && rc < VM_REGISTER_COUNT - } - - pub(crate) const fn is_valid_register_quadruple_alu( - ra: RegisterId, - rb: RegisterId, - rc: RegisterId, - rd: RegisterId, - ) -> bool { - ra > REG_FLAG - && ra < VM_REGISTER_COUNT - && rb < VM_REGISTER_COUNT - && rc < VM_REGISTER_COUNT - && rd < VM_REGISTER_COUNT - } - - pub(crate) const fn is_valid_register_quadruple( - ra: RegisterId, - rb: RegisterId, - rc: RegisterId, - rd: RegisterId, - ) -> bool { - ra < VM_REGISTER_COUNT && rb < VM_REGISTER_COUNT && rc < VM_REGISTER_COUNT && rd < VM_REGISTER_COUNT - } - - pub(crate) const fn is_valid_register_triple(ra: RegisterId, rb: RegisterId, rc: RegisterId) -> bool { - ra < VM_REGISTER_COUNT && rb < VM_REGISTER_COUNT && rc < VM_REGISTER_COUNT - } - - pub(crate) const fn is_valid_register_couple(ra: RegisterId, rb: RegisterId) -> bool { - ra < VM_REGISTER_COUNT && rb < VM_REGISTER_COUNT - } - - pub(crate) const fn is_valid_register(ra: RegisterId) -> bool { - ra < VM_REGISTER_COUNT - } - - pub(crate) const fn transaction(&self) -> &Transaction { - &self.tx - } - - pub(crate) fn internal_contract(&self) -> Result { - if self.is_external_context() { - return Err(ExecuteError::ExpectedInternalContext); - } - - let c = self.registers[REG_FP] as usize; - let cx = c + ContractId::size_of(); - let contract = ContractId::try_from(&self.memory[c..cx]).expect("Memory bounds logically verified"); - - Ok(contract) - } - pub fn log(&self) -> &[LogEvent] { self.log.as_slice() } diff --git a/src/interpreter/blockchain.rs b/src/interpreter/blockchain.rs index e7696fc1e8..c0197cb8b5 100644 --- a/src/interpreter/blockchain.rs +++ b/src/interpreter/blockchain.rs @@ -1,73 +1,239 @@ -use super::{ExecuteError, Interpreter, MemoryRange}; +use super::{ExecuteError, Interpreter}; use crate::consts::*; -use crate::data::InterpreterStorage; +use crate::data::{InterpreterStorage, MerkleStorage, Storage}; -use fuel_asm::Word; -use fuel_tx::{ContractId, Input}; +use fuel_asm::{RegisterId, Word}; +use fuel_tx::{Address, Bytes32, Bytes8, Color, ContractId, Input, Salt}; -use std::convert::TryFrom; +use std::mem; + +const WORD_SIZE: usize = mem::size_of::(); impl Interpreter where S: InterpreterStorage, { - pub(crate) fn burn(&mut self, a: Word) -> Result { - self.internal_contract() - .map(|contract| (contract, (*contract).into())) - .and_then(|(contract, color)| self.balance_sub(contract, color, a)) - .map(|_| self.inc_pc()) + pub(crate) fn coinbase(&self) -> Result { + Ok(self.storage.coinbase()?) + } + + pub(crate) fn burn(&mut self, a: Word) -> Result<(), ExecuteError> { + let (c, cx) = self.internal_contract_bounds()?; + + // Safety: Memory bounds logically verified by the interpreter + let contract = unsafe { ContractId::as_ref_unchecked(&self.memory[c..cx]) }; + let color = unsafe { Color::as_ref_unchecked(&self.memory[c..cx]) }; + + let balance = self.balance(contract, color)?; + let balance = balance.checked_sub(a).ok_or(ExecuteError::NotEnoughBalance)?; + + >::insert(&mut self.storage, contract, color, &balance)?; + + self.inc_pc(); + + Ok(()) } - pub(crate) fn mint(&mut self, a: Word) -> Result { - self.internal_contract() - .map(|contract| (contract, (*contract).into())) - .and_then(|(contract, color)| self.balance_add(contract, color, a)) - .map(|_| self.inc_pc()) + pub(crate) fn mint(&mut self, a: Word) -> Result<(), ExecuteError> { + let (c, cx) = self.internal_contract_bounds()?; + + // Safety: Memory bounds logically verified by the interpreter + let contract = unsafe { ContractId::as_ref_unchecked(&self.memory[c..cx]) }; + let color = unsafe { Color::as_ref_unchecked(&self.memory[c..cx]) }; + + let balance = self.balance(contract, color)?; + let balance = balance.checked_add(a).ok_or(ExecuteError::NotEnoughBalance)?; + + >::insert(&mut self.storage, contract, color, &balance)?; + + self.inc_pc(); + + Ok(()) } - // TODO add CCP tests - pub(crate) fn code_copy(&mut self, a: Word, b: Word, c: Word, d: Word) -> bool { - let (ad, overflow) = a.overflowing_add(d); - let (bx, of) = b.overflowing_add(ContractId::size_of() as Word); - let overflow = overflow || of; - let (cd, of) = c.overflowing_add(d); - let overflow = overflow || of; - - let range = MemoryRange::new(a, d); - if overflow - || ad >= VM_MAX_RAM - || bx >= VM_MAX_RAM - || d > MEM_MAX_ACCESS_SIZE - || !self.has_ownership_range(&range) + pub(crate) fn code_copy(&mut self, a: Word, b: Word, c: Word, d: Word) -> Result<(), ExecuteError> { + if d > MEM_MAX_ACCESS_SIZE + || a > VM_MAX_RAM - d + || b > VM_MAX_RAM - ContractId::size_of() as Word + || c > VM_MAX_RAM - d { - return false; + return Err(ExecuteError::MemoryOverflow); } - let contract = - ContractId::try_from(&self.memory[b as usize..bx as usize]).expect("Memory bounds logically checked"); + let (a, b, c, d) = (a as usize, b as usize, c as usize, d as usize); + + let bx = b + ContractId::size_of(); + let cd = c + d; + + // Safety: Memory bounds are checked by the interpreter + let contract = unsafe { ContractId::as_ref_unchecked(&self.memory[b..bx]) }; if !self .tx .inputs() .iter() - .any(|input| matches!(input, Input::Contract { contract_id, .. } if contract_id == &contract)) + .any(|input| matches!(input, Input::Contract { contract_id, .. } if contract_id == contract)) { - return false; + return Err(ExecuteError::ContractNotInTxInputs); } - // TODO optmize - let contract = match self.contract(&contract) { - Ok(Some(c)) => c, - _ => return false, - }; + let contract = self.contract(contract)?.ok_or(ExecuteError::ContractNotFound)?; - let memory = &mut self.memory[a as usize..ad as usize]; - if contract.as_ref().len() < cd as usize { - memory.iter_mut().for_each(|m| *m = 0); + if contract.as_ref().len() < d { + self.try_zeroize(a, d)?; } else { - memory.copy_from_slice(&contract.as_ref()[..d as usize]); + self.try_mem_write(a, &contract.as_ref()[c..cd])?; + } + + self.inc_pc(); + + Ok(()) + } + + pub(crate) fn block_hash(&mut self, a: Word, b: Word) -> Result<(), ExecuteError> { + let hash = self.storage.block_hash(b as u32)?; + + self.try_mem_write(a as usize, hash.as_ref())?; + + self.inc_pc(); + + Ok(()) + } + + pub(crate) fn block_proposer(&mut self, a: Word) -> Result<(), ExecuteError> { + self.coinbase() + .and_then(|data| self.try_mem_write(a as usize, data.as_ref()))?; + + self.inc_pc(); + + Ok(()) + } + + pub(crate) fn code_root(&mut self, a: Word, b: Word) -> Result<(), ExecuteError> { + if a > VM_MAX_RAM - Bytes32::size_of() as Word || b > VM_MAX_RAM - ContractId::size_of() as Word { + return Err(ExecuteError::MemoryOverflow); + } + + let (a, b) = (a as usize, b as usize); + + // Safety: Memory bounds are checked by the interpreter + let contract_id = unsafe { ContractId::as_ref_unchecked(&self.memory[b..b + ContractId::size_of()]) }; + + let (_, root) = >::get(&self.storage, contract_id) + .transpose() + .ok_or(ExecuteError::ContractNotFound)??; + + self.try_mem_write(a, root.as_ref())?; + + self.inc_pc(); + + Ok(()) + } + + pub(crate) fn code_size(&mut self, ra: RegisterId, b: Word) -> Result<(), ExecuteError> { + if b > VM_MAX_RAM - ContractId::size_of() as Word { + return Err(ExecuteError::MemoryOverflow); } - true + let b = b as usize; + + // Safety: Memory bounds are checked by the interpreter + let contract_id = unsafe { ContractId::as_ref_unchecked(&self.memory[b..b + ContractId::size_of()]) }; + + self.contract(contract_id) + .transpose() + .ok_or(ExecuteError::ContractNotFound)? + .map(|contract| self.registers[ra] = contract.as_ref().len() as Word)?; + + self.inc_pc(); + + Ok(()) + } + + pub(crate) fn state_read_word(&mut self, ra: RegisterId, b: Word) -> Result<(), ExecuteError> { + if b > VM_MAX_RAM - Bytes32::size_of() as Word { + return Err(ExecuteError::MemoryOverflow); + } + + let b = b as usize; + + let contract = self.internal_contract()?; + + // Safety: Memory bounds are checked by the interpreter + let key = unsafe { Bytes32::as_ref_unchecked(&self.memory[b..b + Bytes32::size_of()]) }; + + self.registers[ra] = >::get(&self.storage, &contract, key)? + .map(|state| unsafe { Bytes8::from_slice_unchecked(state.as_ref()).into() }) + .map(Word::from_be_bytes) + .unwrap_or(0); + + self.inc_pc(); + + Ok(()) + } + + pub(crate) fn state_read_qword(&mut self, a: Word, b: Word) -> Result<(), ExecuteError> { + if a > VM_MAX_RAM - Bytes32::size_of() as Word || b > VM_MAX_RAM - Bytes32::size_of() as Word { + return Err(ExecuteError::MemoryOverflow); + } + + let (a, b) = (a as usize, b as usize); + + let contract = self.internal_contract()?; + + // Safety: Memory bounds are checked by the interpreter + let key = unsafe { Bytes32::as_ref_unchecked(&self.memory[b..b + Bytes32::size_of()]) }; + + let state = + >::get(&self.storage, &contract, key)?.unwrap_or_default(); + + self.try_mem_write(a, state.as_ref())?; + + self.inc_pc(); + + Ok(()) + } + + pub(crate) fn state_write_word(&mut self, a: Word, b: Word) -> Result<(), ExecuteError> { + if a > VM_MAX_RAM - Bytes32::size_of() as Word { + return Err(ExecuteError::MemoryOverflow); + } + + let a = a as usize; + let (c, cx) = self.internal_contract_bounds()?; + + // Safety: Memory bounds logically verified by the interpreter + let contract = unsafe { ContractId::as_ref_unchecked(&self.memory[c..cx]) }; + let key = unsafe { Bytes32::as_ref_unchecked(&self.memory[a..a + Bytes32::size_of()]) }; + + let mut value = Bytes32::default(); + + (&mut value[..WORD_SIZE]).copy_from_slice(&b.to_be_bytes()); + + >::insert(&mut self.storage, contract, key, &value)?; + + self.inc_pc(); + + Ok(()) + } + + pub(crate) fn state_write_qword(&mut self, a: Word, b: Word) -> Result<(), ExecuteError> { + if a > VM_MAX_RAM - Bytes32::size_of() as Word || b > VM_MAX_RAM - Bytes32::size_of() as Word { + return Err(ExecuteError::MemoryOverflow); + } + + let (a, b) = (a as usize, b as usize); + let (c, cx) = self.internal_contract_bounds()?; + + // Safety: Memory bounds logically verified by the interpreter + let contract = unsafe { ContractId::as_ref_unchecked(&self.memory[c..cx]) }; + let key = unsafe { Bytes32::as_ref_unchecked(&self.memory[a..a + Bytes32::size_of()]) }; + let value = unsafe { Bytes32::as_ref_unchecked(&self.memory[b..b + Bytes32::size_of()]) }; + + >::insert(&mut self.storage, &contract, key, value)?; + + self.inc_pc(); + + Ok(()) } } diff --git a/src/interpreter/contract.rs b/src/interpreter/contract.rs index 020ad446ec..bbf37def87 100644 --- a/src/interpreter/contract.rs +++ b/src/interpreter/contract.rs @@ -1,18 +1,43 @@ use super::{ExecuteError, Interpreter}; -use crate::consts::*; use crate::crypto; -use crate::data::InterpreterStorage; +use crate::data::{InterpreterStorage, MerkleStorage, Storage}; use fuel_asm::Word; -use fuel_tx::crypto as tx_crypto; -use fuel_tx::{Color, ContractId, Transaction, ValidationError}; +use fuel_tx::crypto::Hasher; +use fuel_tx::{Bytes32, Color, ContractId, Salt, Transaction, ValidationError}; +use std::cmp; use std::convert::TryFrom; #[derive(Debug, Default, Clone, PartialEq, Eq, Hash)] #[cfg_attr(feature = "serde-types", derive(serde::Serialize, serde::Deserialize))] pub struct Contract(Vec); +impl 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) + } + + pub fn id(&self, salt: &Salt, root: &Bytes32) -> ContractId { + let mut hasher = Hasher::default(); + + hasher.input(ContractId::SEED); + hasher.input(salt); + hasher.input(root); + + ContractId::from(*hasher.digest()) + } +} + impl From> for Contract { fn from(c: Vec) -> Self { Self(c) @@ -68,70 +93,23 @@ impl TryFrom<&Transaction> for Contract { } } -impl Contract { - pub fn address(&self, salt: &[u8]) -> ContractId { - let mut input = VM_CONTRACT_ID_BASE.to_vec(); - - input.extend_from_slice(salt); - input.extend_from_slice(crypto::merkle_root(self.0.as_slice()).as_ref()); - - (*tx_crypto::hash(input.as_slice())).into() - } -} - impl Interpreter where S: InterpreterStorage, { - pub(crate) fn contract(&self, address: &ContractId) -> Result, ExecuteError> { - Ok(self.storage.get(address)?) + pub(crate) fn contract(&self, contract: &ContractId) -> Result, ExecuteError> { + Ok(>::get(&self.storage, contract)?) } - pub(crate) fn check_contract_exists(&self, address: &ContractId) -> Result { - Ok(self.storage.contains_key(address)?) - } - - pub(crate) fn set_balance( - &mut self, - contract: ContractId, - color: Color, - balance: Word, - ) -> Result<(), ExecuteError> { - self.storage.insert((contract, color), balance)?; - - Ok(()) + pub(crate) fn check_contract_exists(&self, contract: &ContractId) -> Result { + Ok(>::contains_key( + &self.storage, + contract, + )?) } pub(crate) fn balance(&self, contract: &ContractId, color: &Color) -> Result { - Ok(self.storage.get(&(*contract, *color))?.unwrap_or(0)) - } - - pub(crate) fn balance_add( - &mut self, - contract: ContractId, - color: Color, - value: Word, - ) -> Result { - let balance = self.balance(&contract, &color)?; - let balance = balance.checked_add(value).ok_or(ExecuteError::NotEnoughBalance)?; - - self.set_balance(contract, color, balance)?; - - Ok(balance) - } - - pub(crate) fn balance_sub( - &mut self, - contract: ContractId, - color: Color, - value: Word, - ) -> Result { - let balance = self.balance(&contract, &color)?; - let balance = balance.checked_sub(value).ok_or(ExecuteError::NotEnoughBalance)?; - - self.set_balance(contract, color, balance)?; - - Ok(balance) + Ok(>::get(&self.storage, contract, color)?.unwrap_or(0)) } } @@ -172,7 +150,10 @@ mod tests { .collect::>() .into(); - let contract = Contract::from(program.as_ref()).address(salt.as_ref()); + let contract = Contract::from(program.as_ref()); + let contract_root = contract.root(); + let contract = contract.id(&salt, &contract_root); + let color = Color::from(*contract); let output = Output::contract_created(contract); @@ -213,7 +194,7 @@ mod tests { vec![], ); - let script_data_offset = Interpreter::<()>::tx_mem_address() + tx.script_data_offset().unwrap(); + let script_data_offset = VM_TX_MEMORY + tx.script_data_offset().unwrap(); script_ops[0] = Opcode::ADDI(0x10, REG_ZERO, script_data_offset as Immediate12); let script: Vec = script_ops.iter().copied().collect(); diff --git a/src/interpreter/crypto.rs b/src/interpreter/crypto.rs index c73c0773da..38e8d6ac15 100644 --- a/src/interpreter/crypto.rs +++ b/src/interpreter/crypto.rs @@ -3,7 +3,7 @@ use crate::consts::{MEM_MAX_ACCESS_SIZE, VM_MAX_RAM}; use crate::crypto; use fuel_asm::Word; -use fuel_tx::crypto as tx_crypto; +use fuel_tx::crypto::Hasher; impl Interpreter { pub(crate) fn ecrecover(&mut self, a: Word, b: Word, c: Word) -> bool { @@ -20,7 +20,7 @@ impl Interpreter { let e = &self.memory[c as usize..cx as usize]; let sig = &self.memory[b as usize..bx as usize]; - crypto::secp256k1_sign_compact_recover(&sig, &e) + crypto::secp256k1_sign_compact_recover(sig, e) .map(|pk| { self.memory[a as usize..ax as usize].copy_from_slice(&pk); self.clear_err(); @@ -73,9 +73,10 @@ impl Interpreter { { false } else { - let result = tx_crypto::hash(&self.memory[b as usize..bc as usize]); + let result = Hasher::hash(&self.memory[b as usize..bc as usize]); self.memory[a as usize..ax as usize].copy_from_slice(result.as_ref()); + true } } diff --git a/src/interpreter/error.rs b/src/interpreter/error.rs index e02b815f97..2ba47f15e6 100644 --- a/src/interpreter/error.rs +++ b/src/interpreter/error.rs @@ -13,6 +13,7 @@ pub enum ExecuteError { Data(DataError), TransactionCreateStaticContractNotFound, TransactionCreateIdNotInTx, + ArithmeticOverflow, StackOverflow, PredicateOverflow, ProgramOverflow, diff --git a/src/interpreter/executors/balances.rs b/src/interpreter/executors/balances.rs index 46aabb8201..3ade72e45f 100644 --- a/src/interpreter/executors/balances.rs +++ b/src/interpreter/executors/balances.rs @@ -23,8 +23,7 @@ where let balance_memory = self.memory[Bytes32::size_of()..Bytes32::size_of() + MAX_INPUTS as usize * LEN] .chunks_mut(LEN) - .filter(|chunk| &chunk[..Color::size_of()] == color.as_ref()) - .next() + .find(|chunk| &chunk[..Color::size_of()] == color.as_ref()) .map(|chunk| &mut chunk[Color::size_of()..]) .ok_or(ExecuteError::ExternalColorNotFound)?; diff --git a/src/interpreter/executors/initialization.rs b/src/interpreter/executors/initialization.rs index 249a4fd979..7c48d1e4c8 100644 --- a/src/interpreter/executors/initialization.rs +++ b/src/interpreter/executors/initialization.rs @@ -20,6 +20,7 @@ where tx.validate(self.block_height() as Word)?; tx.precompute_metadata(); + self.block_height = self.storage.block_height()?; self.context = Context::from(&tx); self.frames.clear(); diff --git a/src/interpreter/executors/instruction.rs b/src/interpreter/executors/instruction.rs index becd744d95..ac556f96e2 100644 --- a/src/interpreter/executors/instruction.rs +++ b/src/interpreter/executors/instruction.rs @@ -22,182 +22,152 @@ where } match op { - Opcode::ADD(ra, rb, rc) - if Self::is_valid_register_triple_alu(ra, rb, rc) && self.gas_charge(&op).is_ok() => - { + Opcode::ADD(ra, rb, rc) if Self::is_register_writable(ra) && self.gas_charge(&op).is_ok() => { 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.gas_charge(&op).is_ok() => { + Opcode::ADDI(ra, rb, imm) if Self::is_register_writable(ra) && self.gas_charge(&op).is_ok() => { 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.gas_charge(&op).is_ok() => - { + Opcode::AND(ra, rb, rc) if Self::is_register_writable(ra) && self.gas_charge(&op).is_ok() => { 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.gas_charge(&op).is_ok() => { + Opcode::ANDI(ra, rb, imm) if Self::is_register_writable(ra) && self.gas_charge(&op).is_ok() => { 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.gas_charge(&op).is_ok() => - { - self.alu_error( + Opcode::DIV(ra, rb, rc) if Self::is_register_writable(ra) && self.gas_charge(&op).is_ok() => 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.gas_charge(&op).is_ok() => { + Opcode::DIVI(ra, rb, imm) if Self::is_register_writable(ra) && self.gas_charge(&op).is_ok() => { 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.gas_charge(&op).is_ok() => - { + Opcode::EQ(ra, rb, rc) if Self::is_register_writable(ra) && self.gas_charge(&op).is_ok() => { 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.gas_charge(&op).is_ok() => - { + Opcode::EXP(ra, rb, rc) if Self::is_register_writable(ra) && self.gas_charge(&op).is_ok() => { 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.gas_charge(&op).is_ok() => { + Opcode::EXPI(ra, rb, imm) if Self::is_register_writable(ra) && self.gas_charge(&op).is_ok() => { 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.gas_charge(&op).is_ok() => - { + Opcode::GT(ra, rb, rc) if Self::is_register_writable(ra) && self.gas_charge(&op).is_ok() => { 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.gas_charge(&op).is_ok() => - { - 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::LT(ra, rb, rc) if Self::is_register_writable(ra) && self.gas_charge(&op).is_ok() => { + self.alu_set(ra, (self.registers[rb] < self.registers[rc]) as Word) } - Opcode::MROO(ra, rb, rc) - if Self::is_valid_register_triple_alu(ra, rb, rc) && self.gas_charge(&op).is_ok() => - { - self.alu_error( + Opcode::MLOG(ra, rb, rc) if Self::is_register_writable(ra) && self.gas_charge(&op).is_ok() => self + .alu_error( ra, - |b, c| (b as f64).powf((c as f64).recip()).trunc() as Word, + |b, c| (b as f64).log(c as f64).trunc() as Word, self.registers[rb], self.registers[rc], - self.registers[rc] == 0, - ) - } + self.registers[rb] == 0 || self.registers[rc] <= 1, + ), - Opcode::MOD(ra, rb, rc) - if Self::is_valid_register_triple_alu(ra, rb, rc) && self.gas_charge(&op).is_ok() => - { - self.alu_error( + Opcode::MOD(ra, rb, rc) if Self::is_register_writable(ra) && self.gas_charge(&op).is_ok() => 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.gas_charge(&op).is_ok() => { + Opcode::MODI(ra, rb, imm) if Self::is_register_writable(ra) && self.gas_charge(&op).is_ok() => { 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.gas_charge(&op).is_ok() => { + Opcode::MOVE(ra, rb) if Self::is_register_writable(ra) && self.gas_charge(&op).is_ok() => { self.alu_set(ra, self.registers[rb]) } - Opcode::MUL(ra, rb, rc) - if Self::is_valid_register_triple_alu(ra, rb, rc) && self.gas_charge(&op).is_ok() => - { + Opcode::MROO(ra, rb, rc) if Self::is_register_writable(ra) && self.gas_charge(&op).is_ok() => 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::MUL(ra, rb, rc) if Self::is_register_writable(ra) && self.gas_charge(&op).is_ok() => { 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.gas_charge(&op).is_ok() => { + Opcode::MULI(ra, rb, imm) if Self::is_register_writable(ra) && self.gas_charge(&op).is_ok() => { self.alu_overflow(ra, Word::overflowing_mul, self.registers[rb], imm as Word) } Opcode::NOOP if self.gas_charge(&op).is_ok() => self.alu_clear(), - Opcode::NOT(ra, rb) if Self::is_valid_register_couple_alu(ra, rb) && self.gas_charge(&op).is_ok() => { + Opcode::NOT(ra, rb) if Self::is_register_writable(ra) && self.gas_charge(&op).is_ok() => { self.alu_set(ra, !self.registers[rb]) } - Opcode::OR(ra, rb, rc) - if Self::is_valid_register_triple_alu(ra, rb, rc) && self.gas_charge(&op).is_ok() => - { + Opcode::OR(ra, rb, rc) if Self::is_register_writable(ra) && self.gas_charge(&op).is_ok() => { 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.gas_charge(&op).is_ok() => { + Opcode::ORI(ra, rb, imm) if Self::is_register_writable(ra) && self.gas_charge(&op).is_ok() => { 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.gas_charge(&op).is_ok() => - { + Opcode::SLL(ra, rb, rc) if Self::is_register_writable(ra) && self.gas_charge(&op).is_ok() => { 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.gas_charge(&op).is_ok() => { + Opcode::SLLI(ra, rb, imm) if Self::is_register_writable(ra) && self.gas_charge(&op).is_ok() => { 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.gas_charge(&op).is_ok() => - { + Opcode::SRL(ra, rb, rc) if Self::is_register_writable(ra) && self.gas_charge(&op).is_ok() => { 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.gas_charge(&op).is_ok() => { + Opcode::SRLI(ra, rb, imm) if Self::is_register_writable(ra) && self.gas_charge(&op).is_ok() => { 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.gas_charge(&op).is_ok() => - { + Opcode::SUB(ra, rb, rc) if Self::is_register_writable(ra) && self.gas_charge(&op).is_ok() => { 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.gas_charge(&op).is_ok() => { + Opcode::SUBI(ra, rb, imm) if Self::is_register_writable(ra) && self.gas_charge(&op).is_ok() => { 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.gas_charge(&op).is_ok() => - { + Opcode::XOR(ra, rb, rc) if Self::is_register_writable(ra) && self.gas_charge(&op).is_ok() => { 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.gas_charge(&op).is_ok() => { + Opcode::XORI(ra, rb, imm) if Self::is_register_writable(ra) && self.gas_charge(&op).is_ok() => { 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) + if Self::is_register_writable(ra) && self.gas_charge(&op).is_ok() && 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) + if Self::is_register_writable(ra) && self.gas_charge(&op).is_ok() && self.check_tx_maturity(ra, self.registers[rb]) && self.inc_pc() => {} @@ -205,21 +175,14 @@ where Opcode::JI(imm) if self.gas_charge(&op).is_ok() && self.jump(imm as Word) => {} Opcode::JNEI(ra, rb, imm) - if Self::is_valid_register_couple(ra, rb) - && self.gas_charge(&op).is_ok() + if self.gas_charge(&op).is_ok() && self.jump_not_equal_imm(self.registers[ra], self.registers[rb], imm as Word) => {} - Opcode::RET(ra) - if Self::is_valid_register(ra) && self.gas_charge(&op).is_ok() && self.ret(ra) && self.inc_pc() => - { + Opcode::RET(ra) if self.gas_charge(&op).is_ok() && self.ret(ra) && self.inc_pc() => { result = Ok(ExecuteState::Return(self.registers[ra])); } - Opcode::ALOC(ra) - if Self::is_valid_register(ra) - && self.gas_charge(&op).is_ok() - && self.malloc(self.registers[ra]) - && self.inc_pc() => {} + Opcode::ALOC(ra) if self.gas_charge(&op).is_ok() && self.malloc(self.registers[ra]) && self.inc_pc() => {} Opcode::CFEI(imm) if self.gas_charge(&op).is_ok() @@ -232,64 +195,58 @@ where && self.inc_pc() => {} Opcode::LB(ra, rb, imm) - if Self::is_valid_register_couple_alu(ra, rb) + if Self::is_register_writable(ra) && self.gas_charge(&op).is_ok() && 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) + if Self::is_register_writable(ra) && self.gas_charge(&op).is_ok() && 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.gas_charge(&op).is_ok() + if self.gas_charge(&op).is_ok() && self.memclear(self.registers[ra], self.registers[rb]) && self.inc_pc() => {} Opcode::MCLI(ra, imm) - if Self::is_valid_register(ra) - && self.gas_charge(&op).is_ok() - && self.memclear(self.registers[ra], imm as Word) - && self.inc_pc() => {} + if self.gas_charge(&op).is_ok() && 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.gas_charge(&op).is_ok() + if self.gas_charge(&op).is_ok() && 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) + if Self::is_register_writable(ra) && self.gas_charge(&op).is_ok() && 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.gas_charge(&op).is_ok() + if self.gas_charge(&op).is_ok() && 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.gas_charge(&op).is_ok() + if self.gas_charge(&op).is_ok() && 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.gas_charge(&op).is_ok() && self.inc_pc() => { + Opcode::BHEI(ra) if Self::is_register_writable(ra) && self.gas_charge(&op).is_ok() && 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.gas_charge(&op).is_ok() && self.burn(self.registers[ra])? => {} + Opcode::BHSH(ra, rb) + if self.gas_charge(&op).is_ok() + && self.block_hash(self.registers[ra], self.registers[rb]).map(|_| true)? => {} + + Opcode::BURN(ra) if self.gas_charge(&op).is_ok() && self.burn(self.registers[ra]).map(|_| true)? => {} Opcode::CALL(ra, rb, rc, rd) - if Self::is_valid_register_quadruple(ra, rb, rc, rd) - && self.gas_charge(&op).is_ok() + if self.gas_charge(&op).is_ok() && self .call( self.registers[ra], @@ -299,107 +256,115 @@ where ) .map(|_| true)? => {} + Opcode::CB(ra) + if self.gas_charge(&op).is_ok() && self.block_proposer(self.registers[ra]).map(|_| true)? => {} + Opcode::CCP(ra, rb, rc, rd) - if Self::is_valid_register_quadruple(ra, rb, rc, rd) + if self.gas_charge(&op).is_ok() + && self + .code_copy( + self.registers[ra], + self.registers[rb], + self.registers[rc], + self.registers[rd], + ) + .map(|_| true)? => {} + + Opcode::CROO(ra, rb) + if self.gas_charge(&op).is_ok() + && self.code_root(self.registers[ra], self.registers[rb]).map(|_| true)? => {} + + Opcode::CSIZ(ra, rb) + if Self::is_register_writable(ra) && self.gas_charge(&op).is_ok() - && self.code_copy( - self.registers[ra], - self.registers[rb], - self.registers[rc], - self.registers[rd], - ) - && self.inc_pc() => {} + && self.code_size(ra, self.registers[rb]).map(|_| true)? => {} - // TODO CODEROOT: Code Merkle root - // TODO CODESIZE: Code size - // TODO COINBASE: Block proposer address - // TODO LOADCODE: Load code from an external contract + // TODO LDC + // TODO Append to receipts Opcode::LOG(ra, rb, rc, rd) - if Self::is_valid_register_quadruple(ra, rb, rc, rd) + if self.gas_charge(&op).is_ok() && self.log_append(&[ra, rb, rc, rd]) && self.inc_pc() => {} + + // TODO LOGD + Opcode::MINT(ra) if self.gas_charge(&op).is_ok() && self.mint(self.registers[ra]).map(|_| true)? => {} + + // TODO RETD + // TODO RVRT + // TODO SLDC + Opcode::SRW(ra, rb) + if Self::is_register_writable(ra) && self.gas_charge(&op).is_ok() - && self.log_append(&[ra, rb, rc, rd]) - && self.inc_pc() => {} + && self.state_read_word(ra, self.registers[rb]).map(|_| true)? => {} + + Opcode::SRWQ(ra, rb) + if self.gas_charge(&op).is_ok() + && self + .state_read_qword(self.registers[ra], self.registers[rb]) + .map(|_| true)? => {} + + Opcode::SWW(ra, rb) + if self.gas_charge(&op).is_ok() + && self + .state_write_word(self.registers[ra], self.registers[rb]) + .map(|_| true)? => {} + + Opcode::SWWQ(ra, rb) + if self.gas_charge(&op).is_ok() + && self + .state_write_qword(self.registers[ra], self.registers[rb]) + .map(|_| true)? => {} - Opcode::MINT(ra) - if Self::is_valid_register(ra) && self.gas_charge(&op).is_ok() && self.mint(self.registers[ra])? => {} - - // 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.gas_charge(&op).is_ok() + if self.gas_charge(&op).is_ok() && 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.gas_charge(&op).is_ok() + if self.gas_charge(&op).is_ok() && 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.gas_charge(&op).is_ok() + if self.gas_charge(&op).is_ok() && self.sha256(self.registers[ra], self.registers[rb], self.registers[rc]) && self.inc_pc() => {} - Opcode::XIL(ra, rb) - if Self::is_valid_register_couple(ra, rb) && self.gas_charge(&op).is_ok() && self.inc_pc() => - { + Opcode::XIL(ra, rb) if self.gas_charge(&op).is_ok() && self.inc_pc() => { result = self .transaction_input_length(ra, self.registers[rb]) .map(|_| ExecuteState::Proceed) } - Opcode::XIS(ra, rb) - if Self::is_valid_register_couple(ra, rb) && self.gas_charge(&op).is_ok() && self.inc_pc() => - { + Opcode::XIS(ra, rb) if self.gas_charge(&op).is_ok() && self.inc_pc() => { result = self .transaction_input_start(ra, self.registers[rb]) .map(|_| ExecuteState::Proceed) } - Opcode::XOL(ra, rb) - if Self::is_valid_register_couple(ra, rb) && self.gas_charge(&op).is_ok() && self.inc_pc() => - { + Opcode::XOL(ra, rb) if self.gas_charge(&op).is_ok() && self.inc_pc() => { result = self .transaction_output_length(ra, self.registers[rb]) .map(|_| ExecuteState::Proceed) } - Opcode::XOS(ra, rb) - if Self::is_valid_register_couple(ra, rb) && self.gas_charge(&op).is_ok() && self.inc_pc() => - { + Opcode::XOS(ra, rb) if self.gas_charge(&op).is_ok() && self.inc_pc() => { result = self .transaction_output_start(ra, self.registers[rb]) .map(|_| ExecuteState::Proceed) } - Opcode::XWL(ra, rb) - if Self::is_valid_register_couple(ra, rb) && self.gas_charge(&op).is_ok() && self.inc_pc() => - { + Opcode::XWL(ra, rb) if self.gas_charge(&op).is_ok() && self.inc_pc() => { result = self .transaction_witness_length(ra, self.registers[rb]) .map(|_| ExecuteState::Proceed) } - Opcode::XWS(ra, rb) - if Self::is_valid_register_couple(ra, rb) && self.gas_charge(&op).is_ok() && self.inc_pc() => - { + Opcode::XWS(ra, rb) if self.gas_charge(&op).is_ok() && self.inc_pc() => { result = self .transaction_witness_start(ra, self.registers[rb]) .map(|_| ExecuteState::Proceed) } - Opcode::FLAG(ra) if Self::is_valid_register(ra) && self.gas_charge(&op).is_ok() && self.inc_pc() => { - self.set_flag(self.registers[ra]) - } + Opcode::FLAG(ra) if self.gas_charge(&op).is_ok() && self.inc_pc() => self.set_flag(self.registers[ra]), _ => result = Err(ExecuteError::OpcodeFailure(op)), } diff --git a/src/interpreter/executors/main.rs b/src/interpreter/executors/main.rs index a3db4bf412..549eb1c636 100644 --- a/src/interpreter/executors/main.rs +++ b/src/interpreter/executors/main.rs @@ -1,10 +1,10 @@ use super::{ExecuteState, ProgramState, StateTransition, StateTransitionRef}; use crate::consts::*; -use crate::data::InterpreterStorage; +use crate::data::{InterpreterStorage, Storage}; use crate::interpreter::{Contract, ExecuteError, Interpreter, LogEvent, MemoryRange}; use fuel_asm::{Opcode, Word}; -use fuel_tx::{Input, Output, Transaction}; +use fuel_tx::{Bytes32, ContractId, Input, Output, Salt, Transaction}; use std::convert::TryFrom; @@ -31,7 +31,9 @@ where } let contract = Contract::try_from(&self.tx)?; - let id = contract.address(salt.as_ref()); + let root = contract.root(); + let id = contract.id(salt, &root); + if !&self .tx .outputs() @@ -41,7 +43,8 @@ where Err(ExecuteError::TransactionCreateIdNotInTx)?; } - self.storage.insert(id, contract)?; + >::insert(&mut self.storage, &id, &contract)?; + >::insert(&mut self.storage, &id, &(*salt, root))?; // Verify predicates // https://github.com/FuelLabs/fuel-specs/blob/master/specs/protocol/tx_validity.md#predicate-verification @@ -58,7 +61,7 @@ where .map(|ofs| (ofs as Word, predicate.len() as Word)), _ => None, }) - .map(|(ofs, len)| (ofs + Self::tx_mem_address() as Word, len)) + .map(|(ofs, len)| (ofs + VM_TX_MEMORY as Word, len)) .map(|(ofs, len)| MemoryRange::new(ofs, len)) .collect(); @@ -76,7 +79,7 @@ where } Transaction::Script { .. } => { - let offset = (Self::tx_mem_address() + Transaction::script_offset()) as Word; + let offset = (VM_TX_MEMORY + Transaction::script_offset()) as Word; self.registers[REG_PC] = offset; self.registers[REG_IS] = offset; diff --git a/src/interpreter/executors/predicate.rs b/src/interpreter/executors/predicate.rs index 49b18e6bd4..406d2a875e 100644 --- a/src/interpreter/executors/predicate.rs +++ b/src/interpreter/executors/predicate.rs @@ -11,7 +11,7 @@ where { pub(crate) fn verify_predicate(&mut self, predicate: &MemoryRange) -> Result { // TODO initialize VM with tx prepared for sign - let (start, end) = predicate.boundaries(&self); + let (start, end) = predicate.boundaries(self); self.registers[REG_PC] = start; self.registers[REG_IS] = start; diff --git a/src/interpreter/executors/state.rs b/src/interpreter/executors/state.rs index 968ffcd5b3..4dd3051b04 100644 --- a/src/interpreter/executors/state.rs +++ b/src/interpreter/executors/state.rs @@ -131,7 +131,7 @@ impl<'a> StateTransitionRef<'a> { impl<'a> From<&'a StateTransition> for StateTransitionRef<'a> { fn from(t: &'a StateTransition) -> StateTransitionRef<'a> { Self { - state: t.state().clone(), + state: *t.state(), tx: t.tx(), log: t.log(), } diff --git a/src/interpreter/frame.rs b/src/interpreter/frame.rs index 1bef195df1..4c90482b0d 100644 --- a/src/interpreter/frame.rs +++ b/src/interpreter/frame.rs @@ -80,9 +80,10 @@ impl io::Write for Call { return Err(bytes::eof()); } - let (to, buf) = bytes::restore_array_unchecked(buf); - let (a, buf) = bytes::restore_word_unchecked(buf); - let (b, _) = bytes::restore_word_unchecked(buf); + // Safety: checked buffer lenght + let (to, buf) = unsafe { bytes::restore_array_unchecked(buf) }; + let (a, buf) = unsafe { bytes::restore_word_unchecked(buf) }; + let (b, _) = unsafe { bytes::restore_word_unchecked(buf) }; self.to = to.into(); self.a = a; @@ -199,18 +200,19 @@ impl io::Write for CallFrame { return Err(bytes::eof()); } - let (to, buf) = bytes::restore_array_unchecked(buf); - let (color, buf) = bytes::restore_array_unchecked(buf); + // Safety: checked buffer length + let (to, buf) = unsafe { bytes::restore_array_unchecked(buf) }; + let (color, buf) = unsafe { bytes::restore_array_unchecked(buf) }; let buf = self.registers.iter_mut().fold(buf, |buf, reg| { - let (r, buf) = bytes::restore_word_unchecked(buf); + let (r, buf) = unsafe { bytes::restore_word_unchecked(buf) }; *reg = r; buf }); - let (code_len, buf) = bytes::restore_usize_unchecked(buf); - let (a, buf) = bytes::restore_word_unchecked(buf); - let (b, buf) = bytes::restore_word_unchecked(buf); + let (code_len, buf) = unsafe { bytes::restore_usize_unchecked(buf) }; + let (a, buf) = unsafe { bytes::restore_word_unchecked(buf) }; + let (b, buf) = unsafe { bytes::restore_word_unchecked(buf) }; let (bytes, code, _) = bytes::restore_raw_bytes(buf, code_len)?; n += bytes; diff --git a/src/interpreter/internal.rs b/src/interpreter/internal.rs new file mode 100644 index 0000000000..e35ef81be4 --- /dev/null +++ b/src/interpreter/internal.rs @@ -0,0 +1,123 @@ +use super::{ExecuteError, Interpreter}; +use crate::consts::*; + +use fuel_asm::{RegisterId, Word}; +use fuel_tx::{ContractId, Transaction}; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[cfg_attr(feature = "serde-types", derive(serde::Serialize, serde::Deserialize))] +pub enum Context { + Predicate, + Script, + Call, + NotInitialized, +} + +impl Default for Context { + fn default() -> Self { + Self::NotInitialized + } +} + +impl Context { + pub const fn is_external(&self) -> bool { + 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 + } + } +} + +impl Interpreter { + pub(crate) fn push_stack(&mut self, data: &[u8]) -> Result<(), ExecuteError> { + let (ssp, overflow) = self.registers[REG_SSP].overflowing_add(data.len() as Word); + + if overflow || !self.is_external_context() && ssp > self.registers[REG_FP] { + Err(ExecuteError::StackOverflow) + } else { + self.memory[self.registers[REG_SSP] as usize..ssp as usize].copy_from_slice(data); + self.registers[REG_SSP] = ssp; + + Ok(()) + } + } + + pub(crate) const fn block_height(&self) -> u32 { + self.block_height + } + + pub(crate) fn set_flag(&mut self, a: Word) { + self.registers[REG_FLAG] = a; + } + + pub(crate) fn clear_err(&mut self) { + self.registers[REG_ERR] = 0; + } + + pub(crate) fn set_err(&mut self) { + self.registers[REG_ERR] = 1; + } + + pub(crate) fn inc_pc(&mut self) -> bool { + let (result, overflow) = self.registers[REG_PC].overflowing_add(4); + + self.registers[REG_PC] = result; + + !overflow + } + + pub(crate) const fn context(&self) -> Context { + if self.registers[REG_FP] == 0 { + self.context + } else { + Context::Call + } + } + + pub(crate) const fn is_external_context(&self) -> bool { + self.context().is_external() + } + + pub(crate) const fn is_internal_context(&self) -> bool { + !self.is_external_context() + } + + pub(crate) const fn is_predicate(&self) -> bool { + matches!(self.context, Context::Predicate) + } + + pub(crate) const fn is_register_writable(ra: RegisterId) -> bool { + ra > REG_FLAG + } + + pub(crate) const fn transaction(&self) -> &Transaction { + &self.tx + } + + pub(crate) fn internal_contract(&self) -> Result<&ContractId, ExecuteError> { + let (c, cx) = self.internal_contract_bounds()?; + + // Safety: Memory bounds logically verified by the interpreter + let contract = unsafe { ContractId::as_ref_unchecked(&self.memory[c..cx]) }; + + Ok(contract) + } + + pub(crate) fn internal_contract_bounds(&self) -> Result<(usize, usize), ExecuteError> { + self.is_internal_context() + .then(|| { + let c = self.registers[REG_FP] as usize; + let cx = c + ContractId::size_of(); + + (c, cx) + }) + .ok_or(ExecuteError::ExpectedInternalContext) + } +} diff --git a/src/interpreter/memory.rs b/src/interpreter/memory.rs index 7c7d2efe1d..d2c0c8d206 100644 --- a/src/interpreter/memory.rs +++ b/src/interpreter/memory.rs @@ -1,4 +1,4 @@ -use super::Interpreter; +use super::{ExecuteError, Interpreter}; use crate::consts::*; use fuel_asm::{RegisterId, Word}; @@ -11,6 +11,52 @@ mod range; pub use range::MemoryRange; impl Interpreter { + /// Copy `data` into `addr[..|data|[` + /// + /// Check for overflow and memory ownership + /// + /// # Panics + /// + /// Will panic if data overlaps with `addr[..|data|[` + pub(crate) fn try_mem_write(&mut self, addr: usize, data: &[u8]) -> Result<(), ExecuteError> { + addr.checked_add(data.len()) + .ok_or(ExecuteError::ArithmeticOverflow) + .and_then(|ax| { + (ax <= VM_MAX_RAM as usize) + .then(|| MemoryRange::new(addr as Word, 32)) + .ok_or(ExecuteError::MemoryOverflow) + }) + .and_then(|range| { + self.has_ownership_range(&range) + .then(|| { + let src = data.as_ptr(); + let dst = &mut self.memory[addr] as *mut u8; + + unsafe { + ptr::copy_nonoverlapping(src, dst, data.len()); + } + }) + .ok_or(ExecuteError::MemoryOwnership) + }) + } + + pub(crate) fn try_zeroize(&mut self, addr: usize, len: usize) -> Result<(), ExecuteError> { + addr.checked_add(len) + .ok_or(ExecuteError::ArithmeticOverflow) + .and_then(|ax| { + (ax <= VM_MAX_RAM as usize) + .then(|| MemoryRange::new(addr as Word, 32)) + .ok_or(ExecuteError::MemoryOverflow) + }) + .and_then(|range| { + self.has_ownership_range(&range) + .then(|| { + (&mut self.memory[addr..]).iter_mut().take(len).for_each(|m| *m = 0); + }) + .ok_or(ExecuteError::MemoryOwnership) + }) + } + /// Grant ownership of the range `[a..ab[` pub(crate) fn has_ownership_range(&self, range: &MemoryRange) -> bool { let (a, ab) = range.boundaries(self); @@ -94,7 +140,7 @@ impl Interpreter { let bc = bc as usize; let bcw = bcw as usize; - if overflow || bcw >= VM_MAX_RAM as RegisterId { + if overflow || bcw > VM_MAX_RAM as RegisterId { false } else { // Safe conversion of sized slice diff --git a/src/interpreter/transaction.rs b/src/interpreter/transaction.rs index efc8a51625..51afa4025e 100644 --- a/src/interpreter/transaction.rs +++ b/src/interpreter/transaction.rs @@ -1,4 +1,5 @@ use super::{ExecuteError, Interpreter, RegisterId}; +use crate::consts::*; use fuel_asm::Word; use fuel_tx::bytes::SizedBytes; @@ -17,7 +18,7 @@ impl Interpreter { pub(crate) fn transaction_input_start(&mut self, ra: RegisterId, b: Word) -> Result<(), ExecuteError> { self.registers[ra] = - (Self::tx_mem_address() + self.tx.input_offset(b as usize).ok_or(ExecuteError::InputNotFound)?) as Word; + (VM_TX_MEMORY + self.tx.input_offset(b as usize).ok_or(ExecuteError::InputNotFound)?) as Word; Ok(()) } @@ -35,7 +36,7 @@ impl Interpreter { pub(crate) fn transaction_output_start(&mut self, ra: RegisterId, b: Word) -> Result<(), ExecuteError> { self.registers[ra] = - (Self::tx_mem_address() + self.tx.output_offset(b as usize).ok_or(ExecuteError::OutputNotFound)?) as Word; + (VM_TX_MEMORY + self.tx.output_offset(b as usize).ok_or(ExecuteError::OutputNotFound)?) as Word; Ok(()) } @@ -52,7 +53,7 @@ impl Interpreter { } pub(crate) fn transaction_witness_start(&mut self, ra: RegisterId, b: Word) -> Result<(), ExecuteError> { - self.registers[ra] = (Self::tx_mem_address() + self.registers[ra] = (VM_TX_MEMORY + self .tx .witness_offset(b as usize) diff --git a/src/lib.rs b/src/lib.rs index 0d0f019c98..d7b419499b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -10,7 +10,7 @@ pub mod debug; pub mod interpreter; pub mod prelude { - pub use crate::data::{InterpreterStorage, MemoryStorage, Storage}; + pub use crate::data::{InterpreterStorage, MemoryStorage, MerkleStorage, Storage}; pub use crate::debug::Debugger; pub use crate::interpreter::{ Call, CallFrame, Context, Contract, ExecuteError, Interpreter, LogEvent, MemoryRange, ProgramState, diff --git a/tests/crypto.rs b/tests/crypto.rs deleted file mode 100644 index 402c63c502..0000000000 --- a/tests/crypto.rs +++ /dev/null @@ -1,33 +0,0 @@ -use fuel_tx::crypto as tx_crypto; -use fuel_vm::crypto; -use rand::rngs::StdRng; -use rand::{RngCore, SeedableRng}; -use secp256k1::{PublicKey, Secp256k1, SecretKey}; - -use std::convert::TryFrom; - -#[test] -fn ecrecover() { - let secp = Secp256k1::new(); - let mut rng = StdRng::seed_from_u64(2322u64); - let mut secret_seed = [0u8; 32]; - let mut message = [0u8; 95]; - - for _ in 0..10 { - rng.fill_bytes(&mut message); - rng.fill_bytes(&mut secret_seed); - - let secret = SecretKey::from_slice(&secret_seed).expect("Failed to generate random secret!"); - let public = PublicKey::from_secret_key(&secp, &secret).serialize_uncompressed(); - let public = <[u8; 64]>::try_from(&public[1..]).expect("Failed to parse public key!"); - - let e = tx_crypto::hash(&message); - - let sig = crypto::secp256k1_sign_compact_recoverable(secret.as_ref(), e.as_ref()) - .expect("Failed to generate signature"); - let pk_p = - crypto::secp256k1_sign_compact_recover(&sig, e.as_ref()).expect("Failed to recover PK from signature"); - - assert_eq!(public, pk_p); - } -} diff --git a/tests/interpreter/blockchain.rs b/tests/interpreter/blockchain.rs new file mode 100644 index 0000000000..1b69655821 --- /dev/null +++ b/tests/interpreter/blockchain.rs @@ -0,0 +1,269 @@ +use fuel_tx::bytes; +use fuel_tx::crypto::Hasher; +use fuel_vm::consts::*; +use fuel_vm::prelude::*; +use rand::rngs::StdRng; +use rand::{Rng, SeedableRng}; + +use std::mem; + +const WORD_SIZE: usize = mem::size_of::(); + +#[test] +fn state_read_write() { + let rng = &mut StdRng::seed_from_u64(2322u64); + + let mut storage = MemoryStorage::default(); + + let gas_price = 0; + let gas_limit = 1_000_000; + let maturity = 0; + + let salt: Salt = rng.gen(); + + // Create a program with two main routines + // + // 0 - Fetch (key, value) from call[b] and state[key] += value + // + // 1 - Fetch (key, value) from call[b], unpack b into 4x16, xor the limbs of + // state[key] with unpacked b + + #[rustfmt::skip] + let function_selector: Vec = vec![ + Opcode::ADDI(0x30, REG_ZERO, 0), + Opcode::ADDI(0x31, REG_ONE, 0), + ]; + + #[rustfmt::skip] + let call_arguments_parser: Vec = vec![ + Opcode::ADDI(0x10, REG_FP, CallFrame::a_offset() as Immediate12), + Opcode::LW(0x10, 0x10, 0), + Opcode::ADDI(0x11, REG_FP, CallFrame::b_offset() as Immediate12), + Opcode::LW(0x11, 0x11, 0), + ]; + + #[rustfmt::skip] + let routine_add_word_to_state: Vec = vec![ + Opcode::JNEI(0x10, 0x30, 13), // (0, b) Add word to state + Opcode::LW(0x20, 0x11, 4), // r[0x20] := m[b+32, 8] + Opcode::SRW(0x21, 0x11), // r[0x21] := s[m[b, 32], 8] + Opcode::ADD(0x20, 0x20, 0x21), // r[0x20] += r[0x21] + Opcode::SWW(0x11, 0x20), // s[m[b,32]] := r[0x20] + Opcode::LOG(0x20, 0x21, 0x00, 0x00), + Opcode::RET(REG_ONE), + ]; + + #[rustfmt::skip] + let routine_unpack_and_xor_limbs_into_state: Vec = vec![ + Opcode::JNEI(0x10, 0x31, 45), // (1, b) Unpack arg into 4x16 and xor into state + Opcode::ADDI(0x20, REG_ZERO, 32), // r[0x20] := 32 + Opcode::ALOC(0x20), // aloc 0x20 + Opcode::ADDI(0x20, REG_HP, 1), // r[0x20] := $hp+1 + Opcode::SRWQ(0x20, 0x11), // m[0x20,32] := s[m[b, 32], 32] + Opcode::LW(0x21, 0x11, 4), // r[0x21] := m[b+32, 8] + Opcode::LOG(0x21, 0x00, 0x00, 0x00), + Opcode::SRLI(0x22, 0x21, 48), // r[0x22] := r[0x21] >> 48 + Opcode::SRLI(0x23, 0x21, 32), // r[0x23] := r[0x21] >> 32 + Opcode::ANDI(0x23, 0x23, 0xff), // r[0x23] &= 0xffff + Opcode::SRLI(0x24, 0x21, 16), // r[0x24] := r[0x21] >> 16 + Opcode::ANDI(0x24, 0x24, 0xff), // r[0x24] &= 0xffff + Opcode::ANDI(0x25, 0x21, 0xff), // r[0x25] := r[0x21] & 0xffff + Opcode::LOG(0x22, 0x23, 0x24, 0x25), + Opcode::LW(0x26, 0x20, 0), // r[0x26] := m[$fp, 8] + Opcode::XOR(0x26, 0x26, 0x22), // r[0x26] ^= r[0x22] + Opcode::LOG(0x26, 0x00, 0x00, 0x00), + Opcode::SW(0x20, 0x26, 0), // m[0x20,8] := r[0x26] + Opcode::LW(0x26, 0x20, 1), // r[0x26] := m[$fp+8, 8] + Opcode::XOR(0x26, 0x26, 0x22), // r[0x26] ^= r[0x22] + Opcode::LOG(0x26, 0x00, 0x00, 0x00), + Opcode::SW(0x20, 0x26, 1), // m[0x20+8,8] := r[0x26] + Opcode::LW(0x26, 0x20, 2), // r[0x26] := m[$fp+16, 8] + Opcode::XOR(0x26, 0x26, 0x22), // r[0x26] ^= r[0x22] + Opcode::LOG(0x26, 0x00, 0x00, 0x00), + Opcode::SW(0x20, 0x26, 2), // m[0x20+16,8] := r[0x26] + Opcode::LW(0x26, 0x20, 3), // r[0x26] := m[$fp+24, 8] + Opcode::XOR(0x26, 0x26, 0x22), // r[0x26] ^= r[0x22] + Opcode::LOG(0x26, 0x00, 0x00, 0x00), + Opcode::SW(0x20, 0x26, 3), // m[0x20+24,8] := r[0x26] + Opcode::SWWQ(0x11, 0x20), // s[m[b,32],32]:= m[0x20, 32] + Opcode::RET(REG_ONE), + ]; + + #[rustfmt::skip] + let invalid_call: Vec = vec![ + Opcode::RET(REG_ZERO), + ]; + + let program: Witness = function_selector + .into_iter() + .chain(call_arguments_parser.into_iter()) + .chain(routine_add_word_to_state.into_iter()) + .chain(routine_unpack_and_xor_limbs_into_state.into_iter()) + .chain(invalid_call.into_iter()) + .collect::>() + .into(); + + let contract = Contract::from(program.as_ref()); + let contract_root = contract.root(); + let contract = contract.id(&salt, &contract_root); + + let output = Output::contract_created(contract); + + let bytecode_witness = 0; + let tx = Transaction::create( + gas_price, + gas_limit, + maturity, + bytecode_witness, + salt, + vec![], + vec![], + vec![output], + vec![program], + ); + + // Deploy the contract into the blockchain + Interpreter::transition(&mut storage, tx).expect("Failed to transact"); + + let input = Input::contract(rng.gen(), rng.gen(), rng.gen(), contract); + let output = Output::contract(0, rng.gen(), rng.gen()); + + // The script needs to locate the data offset at runtime. Hence, we need to know + // upfront the serialized size of the script so we can set the registers + // accordingly. + // + // This variable is created to assert we have correct script size in the + // instructions. + let script_len = 16; + + // Based on the defined script length, we set the appropriate data offset + let script_data_offset = VM_TX_MEMORY + Transaction::script_offset() + script_len; + let script_data_offset = script_data_offset as Immediate12; + + let script = vec![ + Opcode::ADDI(0x10, REG_ZERO, script_data_offset), + Opcode::ADDI(0x11, REG_ZERO, gas_limit as Immediate12), + Opcode::CALL(0x10, REG_ZERO, 0x10, 0x11), + Opcode::RET(REG_ONE), + ] + .iter() + .copied() + .collect::>(); + + // Assert the offsets are set correctnly + let offset = VM_TX_MEMORY + Transaction::script_offset() + bytes::padded_len(script.as_slice()); + assert_eq!(script_data_offset, offset as Immediate12); + + let mut script_data = vec![]; + + // Routine to be called: Add word to state + let routine: Word = 0; + + // Offset of the script data relative to the call data + let call_data_offset = script_data_offset as usize + ContractId::size_of() + 2 * WORD_SIZE; + let call_data_offset = call_data_offset as Word; + + // Key and value to be added + let key = Hasher::hash(b"some key"); + let val: Word = 150; + + // Script data containing the call arguments (contract, a, b) and (key, value) + script_data.extend(contract.as_ref()); + script_data.extend(&routine.to_be_bytes()); + script_data.extend(&call_data_offset.to_be_bytes()); + script_data.extend(key.as_ref()); + script_data.extend(&val.to_be_bytes()); + + let tx = Transaction::script( + gas_price, + gas_limit, + maturity, + script.clone(), + script_data, + vec![input.clone()], + vec![output], + vec![], + ); + + // Assert the initial state of `key` is empty + let state = storage.contract_state(&contract, &key); + assert_eq!(Bytes32::default(), state); + + let transition = Interpreter::transition(&mut storage, tx).expect("Failed to transact"); + let state = storage.contract_state(&contract, &key); + + // Assert the state of `key` is mutated to `val` + assert_eq!(&val.to_be_bytes()[..], &state.as_ref()[..WORD_SIZE]); + + // Expect the correct log order + let log = transition.log(); + assert!(matches!(log[0], LogEvent::Register { register, value, .. } if register == 0x20 && value == val)); + assert!(matches!(log[1], LogEvent::Register { register, value, .. } if register == 0x21 && value == 0)); + + let mut script_data = vec![]; + + // Routine to be called: Unpack and XOR into state + let routine: Word = 1; + + // Create limbs reference values + let a = 0x25; + let b = 0xc1; + let c = 0xd3; + let d = 0xaa; + + let val: Word = (a << 48) | (b << 32) | (c << 16) | d; + + // Script data containing the call arguments (contract, a, b) and (key, value) + script_data.extend(contract.as_ref()); + script_data.extend(&routine.to_be_bytes()); + script_data.extend(&call_data_offset.to_be_bytes()); + script_data.extend(key.as_ref()); + script_data.extend(&val.to_be_bytes()); + + let tx = Transaction::script( + gas_price, + gas_limit, + maturity, + script, + script_data, + vec![input], + vec![output], + vec![], + ); + + // Mutate the state + let transition = Interpreter::transition(&mut storage, tx).expect("Failed to transact"); + + let log = transition.log(); + + // Expect the arguments to be received correctly by the VM + assert!(matches!(log[0], LogEvent::Register { register, value, .. } if register == 0x21 && value == val)); + assert!(matches!(log[1], LogEvent::Register { register, value, .. } if register == 0x22 && value == a)); + assert!(matches!(log[2], LogEvent::Register { register, value, .. } if register == 0x23 && value == b)); + assert!(matches!(log[3], LogEvent::Register { register, value, .. } if register == 0x24 && value == c)); + assert!(matches!(log[4], LogEvent::Register { register, value, .. } if register == 0x25 && value == d)); + + let m = a ^ 0x96; + let n = a ^ 0x00; + let o = a ^ 0x00; + let p = a ^ 0x00; + + // Expect the value to be unpacked correctly into 4x16 limbs + XOR state + assert!(matches!(log[5], LogEvent::Register { register, value, .. } if register == 0x26 && value == m)); + assert!(matches!(log[6], LogEvent::Register { register, value, .. } if register == 0x26 && value == n)); + assert!(matches!(log[7], LogEvent::Register { register, value, .. } if register == 0x26 && value == o)); + assert!(matches!(log[8], LogEvent::Register { register, value, .. } if register == 0x26 && value == p)); + + let mut bytes = [0u8; 32]; + + // Reconstruct the final state out of the limbs + (&mut bytes[..8]).copy_from_slice(&m.to_be_bytes()); + (&mut bytes[8..16]).copy_from_slice(&n.to_be_bytes()); + (&mut bytes[16..24]).copy_from_slice(&o.to_be_bytes()); + (&mut bytes[24..]).copy_from_slice(&p.to_be_bytes()); + + // Assert the state is correct + let bytes = Bytes32::from(bytes); + let state = storage.contract_state(&contract, &key); + assert_eq!(bytes, state); +} diff --git a/tests/interpreter/crypto.rs b/tests/interpreter/crypto.rs index 8da0fad908..08b1df6b6b 100644 --- a/tests/interpreter/crypto.rs +++ b/tests/interpreter/crypto.rs @@ -1,4 +1,4 @@ -use fuel_tx::crypto as tx_crypto; +use fuel_tx::crypto::Hasher; use fuel_vm::consts::*; use fuel_vm::crypto; use fuel_vm::prelude::*; @@ -22,7 +22,7 @@ fn ecrecover() { let public = <[u8; 64]>::try_from(&public[1..]).expect("Failed to parse public key!"); let message = b"The gift of words is the gift of deception and illusion."; - let e = tx_crypto::hash(&message[..]); + let e = Hasher::hash(&message[..]); let sig = crypto::secp256k1_sign_compact_recoverable(secret.as_ref(), e.as_ref()).expect("Failed to generate signature"); @@ -103,7 +103,7 @@ fn sha256() { let message = b"I say let the world go to hell, but I should always have my tea."; let length = message.len() as Immediate12; - let hash = tx_crypto::hash(message); + let hash = Hasher::hash(message); let alloc = length // message + 32 // reference hash diff --git a/tests/interpreter/flow.rs b/tests/interpreter/flow.rs index 5ed25bd803..e2eac3db48 100644 --- a/tests/interpreter/flow.rs +++ b/tests/interpreter/flow.rs @@ -32,7 +32,10 @@ fn code_copy() { .collect(); let program = Witness::from(program.as_slice()); - let contract = Contract::from(program.as_ref()).address(salt.as_ref()); + let contract = Contract::from(program.as_ref()); + let contract_root = contract.root(); + let contract = contract.id(&salt, &contract_root); + let contract_size = program.as_ref().len(); let output = Output::contract_created(contract); @@ -81,7 +84,7 @@ fn code_copy() { vec![], ); - let script_data_mem = Interpreter::<()>::tx_mem_address() + tx.script_data_offset().unwrap(); + let script_data_mem = VM_TX_MEMORY + tx.script_data_offset().unwrap(); script_ops[3] = Opcode::ADDI(0x20, REG_ZERO, script_data_mem as Immediate12); let script_mem: Vec = script_ops.iter().copied().collect(); @@ -119,7 +122,10 @@ fn call() { .collect(); let program = Witness::from(program.as_slice()); - let contract = Contract::from(program.as_ref()).address(salt.as_ref()); + let contract = Contract::from(program.as_ref()); + let contract_root = contract.root(); + let contract = contract.id(&salt, &contract_root); + let output = Output::contract_created(contract); // Deploy the contract @@ -161,7 +167,7 @@ fn call() { vec![], ); - let script_data_mem = Interpreter::<()>::tx_mem_address() + tx.script_data_offset().unwrap(); + let script_data_mem = VM_TX_MEMORY + tx.script_data_offset().unwrap(); script_ops[0] = Opcode::ADDI(0x10, REG_ZERO, script_data_mem as Immediate12); let script_mem: Vec = script_ops.iter().copied().collect(); diff --git a/tests/interpreter/mod.rs b/tests/interpreter/mod.rs index 70041804c6..47101fd00f 100644 --- a/tests/interpreter/mod.rs +++ b/tests/interpreter/mod.rs @@ -1,4 +1,5 @@ mod alu; +mod blockchain; mod crypto; mod flow; mod predicate; diff --git a/tests/interpreter/predicate.rs b/tests/interpreter/predicate.rs index 8c8a0c4a60..f239e05c02 100644 --- a/tests/interpreter/predicate.rs +++ b/tests/interpreter/predicate.rs @@ -40,8 +40,10 @@ fn predicate() { let maturity = 0; let salt: Salt = rng.gen(); let witness = vec![]; - let contract = Contract::from(witness.as_slice()); - let contract = contract.address(salt.as_ref()); + + let contract = Contract::from(witness.as_ref()); + let contract_root = contract.root(); + let contract = contract.id(&salt, &contract_root); let input = Input::coin( rng.gen(), @@ -114,8 +116,10 @@ fn predicate_false() { let maturity = 0; let salt: Salt = rng.gen(); let witness = vec![]; - let contract = Contract::from(witness.as_slice()); - let contract = contract.address(salt.as_ref()); + + let contract = Contract::from(witness.as_ref()); + let contract_root = contract.root(); + let contract = contract.id(&salt, &contract_root); let input = Input::coin( rng.gen(),