From 1e205b6f00c91bbfc0a1778089b3da30e63277e7 Mon Sep 17 00:00:00 2001 From: Hannes Karppila Date: Wed, 8 May 2024 03:25:46 +0300 Subject: [PATCH 01/25] Add memory reset function --- fuel-vm/src/interpreter/memory.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/fuel-vm/src/interpreter/memory.rs b/fuel-vm/src/interpreter/memory.rs index 2e0a427805..f17d05f977 100644 --- a/fuel-vm/src/interpreter/memory.rs +++ b/fuel-vm/src/interpreter/memory.rs @@ -102,6 +102,13 @@ impl Memory { } } + /// Resets memory to initial state, keeping the original allocations. + pub fn reset(&mut self) { + self.stack.truncate(0); + self.heap.truncate(0); + self.hp = MEM_SIZE; + } + /// Offset of the heap section fn heap_offset(&self) -> usize { MEM_SIZE.saturating_sub(self.heap.len()) From ebc60c6d7a2ca5e340824018cc6497e0c9a8c461 Mon Sep 17 00:00:00 2001 From: Hannes Karppila Date: Wed, 8 May 2024 04:19:34 +0300 Subject: [PATCH 02/25] Add a test case --- fuel-vm/src/interpreter/memory/impl_tests.rs | 27 +++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/fuel-vm/src/interpreter/memory/impl_tests.rs b/fuel-vm/src/interpreter/memory/impl_tests.rs index 340535be9c..eab5b366d4 100644 --- a/fuel-vm/src/interpreter/memory/impl_tests.rs +++ b/fuel-vm/src/interpreter/memory/impl_tests.rs @@ -1,6 +1,9 @@ #![allow(clippy::cast_possible_truncation)] -use crate::constraints::reg_key::*; +use crate::{ + constraints::reg_key::*, + consts::MEM_SIZE, +}; use super::{ Memory, @@ -101,3 +104,25 @@ fn reading_from_internally_allocated_heap_below_hp_fails() { .write_noownerchecks(VM_MAX_RAM - 16, 16) .expect_err("Cannot read across stack/heap boundary"); } + +#[test] +fn memory_reset() { + let mut memory = Memory::new(); + + memory.grow_stack(10).unwrap(); + memory.read(0, 1).expect("Stack should be nonempty"); + memory.reset(); + memory.read(0, 1).expect_err("Stack should be empty"); + + memory + .grow_heap(Reg::::new(&0), VM_MAX_RAM - 10) + .unwrap(); + memory + .read(VM_MAX_RAM - 1, 1) + .expect("Heap should be nonempty"); + memory.reset(); + memory + .read(VM_MAX_RAM - 1, 1) + .expect_err("Heap should be empty"); + assert_eq!(memory.hp, MEM_SIZE); +} From 39fa0535bd65e39ee6f21946dda8e61033c3bfd7 Mon Sep 17 00:00:00 2001 From: Hannes Karppila Date: Wed, 8 May 2024 04:20:49 +0300 Subject: [PATCH 03/25] Add changelog entry --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0dca5404cf..e04bb0d925 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,9 +10,10 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ### Changed - [#725](https://github.com/FuelLabs/fuel-vm/pull/725): Adds more clippy lints to catch possible integer overflow and casting bugs on compile time. - ### Added +- [#732](https://github.com/FuelLabs/fuel-vm/pull/732): Adds `reset` method to VM memory. + #### Breaking - [#725](https://github.com/FuelLabs/fuel-vm/pull/725): `UtxoId::from_str` now rejects inputs with multiple `0x` prefixes. Many `::from_str` implementations also reject extra data in the end of the input, instead of silently ignoring it. `UtxoId::from_str` allows a single `:` between the fields. Unused `GasUnit` struct removed. From ab936e687551320e3885748d458b3ed268cadf8b Mon Sep 17 00:00:00 2001 From: Hannes Karppila Date: Fri, 10 May 2024 07:31:49 +0300 Subject: [PATCH 04/25] Add VmPool and use it for predicates and execution --- CHANGELOG.md | 2 + fuel-vm/examples/external.rs | 7 +- fuel-vm/examples/single_step.rs | 3 +- fuel-vm/src/checked_transaction.rs | 151 ++++++++++++------ fuel-vm/src/checked_transaction/builder.rs | 7 +- fuel-vm/src/interpreter/debug.rs | 7 +- .../instruction/tests/reserved_registers.rs | 3 +- fuel-vm/src/interpreter/executors/main.rs | 20 ++- .../src/interpreter/executors/main/tests.rs | 8 +- fuel-vm/src/interpreter/internal/tests.rs | 5 +- fuel-vm/src/interpreter/memory/tests.rs | 11 +- fuel-vm/src/lib.rs | 1 + fuel-vm/src/pool.rs | 64 ++++++++ fuel-vm/src/predicate.rs | 2 + fuel-vm/src/tests/blockchain.rs | 23 +-- fuel-vm/src/tests/code_coverage.rs | 3 +- fuel-vm/src/tests/crypto.rs | 23 +-- fuel-vm/src/tests/external.rs | 11 +- fuel-vm/src/tests/limits.rs | 11 +- fuel-vm/src/tests/memory.rs | 3 +- fuel-vm/src/tests/metadata.rs | 17 +- fuel-vm/src/tests/predicate.rs | 48 +++--- fuel-vm/src/tests/profile_gas.rs | 7 +- fuel-vm/src/tests/test_helpers.rs | 4 +- fuel-vm/src/tests/validation.rs | 13 +- fuel-vm/src/util.rs | 10 +- 26 files changed, 328 insertions(+), 136 deletions(-) create mode 100644 fuel-vm/src/pool.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index e04bb0d925..2a4316d15d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ### Changed - [#725](https://github.com/FuelLabs/fuel-vm/pull/725): Adds more clippy lints to catch possible integer overflow and casting bugs on compile time. + ### Added - [#732](https://github.com/FuelLabs/fuel-vm/pull/732): Adds `reset` method to VM memory. @@ -18,6 +19,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). - [#725](https://github.com/FuelLabs/fuel-vm/pull/725): `UtxoId::from_str` now rejects inputs with multiple `0x` prefixes. Many `::from_str` implementations also reject extra data in the end of the input, instead of silently ignoring it. `UtxoId::from_str` allows a single `:` between the fields. Unused `GasUnit` struct removed. - [#726](https://github.com/FuelLabs/fuel-vm/pull/726): Removed code related to Binary Merkle Sum Trees (BMSTs). The BMST is deprecated and not used in production environments. +- [#732](https://github.com/FuelLabs/fuel-vm/pull/732): Adds `VmPool` to allow reusing relatively expensive-to-allocate VM memories… Functions and traits which require VM initalization such as `estimate_predicates` now take the `VmPool` as an argument. The old behavior cah be restored by just passing `Default::default()` as the argument. ## [Version 0.49.0] diff --git a/fuel-vm/examples/external.rs b/fuel-vm/examples/external.rs index fd31f09573..05b1620d5c 100644 --- a/fuel-vm/examples/external.rs +++ b/fuel-vm/examples/external.rs @@ -34,6 +34,7 @@ use fuel_tx::{ use fuel_vm::{ error::SimpleResult, interpreter::EcalHandler, + pool::VmPool, prelude::{ Interpreter, IntoChecked, @@ -104,7 +105,7 @@ fn example_file_read() { .maturity(Default::default()) .add_random_fee_input() .finalize() - .into_checked(Default::default(), &consensus_params) + .into_checked(Default::default(), &consensus_params, VmPool::default()) .expect("failed to generate a checked tx"); client.transact(tx); let receipts = client.receipts().expect("Expected receipts"); @@ -163,7 +164,7 @@ fn example_counter() { .maturity(Default::default()) .add_random_fee_input() .finalize() - .into_checked(Default::default(), &consensus_params) + .into_checked(Default::default(), &consensus_params, VmPool::default()) .expect("failed to generate a checked tx"); client.transact(tx); let receipts = client.receipts().expect("Expected receipts"); @@ -226,7 +227,7 @@ fn example_shared_counter() { .maturity(Default::default()) .add_random_fee_input() .finalize() - .into_checked(Default::default(), &consensus_params) + .into_checked(Default::default(), &consensus_params, VmPool::default()) .expect("failed to generate a checked tx"); client.transact(tx); let receipts = client.receipts().expect("Expected receipts"); diff --git a/fuel-vm/examples/single_step.rs b/fuel-vm/examples/single_step.rs index a0c639771f..d8f92177a4 100644 --- a/fuel-vm/examples/single_step.rs +++ b/fuel-vm/examples/single_step.rs @@ -15,6 +15,7 @@ use fuel_vm::{ Interpreter, NotSupportedEcal, }, + pool::VmPool, prelude::*, }; @@ -46,7 +47,7 @@ fn main() { .maturity(Default::default()) .add_random_fee_input() .finalize() - .into_checked(Default::default(), &consensus_params) + .into_checked(Default::default(), &consensus_params, VmPool::default()) .expect("failed to generate a checked tx") .into_ready( 0, diff --git a/fuel-vm/src/checked_transaction.rs b/fuel-vm/src/checked_transaction.rs index d253330aa6..a5bfb78892 100644 --- a/fuel-vm/src/checked_transaction.rs +++ b/fuel-vm/src/checked_transaction.rs @@ -43,9 +43,13 @@ pub use types::*; use crate::{ error::PredicateVerificationFailed, + pool::VmPool, prelude::*, }; +#[cfg(feature = "test-helpers")] +use crate::pool::test_pool; + bitflags::bitflags! { /// Possible types of transaction checks. #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] @@ -228,7 +232,11 @@ where { fn default() -> Self { Tx::default() - .into_checked(Default::default(), &ConsensusParameters::standard()) + .into_checked( + Default::default(), + &ConsensusParameters::standard(), + test_pool(), + ) .expect("default tx should produce a valid fully checked transaction") } } @@ -292,6 +300,7 @@ pub trait IntoChecked: FormatValidityChecks + Sized { self, block_height: BlockHeight, consensus_params: &ConsensusParameters, + pool: VmPool, ) -> Result, CheckError> where Checked: CheckPredicates, @@ -299,7 +308,7 @@ pub trait IntoChecked: FormatValidityChecks + Sized { let check_predicate_params = consensus_params.into(); self.into_checked_basic(block_height, consensus_params)? .check_signatures(&consensus_params.chain_id())? - .check_predicates(&check_predicate_params) + .check_predicates(&check_predicate_params, pool) } /// Returns transaction that passed only `Checks::Basic`. @@ -369,12 +378,17 @@ impl From<&ConsensusParameters> for CheckPredicateParams { #[async_trait::async_trait] pub trait CheckPredicates: Sized { /// Performs predicates verification of the transaction. - fn check_predicates(self, params: &CheckPredicateParams) -> Result; + fn check_predicates( + self, + params: &CheckPredicateParams, + pool: VmPool, + ) -> Result; /// Performs predicates verification of the transaction in parallel. async fn check_predicates_async( self, params: &CheckPredicateParams, + pool: VmPool, ) -> Result; } @@ -385,12 +399,14 @@ pub trait EstimatePredicates: Sized { fn estimate_predicates( &mut self, params: &CheckPredicateParams, + pool: VmPool, ) -> Result<(), CheckError>; /// Estimates predicates of the transaction in parallel. async fn estimate_predicates_async( &mut self, params: &CheckPredicateParams, + pool: VmPool, ) -> Result<(), CheckError>; } @@ -422,9 +438,10 @@ where fn check_predicates( mut self, params: &CheckPredicateParams, + pool: VmPool, ) -> Result { if !self.checks_bitmask.contains(Checks::Predicates) { - Interpreter::::check_predicates(&self, params)?; + Interpreter::::check_predicates(&self, params, pool)?; self.checks_bitmask.insert(Checks::Predicates); } Ok(self) @@ -433,13 +450,14 @@ where async fn check_predicates_async( mut self, params: &CheckPredicateParams, + pool: VmPool, ) -> Result where E: ParallelExecutor, { if !self.checks_bitmask.contains(Checks::Predicates) { Interpreter::::check_predicates_async::( - &self, params, + &self, params, pool, ) .await?; @@ -457,20 +475,24 @@ impl EstimatePredicates for T fn estimate_predicates( &mut self, params: &CheckPredicateParams, + pool: VmPool, ) -> Result<(), CheckError> { - Interpreter::::estimate_predicates(self, params)?; + Interpreter::::estimate_predicates(self, params, pool)?; Ok(()) } async fn estimate_predicates_async( &mut self, params: &CheckPredicateParams, + pool: VmPool, ) -> Result<(), CheckError> where E: ParallelExecutor, { - Interpreter::::estimate_predicates_async::(self, params) - .await?; + Interpreter::::estimate_predicates_async::( + self, params, pool, + ) + .await?; Ok(()) } @@ -481,26 +503,28 @@ impl EstimatePredicates for Transaction { fn estimate_predicates( &mut self, params: &CheckPredicateParams, + pool: VmPool, ) -> Result<(), CheckError> { match self { - Self::Script(tx) => tx.estimate_predicates(params), - Self::Create(tx) => tx.estimate_predicates(params), + Self::Script(tx) => tx.estimate_predicates(params, pool), + Self::Create(tx) => tx.estimate_predicates(params, pool), Self::Mint(_) => Ok(()), - Self::Upgrade(tx) => tx.estimate_predicates(params), - Self::Upload(tx) => tx.estimate_predicates(params), + Self::Upgrade(tx) => tx.estimate_predicates(params, pool), + Self::Upload(tx) => tx.estimate_predicates(params, pool), } } async fn estimate_predicates_async( &mut self, params: &CheckPredicateParams, + pool: VmPool, ) -> Result<(), CheckError> { match self { - Self::Script(tx) => tx.estimate_predicates_async::(params).await, - Self::Create(tx) => tx.estimate_predicates_async::(params).await, + Self::Script(tx) => tx.estimate_predicates_async::(params, pool).await, + Self::Create(tx) => tx.estimate_predicates_async::(params, pool).await, Self::Mint(_) => Ok(()), - Self::Upgrade(tx) => tx.estimate_predicates_async::(params).await, - Self::Upload(tx) => tx.estimate_predicates_async::(params).await, + Self::Upgrade(tx) => tx.estimate_predicates_async::(params, pool).await, + Self::Upload(tx) => tx.estimate_predicates_async::(params, pool).await, } } } @@ -510,6 +534,7 @@ impl CheckPredicates for Checked { fn check_predicates( mut self, _params: &CheckPredicateParams, + _pool: VmPool, ) -> Result { self.checks_bitmask.insert(Checks::Predicates); Ok(self) @@ -518,6 +543,7 @@ impl CheckPredicates for Checked { async fn check_predicates_async( mut self, _params: &CheckPredicateParams, + _pool: VmPool, ) -> Result { self.checks_bitmask.insert(Checks::Predicates); Ok(self) @@ -526,23 +552,27 @@ impl CheckPredicates for Checked { #[async_trait::async_trait] impl CheckPredicates for Checked { - fn check_predicates(self, params: &CheckPredicateParams) -> Result { + fn check_predicates( + self, + params: &CheckPredicateParams, + pool: VmPool, + ) -> Result { let checked_transaction: CheckedTransaction = self.into(); let checked_transaction: CheckedTransaction = match checked_transaction { CheckedTransaction::Script(tx) => { - CheckPredicates::check_predicates(tx, params)?.into() + CheckPredicates::check_predicates(tx, params, pool)?.into() } CheckedTransaction::Create(tx) => { - CheckPredicates::check_predicates(tx, params)?.into() + CheckPredicates::check_predicates(tx, params, pool)?.into() } CheckedTransaction::Mint(tx) => { - CheckPredicates::check_predicates(tx, params)?.into() + CheckPredicates::check_predicates(tx, params, pool)?.into() } CheckedTransaction::Upgrade(tx) => { - CheckPredicates::check_predicates(tx, params)?.into() + CheckPredicates::check_predicates(tx, params, pool)?.into() } CheckedTransaction::Upload(tx) => { - CheckPredicates::check_predicates(tx, params)?.into() + CheckPredicates::check_predicates(tx, params, pool)?.into() } }; Ok(checked_transaction.into()) @@ -551,6 +581,7 @@ impl CheckPredicates for Checked { async fn check_predicates_async( mut self, params: &CheckPredicateParams, + pool: VmPool, ) -> Result where E: ParallelExecutor, @@ -559,27 +590,27 @@ impl CheckPredicates for Checked { let checked_transaction: CheckedTransaction = match checked_transaction { CheckedTransaction::Script(tx) => { - CheckPredicates::check_predicates_async::(tx, params) + CheckPredicates::check_predicates_async::(tx, params, pool) .await? .into() } CheckedTransaction::Create(tx) => { - CheckPredicates::check_predicates_async::(tx, params) + CheckPredicates::check_predicates_async::(tx, params, pool) .await? .into() } CheckedTransaction::Mint(tx) => { - CheckPredicates::check_predicates_async::(tx, params) + CheckPredicates::check_predicates_async::(tx, params, pool) .await? .into() } CheckedTransaction::Upgrade(tx) => { - CheckPredicates::check_predicates_async::(tx, params) + CheckPredicates::check_predicates_async::(tx, params, pool) .await? .into() } CheckedTransaction::Upload(tx) => { - CheckPredicates::check_predicates_async::(tx, params) + CheckPredicates::check_predicates_async::(tx, params, pool) .await? .into() } @@ -859,7 +890,11 @@ mod tests { let checked = tx .clone() - .into_checked(Default::default(), &ConsensusParameters::standard()) + .into_checked( + Default::default(), + &ConsensusParameters::standard(), + test_pool(), + ) .expect("Expected valid transaction"); // verify transaction getter works @@ -881,7 +916,11 @@ mod tests { let tx = signed_message_coin_tx(rng, gas_limit, input_amount, zero_fee_limit); let checked = tx - .into_checked(Default::default(), &ConsensusParameters::standard()) + .into_checked( + Default::default(), + &ConsensusParameters::standard(), + test_pool(), + ) .expect("Expected valid transaction"); // verify available balance was decreased by max fee @@ -901,7 +940,11 @@ mod tests { let tx = signed_message_coin_tx(rng, gas_limit, input_amount, zero_fee_limit); let checked = tx - .into_checked(Default::default(), &ConsensusParameters::standard()) + .into_checked( + Default::default(), + &ConsensusParameters::standard(), + test_pool(), + ) .expect("Expected valid transaction"); // verify available balance was decreased by max fee @@ -929,7 +972,11 @@ mod tests { .finalize(); let err = tx - .into_checked(Default::default(), &ConsensusParameters::standard()) + .into_checked( + Default::default(), + &ConsensusParameters::standard(), + test_pool(), + ) .expect_err("Expected valid transaction"); // then @@ -971,7 +1018,11 @@ mod tests { .finalize(); let err = tx - .into_checked(Default::default(), &ConsensusParameters::standard()) + .into_checked( + Default::default(), + &ConsensusParameters::standard(), + test_pool(), + ) .expect_err("Expected valid transaction"); // then @@ -1524,7 +1575,11 @@ mod tests { .finalize(); let err = tx - .into_checked(Default::default(), &ConsensusParameters::standard()) + .into_checked( + Default::default(), + &ConsensusParameters::standard(), + test_pool(), + ) .expect_err("Expected invalid transaction"); // assert that tx without base input assets fails @@ -1549,7 +1604,7 @@ mod tests { let transaction = base_asset_tx(rng, arb_input_amount, gas_limit, zero_max_fee); transaction .clone() - .into_checked(Default::default(), ¶ms) + .into_checked(Default::default(), ¶ms, test_pool()) .unwrap(); let fees = TransactionFee::checked_from_tx( &GasCosts::default(), @@ -1565,7 +1620,7 @@ mod tests { base_asset_tx(rng, new_input_amount, gas_limit, real_max_fee); new_transaction .clone() - .into_checked(Default::default(), ¶ms) + .into_checked(Default::default(), ¶ms, test_pool()) .unwrap() .into_ready(gas_price, &GasCosts::default(), params.fee_params()) .expect("`new_transaction` should be fully valid"); @@ -1574,7 +1629,7 @@ mod tests { // invalidating the transaction by increasing witness size new_transaction.witnesses_mut().push(rng.gen()); let bigger_checked = new_transaction - .into_checked(Default::default(), ¶ms) + .into_checked(Default::default(), ¶ms, test_pool()) .unwrap(); // when @@ -1613,7 +1668,7 @@ mod tests { // when let err = transaction - .into_checked(Default::default(), &consensus_params) + .into_checked(Default::default(), &consensus_params, test_pool()) .expect_err("overflow expected"); // then @@ -1642,7 +1697,7 @@ mod tests { let fee_params = consensus_params.fee_params(); let err = transaction - .into_checked(Default::default(), &consensus_params) + .into_checked(Default::default(), &consensus_params, test_pool()) .unwrap() .into_ready(max_gas_price, &gas_costs, fee_params) .expect_err("overflow expected"); @@ -1669,7 +1724,7 @@ mod tests { // when let err = transaction - .into_checked(Default::default(), &consensus_params) + .into_checked(Default::default(), &consensus_params, test_pool()) .unwrap() .into_ready(gas_price, &gas_costs, fee_params) .expect_err("overflow expected"); @@ -1695,7 +1750,7 @@ mod tests { base_asset_tx_with_tip(rng, input_amount, gas_limit, max_fee_limit, None); tx_without_tip .clone() - .into_checked(block_height, ¶ms) + .into_checked(block_height, ¶ms, test_pool()) .unwrap() .into_ready(gas_price, &gas_costs, params.fee_params()) .expect("Should be valid"); @@ -1710,7 +1765,7 @@ mod tests { Some(tip), ); tx_without_enough_to_pay_for_tip - .into_checked(block_height, ¶ms) + .into_checked(block_height, ¶ms, test_pool()) .unwrap() .into_ready(gas_price, &gas_costs, params.fee_params()) .expect_err("Expected invalid transaction"); @@ -1728,7 +1783,7 @@ mod tests { // then tx.clone() - .into_checked(block_height, ¶ms) + .into_checked(block_height, ¶ms, test_pool()) .unwrap() .into_ready(gas_price, &GasCosts::default(), params.fee_params()) .expect("Should be valid"); @@ -1747,7 +1802,7 @@ mod tests { let consensus_params = params(1); let err = transaction - .into_checked(Default::default(), &consensus_params) + .into_checked(Default::default(), &consensus_params, test_pool()) .unwrap() .into_ready( gas_price, @@ -1789,7 +1844,11 @@ mod tests { .finalize(); let checked = tx - .into_checked(Default::default(), &ConsensusParameters::standard()) + .into_checked( + Default::default(), + &ConsensusParameters::standard(), + test_pool(), + ) .expect_err("Expected valid transaction"); assert_eq!( @@ -1828,6 +1887,7 @@ mod tests { .into_checked( block_height, &ConsensusParameters::standard_with_id(chain_id), + test_pool(), ) .unwrap() // Sets Checks::Signatures @@ -1857,10 +1917,11 @@ mod tests { .into_checked( block_height, &consensus_params, + test_pool(), ) .unwrap() // Sets Checks::Predicates - .check_predicates(&check_predicate_params) + .check_predicates(&check_predicate_params, test_pool()) .unwrap(); assert!(checked .checks() diff --git a/fuel-vm/src/checked_transaction/builder.rs b/fuel-vm/src/checked_transaction/builder.rs index cfd9c9cc89..c35f54ac5d 100644 --- a/fuel-vm/src/checked_transaction/builder.rs +++ b/fuel-vm/src/checked_transaction/builder.rs @@ -6,6 +6,7 @@ use super::{ }; use crate::{ checked_transaction::CheckPredicates, + pool::VmPool, prelude::*, }; use fuel_tx::{ @@ -20,7 +21,7 @@ where Tx: IntoChecked, { /// Finalize the builder into a [`Checked`] of the correct type - fn finalize_checked(&self, height: BlockHeight) -> Checked; + fn finalize_checked(&self, height: BlockHeight, pool: VmPool) -> Checked; /// Finalize the builder into a [`Checked`] of the correct type, with basic checks /// only @@ -32,9 +33,9 @@ where Self: Finalizable, Checked: CheckPredicates, { - fn finalize_checked(&self, height: BlockHeight) -> Checked { + fn finalize_checked(&self, height: BlockHeight, pool: VmPool) -> Checked { self.finalize() - .into_checked(height, self.get_params()) + .into_checked(height, self.get_params(), pool) .expect("failed to check tx") } diff --git a/fuel-vm/src/interpreter/debug.rs b/fuel-vm/src/interpreter/debug.rs index 1e33b97d7e..436191c719 100644 --- a/fuel-vm/src/interpreter/debug.rs +++ b/fuel-vm/src/interpreter/debug.rs @@ -8,6 +8,9 @@ use super::Interpreter; use crate::prelude::*; use fuel_asm::RegId; +#[cfg(test)] +use crate::pool::test_pool; + impl Interpreter where Tx: ExecutableTransaction, @@ -91,7 +94,7 @@ fn breakpoint_script() { .script_gas_limit(gas_limit) .add_random_fee_input() .finalize() - .into_checked(height, &consensus_params) + .into_checked(height, &consensus_params, test_pool()) .expect("failed to generate checked tx") .into_ready( gas_price, @@ -170,7 +173,7 @@ fn single_stepping() { .script_gas_limit(gas_limit) .add_random_fee_input() .finalize() - .into_checked(height, &consensus_params) + .into_checked(height, &consensus_params, test_pool()) .expect("failed to generate checked tx") .into_ready( gas_price, diff --git a/fuel-vm/src/interpreter/executors/instruction/tests/reserved_registers.rs b/fuel-vm/src/interpreter/executors/instruction/tests/reserved_registers.rs index ecc4e4f8ee..244a6ccffb 100644 --- a/fuel-vm/src/interpreter/executors/instruction/tests/reserved_registers.rs +++ b/fuel-vm/src/interpreter/executors/instruction/tests/reserved_registers.rs @@ -7,6 +7,7 @@ use super::*; use crate::{ checked_transaction::IntoChecked, interpreter::InterpreterParams, + pool::test_pool, prelude::{ FeeParameters, MemoryStorage, @@ -65,7 +66,7 @@ fn cant_write_to_reserved_registers(raw_random_instruction: u32) -> TestResult { .finalize(); let tx = tx - .into_checked(block_height, &consensus_params) + .into_checked(block_height, &consensus_params, test_pool()) .expect("failed to check tx") .into_ready(zero_gas_price, vm.gas_costs(), &fee_params) .expect("failed dynamic checks"); diff --git a/fuel-vm/src/interpreter/executors/main.rs b/fuel-vm/src/interpreter/executors/main.rs index e29594d339..98ec0fb9e5 100644 --- a/fuel-vm/src/interpreter/executors/main.rs +++ b/fuel-vm/src/interpreter/executors/main.rs @@ -27,6 +27,7 @@ use crate::{ Interpreter, RuntimeBalances, }, + pool::VmPool, predicate::RuntimePredicate, prelude::{ BugVariant, @@ -157,12 +158,13 @@ where pub fn check_predicates( checked: &Checked, params: &CheckPredicateParams, + pool: VmPool, ) -> Result where ::Metadata: CheckedMetadata, { let tx = checked.transaction(); - Self::run_predicate(PredicateRunKind::Verifying(tx), params) + Self::run_predicate(PredicateRunKind::Verifying(tx), params, pool) } /// Initialize the VM with the provided transaction and check all predicates defined @@ -173,6 +175,7 @@ where pub async fn check_predicates_async( checked: &Checked, params: &CheckPredicateParams, + pool: VmPool, ) -> Result where Tx: Send + 'static, @@ -182,7 +185,7 @@ where let tx = checked.transaction(); let predicates_checked = - Self::run_predicate_async::(PredicateRunKind::Verifying(tx), params) + Self::run_predicate_async::(PredicateRunKind::Verifying(tx), params, pool) .await?; Ok(predicates_checked) @@ -197,9 +200,10 @@ where pub fn estimate_predicates( transaction: &mut Tx, params: &CheckPredicateParams, + pool: VmPool, ) -> Result { let predicates_checked = - Self::run_predicate(PredicateRunKind::Estimating(transaction), params)?; + Self::run_predicate(PredicateRunKind::Estimating(transaction), params, pool)?; Ok(predicates_checked) } @@ -212,6 +216,7 @@ where pub async fn estimate_predicates_async( transaction: &mut Tx, params: &CheckPredicateParams, + pool: VmPool, ) -> Result where Tx: Send + 'static, @@ -220,6 +225,7 @@ where let predicates_checked = Self::run_predicate_async::( PredicateRunKind::Estimating(transaction), params, + pool, ) .await?; @@ -229,6 +235,7 @@ where async fn run_predicate_async( kind: PredicateRunKind<'_, Tx>, params: &CheckPredicateParams, + pool: VmPool, ) -> Result where Tx: Send + 'static, @@ -244,6 +251,7 @@ where { let tx = kind.tx().clone(); let my_params = params.clone(); + let pool = pool.clone(); let verify_task = E::create_task(move || { Self::check_predicate( @@ -252,6 +260,7 @@ where predicate_action, predicate, my_params, + pool, ) }); @@ -267,12 +276,14 @@ where fn run_predicate( kind: PredicateRunKind<'_, Tx>, params: &CheckPredicateParams, + pool: VmPool, ) -> Result { let predicate_action = PredicateAction::from(&kind); let mut checks = vec![]; for index in 0..kind.tx().inputs().len() { let tx = kind.tx().clone(); + let pool = pool.clone(); if let Some(predicate) = RuntimePredicate::from_tx(&tx, params.tx_offset, index) @@ -283,6 +294,7 @@ where predicate_action, predicate, params.clone(), + pool, )); } } @@ -296,6 +308,7 @@ where predicate_action: PredicateAction, predicate: RuntimePredicate, params: CheckPredicateParams, + pool: VmPool, ) -> Result<(Word, usize), PredicateVerificationFailed> { match &tx.inputs()[index] { Input::CoinPredicate(CoinPredicate { @@ -326,6 +339,7 @@ where let interpreter_params = InterpreterParams::new(zero_gas_price, params); let mut vm = Self::with_storage(PredicateStorage {}, interpreter_params); + vm.memory = pool.get_new(); let available_gas = match predicate_action { PredicateAction::Verifying => { diff --git a/fuel-vm/src/interpreter/executors/main/tests.rs b/fuel-vm/src/interpreter/executors/main/tests.rs index 8fbee0a3a1..3412f33d36 100644 --- a/fuel-vm/src/interpreter/executors/main/tests.rs +++ b/fuel-vm/src/interpreter/executors/main/tests.rs @@ -5,6 +5,7 @@ use crate::{ CheckPredicates, Checked, }, + pool::test_pool, prelude::*, }; use alloc::{ @@ -57,7 +58,7 @@ fn estimate_gas_gives_proper_gas_used() { let transaction_without_predicate = builder .finalize_checked_basic(Default::default()) - .check_predicates(¶ms.into()) + .check_predicates(¶ms.into(), test_pool()) .expect("Predicate check failed even if we don't have any predicates"); let mut client = MemoryClient::default(); @@ -94,18 +95,19 @@ fn estimate_gas_gives_proper_gas_used() { // unestimated transaction should fail as it's predicates are not estimated assert!(transaction .clone() - .into_checked(Default::default(), params) + .into_checked(Default::default(), params, test_pool()) .is_err()); Interpreter::::estimate_predicates( &mut transaction, ¶ms.into(), + test_pool(), ) .expect("Should successfully estimate predicates"); // transaction should pass checking after estimation - let check_res = transaction.into_checked(Default::default(), params); + let check_res = transaction.into_checked(Default::default(), params, test_pool()); assert!(check_res.is_ok()); } diff --git a/fuel-vm/src/interpreter/internal/tests.rs b/fuel-vm/src/interpreter/internal/tests.rs index 26f8f1d074..f82bfeefaa 100644 --- a/fuel-vm/src/interpreter/internal/tests.rs +++ b/fuel-vm/src/interpreter/internal/tests.rs @@ -9,6 +9,7 @@ use crate::{ }, InterpreterParams, }, + pool::test_pool, prelude::*, }; use fuel_asm::op; @@ -58,7 +59,7 @@ fn external_balance() { .script_gas_limit(gas_limit) .script_gas_limit(100) .maturity(maturity) - .finalize_checked(height) + .finalize_checked(height, test_pool()) .into_ready(gas_price, &gas_costs, &fee_params) .unwrap(); @@ -127,7 +128,7 @@ fn variable_output_updates_in_memory() { .add_random_fee_input() .add_output(variable_output) .finalize() - .into_checked(height, &consensus_params) + .into_checked(height, &consensus_params, test_pool()) .expect("failed to check tx") .into_ready( zero_gas_price, diff --git a/fuel-vm/src/interpreter/memory/tests.rs b/fuel-vm/src/interpreter/memory/tests.rs index bfea609128..18b7237e35 100644 --- a/fuel-vm/src/interpreter/memory/tests.rs +++ b/fuel-vm/src/interpreter/memory/tests.rs @@ -6,6 +6,7 @@ use core::ops::Range; use super::*; use crate::{ interpreter::InterpreterParams, + pool::test_pool, prelude::*, }; use fuel_asm::op; @@ -15,6 +16,8 @@ use test_case::test_case; #[cfg(feature = "random")] #[test] fn memcopy() { + use crate::pool::test_pool; + let tx_params = TxParameters::default().with_max_gas_per_tx(Word::MAX / 2); let zero_gas_price = 0; @@ -31,7 +34,7 @@ fn memcopy() { .finalize(); let tx = tx - .into_checked(Default::default(), &consensus_params) + .into_checked(Default::default(), &consensus_params, test_pool()) .expect("default tx should produce a valid checked transaction") .into_ready( zero_gas_price, @@ -98,7 +101,11 @@ fn stack_alloc_ownership() { .script_gas_limit(1000000) .add_random_fee_input() .finalize() - .into_checked(Default::default(), &ConsensusParameters::standard()) + .into_checked( + Default::default(), + &ConsensusParameters::standard(), + test_pool(), + ) .expect("Empty script should be valid") .into_ready( gas_price, diff --git a/fuel-vm/src/lib.rs b/fuel-vm/src/lib.rs index b72b02620d..9f12b8d26f 100644 --- a/fuel-vm/src/lib.rs +++ b/fuel-vm/src/lib.rs @@ -31,6 +31,7 @@ pub mod crypto; pub mod error; pub mod interpreter; pub mod memory_client; +pub mod pool; pub mod predicate; pub mod state; pub mod storage; diff --git a/fuel-vm/src/pool.rs b/fuel-vm/src/pool.rs new file mode 100644 index 0000000000..83d9dbd822 --- /dev/null +++ b/fuel-vm/src/pool.rs @@ -0,0 +1,64 @@ +//! Pool of VM memory instances for reuse. + +use core::fmt; +use std::sync::{ + Arc, + Mutex, +}; + +use crate::interpreter::Memory; + +/// Pool of VM memory instances for reuse. +#[derive(Default, Clone)] +pub struct VmPool { + pool: Arc>>, +} +impl VmPool { + /// Gets a new VM memory instance from the pool. + pub fn get_new(&self) -> Memory { + let mut pool = self.pool.lock().expect("poisoned"); + pool.pop().unwrap_or_default() + } + + /// Recycles a VM memory instance back into the pool. + pub fn recycle(&self, mut mem: Memory) { + mem.reset(); + let mut pool = self.pool.lock().expect("poisoned"); + pool.push(mem); + } +} + +impl fmt::Debug for VmPool { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self.pool.lock() { + Ok(pool) => write!(f, "VmPool {{ pool: [{} items] }}", pool.len()), + Err(_) => write!(f, "VmPool {{ pool: [poisoned] }}"), + } + } +} + +#[cfg(any(test, feature = "test-helpers"))] +static TEST_POOL: std::sync::OnceLock = std::sync::OnceLock::new(); + +/// Get the global VM pool used for tests and test builders. +#[cfg(any(test, feature = "test-helpers"))] +pub fn test_pool() -> VmPool { + TEST_POOL.get_or_init(VmPool::default).clone() +} + +#[test] +fn test_vm_pool() { + let pool = VmPool::default(); + + let mut mem = pool.get_new(); + mem.grow_stack(1024).expect("Unable to grow stack"); + mem.write_bytes_noownerchecks(0, [1, 2, 3, 4]) + .expect("Unable to write stack"); + let ptr1 = mem.stack_raw() as *const _ as *const u8 as usize; + pool.recycle(mem); + + // Make sure we get the same memory allocation back + let mem = pool.get_new(); + let ptr2 = mem.stack_raw() as *const _ as *const u8 as usize; + assert_eq!(ptr1, ptr2); +} diff --git a/fuel-vm/src/predicate.rs b/fuel-vm/src/predicate.rs index 014ba0c3ba..bc8c4591a8 100644 --- a/fuel-vm/src/predicate.rs +++ b/fuel-vm/src/predicate.rs @@ -71,6 +71,7 @@ mod tests { checked_transaction::CheckPredicateParams, error::PredicateVerificationFailed, interpreter::InterpreterParams, + pool::test_pool, prelude::*, storage::PredicateStorage, }; @@ -282,6 +283,7 @@ mod tests { let result = Interpreter::::check_predicates( &tx, &CheckPredicateParams::default(), + test_pool(), ); assert_eq!(result.map(|_| ()), expected); diff --git a/fuel-vm/src/tests/blockchain.rs b/fuel-vm/src/tests/blockchain.rs index b8d3480256..9160aba94e 100644 --- a/fuel-vm/src/tests/blockchain.rs +++ b/fuel-vm/src/tests/blockchain.rs @@ -6,6 +6,7 @@ use crate::{ InterpreterParams, NotSupportedEcal, }, + pool::test_pool, prelude::*, script_with_data_offset, storage::ContractsStateData, @@ -76,7 +77,7 @@ fn deploy_contract( .with_tx_params(tx_params) .add_output(Output::contract_created(contract_id, state_root)) .add_random_fee_input() - .finalize_checked(height); + .finalize_checked(height, test_pool()); client .deploy(contract_deployer) @@ -599,7 +600,7 @@ fn ldc__load_len_of_target_contract<'a>( .add_random_fee_input() .add_output(output0) .finalize() - .into_checked(height, &consensus_params) + .into_checked(height, &consensus_params, test_pool()) .expect("failed to check tx"); client.deploy(tx_create_target).unwrap(); @@ -661,7 +662,7 @@ fn ldc__load_len_of_target_contract<'a>( .add_random_fee_input() .add_output(output1) .finalize() - .into_checked(height, &consensus_params) + .into_checked(height, &consensus_params, test_pool()) .expect("failed to check tx"); // Patch the code with correct jump address @@ -678,7 +679,7 @@ fn ldc__load_len_of_target_contract<'a>( .add_random_fee_input() .add_output(output1) .finalize() - .into_checked(height, &consensus_params) + .into_checked(height, &consensus_params, test_pool()) .expect("failed to check tx"); client.transact(tx_deploy_loader) @@ -737,7 +738,7 @@ fn ldc_reason_helper(cmd: Vec, expected_reason: PanicReason) { .add_random_fee_input() .add_output(output0) .finalize() - .into_checked(height, &consensus_params) + .into_checked(height, &consensus_params, test_pool()) .expect("failed to check tx"); client.deploy(tx_create_target).unwrap(); @@ -752,7 +753,7 @@ fn ldc_reason_helper(cmd: Vec, expected_reason: PanicReason) { .maturity(maturity) .add_random_fee_input() .finalize() - .into_checked(height, &consensus_params) + .into_checked(height, &consensus_params, test_pool()) .expect("failed to check tx"); let receipts = client.transact(tx_deploy_loader); @@ -1755,7 +1756,7 @@ fn smo_instruction_works() { amount: 0, asset_id: Default::default(), }) - .finalize_checked(block_height); + .finalize_checked(block_height, test_pool()); let non_retryable_free_balance = tx.metadata().non_retryable_balances[&AssetId::BASE]; @@ -1897,7 +1898,7 @@ fn timestamp_works() { .script_gas_limit(gas_limit) .maturity(maturity) .add_random_fee_input() - .finalize_checked(block_height); + .finalize_checked(block_height, test_pool()); let receipts = client.transact(tx); let result = receipts.iter().any(|r| { @@ -1951,7 +1952,7 @@ fn block_height_works(#[values(0, 1, 2, 10, 100)] current_height: u32) { .script_gas_limit(gas_limit) .maturity(maturity) .add_random_fee_input() - .finalize_checked(current_height); + .finalize_checked(current_height, test_pool()); let receipts = client.transact(tx); let Some(Receipt::Log { ra, .. }) = receipts.first() else { @@ -2000,7 +2001,7 @@ fn block_hash_works( .script_gas_limit(gas_limit) .maturity(maturity) .add_random_fee_input() - .finalize_checked(current_height); + .finalize_checked(current_height, test_pool()); let receipts = client.transact(tx); let Some(Receipt::LogData { data, .. }) = receipts.first() else { @@ -2038,7 +2039,7 @@ fn coinbase_works() { .script_gas_limit(gas_limit) .maturity(maturity) .add_random_fee_input() - .finalize_checked(10.into()); + .finalize_checked(10.into(), test_pool()); let receipts = client.transact(tx); let Some(Receipt::LogData { data, .. }) = receipts.first() else { diff --git a/fuel-vm/src/tests/code_coverage.rs b/fuel-vm/src/tests/code_coverage.rs index 26c70f1b08..925a6ff445 100644 --- a/fuel-vm/src/tests/code_coverage.rs +++ b/fuel-vm/src/tests/code_coverage.rs @@ -11,6 +11,7 @@ use fuel_tx::{ use fuel_vm::{ consts::*, + pool::test_pool, prelude::*, }; use rand::{ @@ -55,7 +56,7 @@ fn code_coverage() { ) .script_gas_limit(gas_limit) .maturity(maturity) - .finalize_checked(height); + .finalize_checked(height, test_pool()); #[derive(Clone, Default)] struct ProfilingOutput { diff --git a/fuel-vm/src/tests/crypto.rs b/fuel-vm/src/tests/crypto.rs index 3d80a0ddd0..c1c876327c 100644 --- a/fuel-vm/src/tests/crypto.rs +++ b/fuel-vm/src/tests/crypto.rs @@ -28,6 +28,7 @@ use sha3::{ }; use crate::{ + pool::test_pool, prelude::*, util::test_helpers::check_expected_reason_for_instructions, }; @@ -86,7 +87,7 @@ fn secp256k1_recover() { .script_gas_limit(gas_limit) .maturity(maturity) .add_random_fee_input() - .finalize_checked(height); + .finalize_checked(height, test_pool()); let receipts = client.transact(tx); let success = receipts @@ -144,7 +145,9 @@ fn ecrecover_tx_id() { tx.sign_inputs(&secret, &chain_id); let consensus_params = ConsensusParameters::standard_with_id(chain_id); - let tx = tx.into_checked(height, &consensus_params).unwrap(); + let tx = tx + .into_checked(height, &consensus_params, test_pool()) + .unwrap(); let receipts = client.transact(tx); let success = receipts @@ -223,20 +226,20 @@ async fn recover_tx_id_predicate() { // parallel version let mut tx_for_async = tx.clone(); tx_for_async - .estimate_predicates_async::(&check_params) + .estimate_predicates_async::(&check_params, test_pool()) .await .expect("Should estimate predicate successfully"); tx_for_async - .into_checked(maturity, &consensus_params) + .into_checked(maturity, &consensus_params, test_pool()) .expect("Should check predicate successfully"); } // sequential version - tx.estimate_predicates(&check_params) + tx.estimate_predicates(&check_params, test_pool()) .expect("Should estimate predicate successfully"); - tx.into_checked(maturity, &consensus_params) + tx.into_checked(maturity, &consensus_params, test_pool()) .expect("Should check predicate successfully"); } @@ -371,7 +374,7 @@ fn secp256r1_recover() { .script_gas_limit(gas_limit) .maturity(maturity) .add_random_fee_input() - .finalize_checked(height); + .finalize_checked(height, test_pool()); let receipts = client.transact(tx); let success = receipts @@ -508,7 +511,7 @@ fn ed25519_verify() { .script_gas_limit(gas_limit) .maturity(maturity) .add_random_fee_input() - .finalize_checked(height); + .finalize_checked(height, test_pool()); let receipts = client.transact(tx); let success = receipts @@ -640,7 +643,7 @@ fn sha256() { .script_gas_limit(gas_limit) .maturity(maturity) .add_random_fee_input() - .finalize_checked(height); + .finalize_checked(height, test_pool()); let receipts = client.transact(tx); let success = receipts @@ -731,7 +734,7 @@ fn keccak256() { .script_gas_limit(gas_limit) .maturity(maturity) .add_random_fee_input() - .finalize_checked(height); + .finalize_checked(height, test_pool()); let receipts = client.transact(tx); let success = receipts diff --git a/fuel-vm/src/tests/external.rs b/fuel-vm/src/tests/external.rs index bd777abb62..ca338dfe60 100644 --- a/fuel-vm/src/tests/external.rs +++ b/fuel-vm/src/tests/external.rs @@ -13,10 +13,9 @@ use fuel_tx::{ ScriptExecutionResult, TransactionBuilder, }; -use fuel_vm::prelude::{ - Interpreter, - IntoChecked, - MemoryClient, +use fuel_vm::{ + pool::test_pool, + prelude::*, }; use itertools::Itertools; @@ -55,7 +54,7 @@ fn noop_ecal() { .maturity(Default::default()) .add_random_fee_input() .finalize() - .into_checked(Default::default(), &consensus_params) + .into_checked(Default::default(), &consensus_params, test_pool()) .expect("failed to generate a checked tx"); client.transact(tx); let receipts = client.receipts().expect("Expected receipts"); @@ -131,7 +130,7 @@ fn provide_ecal_fn() { .maturity(Default::default()) .add_random_fee_input() .finalize() - .into_checked(Default::default(), &consensus_params) + .into_checked(Default::default(), &consensus_params, test_pool()) .expect("failed to generate a checked tx"); client.transact(tx); let receipts = client.receipts().expect("Expected receipts"); diff --git a/fuel-vm/src/tests/limits.rs b/fuel-vm/src/tests/limits.rs index 5976232d55..6b1350402e 100644 --- a/fuel-vm/src/tests/limits.rs +++ b/fuel-vm/src/tests/limits.rs @@ -6,7 +6,10 @@ use fuel_tx::{ ConsensusParameters, TransactionBuilder, }; -use fuel_vm::prelude::*; +use fuel_vm::{ + pool::test_pool, + prelude::*, +}; use rand::{ rngs::StdRng, Rng, @@ -37,7 +40,7 @@ fn cannot_exceed_max_inputs() { } script .finalize() - .into_checked(0u32.into(), ¶ms) + .into_checked(0u32.into(), ¶ms, test_pool()) .expect_err("Tx is invalid and shouldn't validate"); } @@ -55,7 +58,7 @@ fn cannot_exceed_max_outputs() { } script .finalize() - .into_checked(0u32.into(), ¶ms) + .into_checked(0u32.into(), ¶ms, test_pool()) .expect_err("Tx is invalid and shouldn't validate"); } @@ -73,6 +76,6 @@ fn cannot_exceed_max_witnesses() { } script .finalize() - .into_checked(0u32.into(), ¶ms) + .into_checked(0u32.into(), ¶ms, test_pool()) .expect_err("Tx is invalid and shouldn't validate"); } diff --git a/fuel-vm/src/tests/memory.rs b/fuel-vm/src/tests/memory.rs index daa9e922f2..ca4e830f4f 100644 --- a/fuel-vm/src/tests/memory.rs +++ b/fuel-vm/src/tests/memory.rs @@ -11,6 +11,7 @@ use fuel_tx::Receipt; use fuel_vm::{ consts::VM_MAX_RAM, interpreter::InterpreterParams, + pool::test_pool, prelude::*, }; @@ -38,7 +39,7 @@ fn setup(program: Vec) -> Transactor { .maturity(maturity) .add_random_fee_input() .finalize() - .into_checked(height, &consensus_params) + .into_checked(height, &consensus_params, test_pool()) .expect("failed to check tx"); let interpreter_params = InterpreterParams::new(gas_price, &consensus_params); diff --git a/fuel-vm/src/tests/metadata.rs b/fuel-vm/src/tests/metadata.rs index c7e31e8bf6..211b1e2ea6 100644 --- a/fuel-vm/src/tests/metadata.rs +++ b/fuel-vm/src/tests/metadata.rs @@ -10,6 +10,7 @@ use crate::{ InterpreterParams, NotSupportedEcal, }, + pool::test_pool, }; use fuel_asm::{ op, @@ -90,7 +91,7 @@ fn metadata() { .add_random_fee_input() .add_output(output) .finalize() - .into_checked(height, &consensus_params) + .into_checked(height, &consensus_params, test_pool()) .expect("failed to check tx"); let interpreter_params = InterpreterParams::new(gas_price, &consensus_params); @@ -140,7 +141,7 @@ fn metadata() { .add_random_fee_input() .add_output(output) .finalize() - .into_checked(height, &consensus_params) + .into_checked(height, &consensus_params, test_pool()) .expect("failed to check tx"); assert!( @@ -201,7 +202,7 @@ fn metadata() { .add_output(outputs[1]) .add_random_fee_input() .finalize() - .into_checked(height, &consensus_params) + .into_checked(height, &consensus_params, test_pool()) .expect("failed to check tx"); let receipts = Transactor::<_, _>::new(&mut storage, interpreter_params) @@ -256,7 +257,7 @@ fn get_metadata_chain_id() { .with_chain_id(chain_id) .add_random_fee_input() .finalize() - .into_checked(height, &consensus_params) + .into_checked(height, &consensus_params, test_pool()) .unwrap(); let receipts = client.transact(script); @@ -291,7 +292,7 @@ fn get_metadata_base_asset_id() { .script_gas_limit(gas_limit) .add_random_fee_input() .finalize() - .into_checked(height, ¶ms) + .into_checked(height, ¶ms, test_pool()) .unwrap(); let receipts = Transactor::<_, _>::new( @@ -328,7 +329,7 @@ fn get_metadata_tx_start() { .script_gas_limit(gas_limit) .add_random_fee_input() .finalize() - .into_checked(height, &ConsensusParameters::default()) + .into_checked(height, &ConsensusParameters::default(), test_pool()) .unwrap(); let receipts = Transactor::<_, _>::new(&mut storage, InterpreterParams::default()) @@ -378,7 +379,7 @@ fn get_transaction_fields() { AssetId::zeroed(), rng.gen(), ) - .finalize_checked(height); + .finalize_checked(height, test_pool()); client.deploy(tx).unwrap(); @@ -470,7 +471,7 @@ fn get_transaction_fields() { ) .add_output(Output::coin(rng.gen(), asset_amt, asset)) .add_output(Output::change(rng.gen(), rng.gen_range(10..1000), asset)) - .finalize_checked(height); + .finalize_checked(height, test_pool()); let inputs = tx.as_ref().inputs(); let outputs = tx.as_ref().outputs(); diff --git a/fuel-vm/src/tests/predicate.rs b/fuel-vm/src/tests/predicate.rs index f8005c2f8b..0ca73e7eb6 100644 --- a/fuel-vm/src/tests/predicate.rs +++ b/fuel-vm/src/tests/predicate.rs @@ -17,6 +17,7 @@ use tokio_rayon::AsyncRayonHandle; use crate::{ error::PredicateVerificationFailed, + pool::test_pool, prelude::*, }; @@ -111,7 +112,7 @@ where let mut transaction = builder.finalize(); transaction - .estimate_predicates(&check_params) + .estimate_predicates(&check_params, test_pool()) .expect("Should estimate predicate"); let checked = transaction @@ -122,14 +123,18 @@ where Interpreter::::check_predicates_async::( &checked, &check_params, + test_pool(), ) .await .map(|checked| checked.gas_used()) }; - let seq_execution = - Interpreter::::check_predicates(&checked, &check_params) - .map(|checked| checked.gas_used()); + let seq_execution = Interpreter::::check_predicates( + &checked, + &check_params, + test_pool(), + ) + .map(|checked| checked.gas_used()); match (parallel_execution, seq_execution) { (Ok(p_gas_used), Ok(s_gas_used)) => { @@ -260,7 +265,7 @@ async fn execute_gas_metered_predicates( let parallel_gas_used = { let mut async_tx = transaction.clone(); async_tx - .estimate_predicates_async::(¶ms) + .estimate_predicates_async::(¶ms, test_pool()) .await .map_err(|_| ())?; @@ -269,7 +274,9 @@ async fn execute_gas_metered_predicates( .expect("Should successfully create checked tranaction with predicate"); Interpreter::::check_predicates_async::( - &tx, ¶ms, + &tx, + ¶ms, + test_pool(), ) .await .map(|r| r.gas_used()) @@ -277,15 +284,18 @@ async fn execute_gas_metered_predicates( }; // sequential version - transaction.estimate_predicates(¶ms).map_err(|_| ())?; + transaction + .estimate_predicates(¶ms, test_pool()) + .map_err(|_| ())?; let tx = transaction .into_checked_basic(Default::default(), &ConsensusParameters::standard()) .expect("Should successfully create checked tranaction with predicate"); - let seq_gas_used = Interpreter::::check_predicates(&tx, ¶ms) - .map(|r| r.gas_used()) - .map_err(|_| ())?; + let seq_gas_used = + Interpreter::::check_predicates(&tx, ¶ms, test_pool()) + .map(|r| r.gas_used()) + .map_err(|_| ())?; assert_eq!(seq_gas_used, parallel_gas_used); @@ -370,14 +380,14 @@ async fn gas_used_by_predicates_not_causes_out_of_gas_during_script() { let _ = builder .clone() .finalize_checked_basic(Default::default()) - .check_predicates_async::(¶ms) + .check_predicates_async::(¶ms, test_pool()) .await .expect("Predicate check failed even if we don't have any predicates"); } let tx_without_predicate = builder .finalize_checked_basic(Default::default()) - .check_predicates(¶ms) + .check_predicates(¶ms, test_pool()) .expect("Predicate check failed even if we don't have any predicates"); let mut client = MemoryClient::default(); @@ -411,7 +421,7 @@ async fn gas_used_by_predicates_not_causes_out_of_gas_during_script() { let mut transaction = builder.finalize(); transaction - .estimate_predicates(¶ms) + .estimate_predicates(¶ms, test_pool()) .expect("Predicate estimation failed"); let checked = transaction @@ -422,7 +432,7 @@ async fn gas_used_by_predicates_not_causes_out_of_gas_during_script() { { let tx_with_predicate = checked .clone() - .check_predicates_async::(¶ms) + .check_predicates_async::(¶ms, test_pool()) .await .expect("Predicate check failed"); @@ -440,7 +450,7 @@ async fn gas_used_by_predicates_not_causes_out_of_gas_during_script() { } let tx_with_predicate = checked - .check_predicates(¶ms) + .check_predicates(¶ms, test_pool()) .expect("Predicate check failed"); client.transact(tx_with_predicate); @@ -493,14 +503,14 @@ async fn gas_used_by_predicates_more_than_limit() { let _ = builder .clone() .finalize_checked_basic(Default::default()) - .check_predicates_async::(¶ms) + .check_predicates_async::(¶ms, test_pool()) .await .expect("Predicate check failed even if we don't have any predicates"); } let tx_without_predicate = builder .finalize_checked_basic(Default::default()) - .check_predicates(¶ms) + .check_predicates(¶ms, test_pool()) .expect("Predicate check failed even if we don't have any predicates"); let mut client = MemoryClient::default(); @@ -543,7 +553,7 @@ async fn gas_used_by_predicates_more_than_limit() { let tx_with_predicate = builder .clone() .finalize_checked_basic(Default::default()) - .check_predicates_async::(¶ms) + .check_predicates_async::(¶ms, test_pool()) .await; assert!(matches!( @@ -554,7 +564,7 @@ async fn gas_used_by_predicates_more_than_limit() { let tx_with_predicate = builder .finalize_checked_basic(Default::default()) - .check_predicates(¶ms); + .check_predicates(¶ms, test_pool()); assert!(matches!( tx_with_predicate.unwrap_err(), diff --git a/fuel-vm/src/tests/profile_gas.rs b/fuel-vm/src/tests/profile_gas.rs index 8122dc71b6..a606a7f840 100644 --- a/fuel-vm/src/tests/profile_gas.rs +++ b/fuel-vm/src/tests/profile_gas.rs @@ -5,7 +5,10 @@ use fuel_asm::{ RegId, }; use fuel_tx::TransactionBuilder; -use fuel_vm::prelude::*; +use fuel_vm::{ + pool::test_pool, + prelude::*, +}; use rand::{ rngs::StdRng, Rng, @@ -46,7 +49,7 @@ fn profile_gas() { ) .script_gas_limit(gas_limit) .maturity(maturity) - .finalize_checked(height); + .finalize_checked(height, test_pool()); let output = GasProfiler::default(); diff --git a/fuel-vm/src/tests/test_helpers.rs b/fuel-vm/src/tests/test_helpers.rs index f60fb8357d..1d87154554 100644 --- a/fuel-vm/src/tests/test_helpers.rs +++ b/fuel-vm/src/tests/test_helpers.rs @@ -11,6 +11,8 @@ use fuel_crypto::SecretKey; use fuel_tx::ConsensusParameters; use fuel_vm::prelude::*; +use fuel_vm::pool::test_pool; + /// Set a register `r` to a Word-sized number value using left-shifts pub fn set_full_word(r: RegisterId, v: Word) -> Vec { let r = u8::try_from(r).unwrap(); @@ -48,7 +50,7 @@ pub fn run_script(script: Vec) -> Vec { Default::default(), ) .finalize() - .into_checked(Default::default(), &consensus_params) + .into_checked(Default::default(), &consensus_params, test_pool()) .expect("failed to generate a checked tx"); client.transact(tx); client.receipts().expect("Expected receipts").to_vec() diff --git a/fuel-vm/src/tests/validation.rs b/fuel-vm/src/tests/validation.rs index 3e2864dab2..744ddd4b97 100644 --- a/fuel-vm/src/tests/validation.rs +++ b/fuel-vm/src/tests/validation.rs @@ -13,7 +13,10 @@ use fuel_tx::{ TransactionBuilder, }; use fuel_types::BlockHeight; -use fuel_vm::prelude::*; +use fuel_vm::{ + pool::test_pool, + prelude::*, +}; use rand::{ rngs::StdRng, Rng, @@ -45,7 +48,7 @@ fn transaction_can_be_executed_after_maturity() { ) .script_gas_limit(100) .maturity(MATURITY) - .finalize_checked(BLOCK_HEIGHT); + .finalize_checked(BLOCK_HEIGHT, test_pool()); let result = TestBuilder::new(2322u64) .block_height(BLOCK_HEIGHT) @@ -129,8 +132,10 @@ fn malleable_fields_do_not_affect_validity() { let vm = Interpreter::<_, Script>::with_memory_storage(); let mut client = MemoryClient::from_txtor(vm.into()); - let receipts = - client.transact(tx.into_checked(0u32.into(), ¶ms).expect("valid tx")); + let receipts = client.transact( + tx.into_checked(0u32.into(), ¶ms, test_pool()) + .expect("valid tx"), + ); let start_id = receipts[0].data().unwrap(); let computed_id = receipts[1].data().unwrap(); diff --git a/fuel-vm/src/util.rs b/fuel-vm/src/util.rs index 7ebb0eb5b6..b36cc707c4 100644 --- a/fuel-vm/src/util.rs +++ b/fuel-vm/src/util.rs @@ -95,6 +95,7 @@ pub mod test_helpers { IntoChecked, }, memory_client::MemoryClient, + pool::test_pool, state::StateTransition, storage::{ ContractsAssetsStorage, @@ -335,7 +336,8 @@ pub mod test_helpers { self.builder.with_script_params(*self.get_script_params()); self.builder.with_fee_params(*self.get_fee_params()); self.builder.with_base_asset_id(*self.get_base_asset_id()); - self.builder.finalize_checked(self.block_height) + self.builder + .finalize_checked(self.block_height, test_pool()) } pub fn get_tx_params(&self) -> &TxParameters { @@ -445,7 +447,7 @@ pub mod test_helpers { .add_random_fee_input() .add_output(Output::contract_created(contract_id, storage_root)) .finalize() - .into_checked(self.block_height, &self.consensus_params) + .into_checked(self.block_height, &self.consensus_params, test_pool()) .expect("failed to check tx"); // setup a contract in current test state @@ -636,7 +638,7 @@ pub mod test_helpers { .with_tx_params(tx_params) .add_output(Output::contract_created(contract_id, state_root)) .add_random_fee_input() - .finalize_checked(height); + .finalize_checked(height, test_pool()); client .deploy(contract_deployer) @@ -672,7 +674,7 @@ pub mod test_helpers { )) .add_random_fee_input() .add_output(Output::contract(0, Default::default(), Default::default())) - .finalize_checked(height); + .finalize_checked(height, test_pool()); check_reason_for_transaction(client, tx_deploy_loader, expected_reason); } From 1fafd09446a4b0cad0b6e5bf00cf4b5ade18c5ca Mon Sep 17 00:00:00 2001 From: Hannes Karppila Date: Fri, 10 May 2024 10:59:50 +0300 Subject: [PATCH 05/25] Add no_std support to pool --- fuel-vm/Cargo.toml | 1 + fuel-vm/src/lib.rs | 2 ++ fuel-vm/src/pool.rs | 36 +++++++++++++++++++++++++++++------- 3 files changed, 32 insertions(+), 7 deletions(-) diff --git a/fuel-vm/Cargo.toml b/fuel-vm/Cargo.toml index 7c32302123..153ade47bd 100644 --- a/fuel-vm/Cargo.toml +++ b/fuel-vm/Cargo.toml @@ -39,6 +39,7 @@ rand = { version = "0.8", optional = true } serde = { version = "1.0", features = ["derive", "rc"], optional = true } serde_with = { version = "3.7", optional = true } sha3 = { version = "0.10", default-features = false } +spin = "0.9" static_assertions = "1.1" strum = { version = "0.24", features = ["derive"], default-features = false } tai64 = { version = "4.0", default-features = false } diff --git a/fuel-vm/src/lib.rs b/fuel-vm/src/lib.rs index 9f12b8d26f..0759c7476b 100644 --- a/fuel-vm/src/lib.rs +++ b/fuel-vm/src/lib.rs @@ -19,6 +19,8 @@ pub extern crate alloc; extern crate core; #[cfg(feature = "std")] extern crate libm as _; // Not needed with stdlib +#[cfg(feature = "std")] +extern crate spin as _; // Not needed with stdlib pub mod backtrace; pub mod call; diff --git a/fuel-vm/src/pool.rs b/fuel-vm/src/pool.rs index 83d9dbd822..0ad407ab3d 100644 --- a/fuel-vm/src/pool.rs +++ b/fuel-vm/src/pool.rs @@ -1,51 +1,73 @@ //! Pool of VM memory instances for reuse. -use core::fmt; -use std::sync::{ - Arc, - Mutex, +use alloc::{ + sync::Arc, + vec::Vec, }; +use core::fmt; use crate::interpreter::Memory; /// Pool of VM memory instances for reuse. #[derive(Default, Clone)] pub struct VmPool { - pool: Arc>>, + #[cfg(feature = "std")] + pool: Arc>>, + #[cfg(not(feature = "std"))] + pool: Arc>>, } impl VmPool { /// Gets a new VM memory instance from the pool. pub fn get_new(&self) -> Memory { + #[cfg(feature = "std")] let mut pool = self.pool.lock().expect("poisoned"); + #[cfg(not(feature = "std"))] + let mut pool = self.pool.lock(); pool.pop().unwrap_or_default() } /// Recycles a VM memory instance back into the pool. pub fn recycle(&self, mut mem: Memory) { mem.reset(); + #[cfg(feature = "std")] let mut pool = self.pool.lock().expect("poisoned"); + #[cfg(not(feature = "std"))] + let mut pool = self.pool.lock(); pool.push(mem); } } impl fmt::Debug for VmPool { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + #[cfg(feature = "std")] match self.pool.lock() { Ok(pool) => write!(f, "VmPool {{ pool: [{} items] }}", pool.len()), Err(_) => write!(f, "VmPool {{ pool: [poisoned] }}"), } + + #[cfg(not(feature = "std"))] + write!(f, "VmPool {{ pool: [{} items] }}", self.pool.lock().len()) } } -#[cfg(any(test, feature = "test-helpers"))] +/// A global pool of VM memory instances used to speed up tests. +#[cfg(all(feature = "std", any(test, feature = "test-helpers")))] static TEST_POOL: std::sync::OnceLock = std::sync::OnceLock::new(); /// Get the global VM pool used for tests and test builders. -#[cfg(any(test, feature = "test-helpers"))] +/// On no_std targets this returns a dummy pool that reallocates every time. +#[cfg(all(feature = "std", any(test, feature = "test-helpers")))] pub fn test_pool() -> VmPool { TEST_POOL.get_or_init(VmPool::default).clone() } +/// Get the global VM pool used for tests and test builders. +/// On no_std targets this returns a dummy pool that reallocates every time. +#[cfg(all(not(feature = "std"), any(test, feature = "test-helpers")))] +pub fn test_pool() -> VmPool { + VmPool::default() +} + #[test] fn test_vm_pool() { let pool = VmPool::default(); From f48b99dddb954b2f1a139ecea720fe3337d68770 Mon Sep 17 00:00:00 2001 From: Hannes Karppila Date: Mon, 13 May 2024 13:21:25 +0300 Subject: [PATCH 06/25] Fix heap reallocation optimization on reset --- fuel-vm/src/interpreter/memory.rs | 46 ++++++++++++++----------------- 1 file changed, 21 insertions(+), 25 deletions(-) diff --git a/fuel-vm/src/interpreter/memory.rs b/fuel-vm/src/interpreter/memory.rs index f17d05f977..085df5e537 100644 --- a/fuel-vm/src/interpreter/memory.rs +++ b/fuel-vm/src/interpreter/memory.rs @@ -50,28 +50,6 @@ mod allocation_tests; #[cfg(test)] mod stack_tests; -/// Resize the heap to at least `new_len` bytes, filling the new space with zeros. -/// If `new_len` is less than the current length, the function does nothing. -/// The function may grow the size more than `new_len` to avoid frequent -/// reallocations. -fn reverse_resize_at_least(vec: &mut Vec, new_len: usize) { - if vec.len() >= new_len { - return - } - - // To reduce small allocations, allocate at least 256 bytes at once. - // After that, double the allocation every time. - let cap = new_len.next_power_of_two().clamp(256, MEM_SIZE); - let mut new_vec = Vec::new(); - new_vec.reserve_exact(cap); - let prefix_zeroes = cap - .checked_sub(vec.len()) - .expect("Attempting to resize impossibly large heap memory"); - new_vec.extend(core::iter::repeat(0).take(prefix_zeroes)); - new_vec.extend(vec.iter().copied()); - *vec = new_vec; -} - /// The memory of the VM, represented as stack and heap. #[derive(Debug, Clone, PartialEq, Eq, Derivative)] pub struct Memory { @@ -105,7 +83,6 @@ impl Memory { /// Resets memory to initial state, keeping the original allocations. pub fn reset(&mut self) { self.stack.truncate(0); - self.heap.truncate(0); self.hp = MEM_SIZE; } @@ -142,6 +119,7 @@ impl Memory { } /// Grows the heap to be at least `new_hp` bytes. + /// Panics if the heap would be shrunk. pub fn grow_heap(&mut self, sp: Reg, new_hp: Word) -> Result<(), PanicReason> { let new_hp_word = new_hp.min(MEM_SIZE as Word); #[allow(clippy::cast_possible_truncation)] // Safety: MEM_SIZE is usize @@ -154,8 +132,26 @@ impl Memory { #[allow(clippy::arithmetic_side_effects)] // Safety: ensured above with min let new_len = MEM_SIZE - new_hp; - // Expand the heap allocation - reverse_resize_at_least(&mut self.heap, new_len); + assert!(self.hp >= new_hp); + #[allow(clippy::arithmetic_side_effects)] // Safety: asserted above + if self.heap.len() >= new_len { + // No need to reallocate, but we need to zero the new space + // in case it was used before a memory reset. + let start = new_hp - self.heap_offset(); + let end = self.hp - self.heap_offset(); + self.heap[start..end].fill(0); + } else { + // Reallocation is needed. + // To reduce frequent reallocations, allocate at least 256 bytes at once. + // After that, double the allocation every time. + let cap = new_len.next_power_of_two().clamp(256, MEM_SIZE); + let old_len = self.heap.len(); + let prefix_zeroes = cap - old_len; + self.heap.resize(cap, 0); + self.heap.copy_within(..old_len, prefix_zeroes); + self.heap[..prefix_zeroes].fill(0); + } + self.hp = new_hp; // If heap enters region where stack has been, truncate the stack From aca6a88e9eba5c032d9829f25214a3c81d0763c2 Mon Sep 17 00:00:00 2001 From: Hannes Karppila Date: Mon, 13 May 2024 13:59:03 +0300 Subject: [PATCH 07/25] Move memory impl into a trait --- fuel-vm/examples/external.rs | 5 +- fuel-vm/examples/single_step.rs | 1 + fuel-vm/src/interpreter.rs | 1 + fuel-vm/src/interpreter/alu/wideint.rs | 1 + fuel-vm/src/interpreter/balances.rs | 5 +- fuel-vm/src/interpreter/blockchain.rs | 1 + fuel-vm/src/interpreter/contract.rs | 1 + fuel-vm/src/interpreter/crypto.rs | 1 + fuel-vm/src/interpreter/diff.rs | 1 + .../src/interpreter/executors/instruction.rs | 1 + fuel-vm/src/interpreter/flow.rs | 2 + fuel-vm/src/interpreter/initialization.rs | 5 +- fuel-vm/src/interpreter/internal.rs | 1 + fuel-vm/src/interpreter/internal/tests.rs | 1 + fuel-vm/src/interpreter/log.rs | 1 + fuel-vm/src/interpreter/memory.rs | 220 +++++++++++------- fuel-vm/src/interpreter/memory/impl_tests.rs | 1 + fuel-vm/src/interpreter/metadata.rs | 1 + fuel-vm/src/pool.rs | 5 +- fuel-vm/src/util.rs | 1 + 20 files changed, 166 insertions(+), 90 deletions(-) diff --git a/fuel-vm/examples/external.rs b/fuel-vm/examples/external.rs index 05b1620d5c..818f34fc17 100644 --- a/fuel-vm/examples/external.rs +++ b/fuel-vm/examples/external.rs @@ -33,7 +33,10 @@ use fuel_tx::{ }; use fuel_vm::{ error::SimpleResult, - interpreter::EcalHandler, + interpreter::{ + EcalHandler, + VmMemory, + }, pool::VmPool, prelude::{ Interpreter, diff --git a/fuel-vm/examples/single_step.rs b/fuel-vm/examples/single_step.rs index d8f92177a4..ca6f30a29e 100644 --- a/fuel-vm/examples/single_step.rs +++ b/fuel-vm/examples/single_step.rs @@ -14,6 +14,7 @@ use fuel_vm::{ interpreter::{ Interpreter, NotSupportedEcal, + VmMemory, }, pool::VmPool, prelude::*, diff --git a/fuel-vm/src/interpreter.rs b/fuel-vm/src/interpreter.rs index 8833504360..d5ca4f52e9 100644 --- a/fuel-vm/src/interpreter.rs +++ b/fuel-vm/src/interpreter.rs @@ -80,6 +80,7 @@ pub use ecal::{ pub use memory::{ Memory, MemoryRange, + VmMemory, }; use crate::checked_transaction::{ diff --git a/fuel-vm/src/interpreter/alu/wideint.rs b/fuel-vm/src/interpreter/alu/wideint.rs index 198fec2ba0..85cbb95449 100644 --- a/fuel-vm/src/interpreter/alu/wideint.rs +++ b/fuel-vm/src/interpreter/alu/wideint.rs @@ -19,6 +19,7 @@ use super::super::{ use crate::{ constraints::reg_key::*, error::SimpleResult, + interpreter::VmMemory, }; // This macro is used to duplicate the implementation for both 128-bit and 256-bit diff --git a/fuel-vm/src/interpreter/balances.rs b/fuel-vm/src/interpreter/balances.rs index 85a444d2e8..3a02c5e656 100644 --- a/fuel-vm/src/interpreter/balances.rs +++ b/fuel-vm/src/interpreter/balances.rs @@ -23,7 +23,10 @@ use alloc::collections::BTreeMap; use core::ops::Index; use hashbrown::HashMap; -use super::Memory; +use super::{ + Memory, + VmMemory, +}; #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub(crate) struct Balance { diff --git a/fuel-vm/src/interpreter/blockchain.rs b/fuel-vm/src/interpreter/blockchain.rs index b1309d5ab4..41724a77c9 100644 --- a/fuel-vm/src/interpreter/blockchain.rs +++ b/fuel-vm/src/interpreter/blockchain.rs @@ -37,6 +37,7 @@ use crate::{ Interpreter, Memory, RuntimeBalances, + VmMemory, }, prelude::Profiler, storage::{ diff --git a/fuel-vm/src/interpreter/contract.rs b/fuel-vm/src/interpreter/contract.rs index 3c8edb4913..77d88d6b0d 100644 --- a/fuel-vm/src/interpreter/contract.rs +++ b/fuel-vm/src/interpreter/contract.rs @@ -15,6 +15,7 @@ use super::{ Interpreter, Memory, RuntimeBalances, + VmMemory, }; use crate::{ constraints::reg_key::*, diff --git a/fuel-vm/src/interpreter/crypto.rs b/fuel-vm/src/interpreter/crypto.rs index 0bb739924d..84b4f1fbd5 100644 --- a/fuel-vm/src/interpreter/crypto.rs +++ b/fuel-vm/src/interpreter/crypto.rs @@ -8,6 +8,7 @@ use super::{ ExecutableTransaction, Interpreter, Memory, + VmMemory, }; use crate::{ constraints::reg_key::*, diff --git a/fuel-vm/src/interpreter/diff.rs b/fuel-vm/src/interpreter/diff.rs index 9da2474256..e96e1d49ea 100644 --- a/fuel-vm/src/interpreter/diff.rs +++ b/fuel-vm/src/interpreter/diff.rs @@ -47,6 +47,7 @@ use super::{ ExecutableTransaction, Interpreter, PanicContext, + VmMemory, }; use storage::*; diff --git a/fuel-vm/src/interpreter/executors/instruction.rs b/fuel-vm/src/interpreter/executors/instruction.rs index 6bef98ecd9..598dee284b 100644 --- a/fuel-vm/src/interpreter/executors/instruction.rs +++ b/fuel-vm/src/interpreter/executors/instruction.rs @@ -14,6 +14,7 @@ use crate::{ EcalHandler, ExecutableTransaction, Interpreter, + VmMemory, }, state::ExecuteState, storage::InterpreterStorage, diff --git a/fuel-vm/src/interpreter/flow.rs b/fuel-vm/src/interpreter/flow.rs index 29e87c0821..4bd55371e0 100644 --- a/fuel-vm/src/interpreter/flow.rs +++ b/fuel-vm/src/interpreter/flow.rs @@ -73,6 +73,8 @@ use fuel_types::{ Word, }; +use super::VmMemory; + #[cfg(test)] mod jump_tests; #[cfg(test)] diff --git a/fuel-vm/src/interpreter/initialization.rs b/fuel-vm/src/interpreter/initialization.rs index a1abba4f9e..26eedb5798 100644 --- a/fuel-vm/src/interpreter/initialization.rs +++ b/fuel-vm/src/interpreter/initialization.rs @@ -19,7 +19,10 @@ use fuel_asm::RegId; use fuel_tx::field::ScriptGasLimit; use fuel_types::Word; -use crate::interpreter::CheckedMetadata; +use crate::interpreter::{ + CheckedMetadata, + VmMemory, +}; impl Interpreter where diff --git a/fuel-vm/src/interpreter/internal.rs b/fuel-vm/src/interpreter/internal.rs index 6d1ea07b75..6b2d4cf9b4 100644 --- a/fuel-vm/src/interpreter/internal.rs +++ b/fuel-vm/src/interpreter/internal.rs @@ -3,6 +3,7 @@ use super::{ Interpreter, Memory, RuntimeBalances, + VmMemory, }; use crate::{ constraints::reg_key::*, diff --git a/fuel-vm/src/interpreter/internal/tests.rs b/fuel-vm/src/interpreter/internal/tests.rs index f82bfeefaa..bc195e451b 100644 --- a/fuel-vm/src/interpreter/internal/tests.rs +++ b/fuel-vm/src/interpreter/internal/tests.rs @@ -8,6 +8,7 @@ use crate::{ set_variable_output, }, InterpreterParams, + VmMemory, }, pool::test_pool, prelude::*, diff --git a/fuel-vm/src/interpreter/log.rs b/fuel-vm/src/interpreter/log.rs index 5ff6a86bf2..a7bc62efc2 100644 --- a/fuel-vm/src/interpreter/log.rs +++ b/fuel-vm/src/interpreter/log.rs @@ -7,6 +7,7 @@ use super::{ ExecutableTransaction, Interpreter, Memory, + VmMemory, }; use crate::{ constraints::reg_key::*, diff --git a/fuel-vm/src/interpreter/memory.rs b/fuel-vm/src/interpreter/memory.rs index 085df5e537..d4b7868593 100644 --- a/fuel-vm/src/interpreter/memory.rs +++ b/fuel-vm/src/interpreter/memory.rs @@ -70,6 +70,124 @@ impl Default for Memory { } } +/// Memory access trait for the VM. +pub trait VmMemory { + /// Resets memory to initial state, keeping the original allocations. + fn reset(&mut self); + + /// Returns a linear memory representation where stack is at the beginning and heap is + /// at the end. + fn into_linear_memory(self) -> Vec; + + /// Grows the stack to be at least `new_sp` bytes. + fn grow_stack(&mut self, new_sp: Word) -> Result<(), PanicReason>; + + /// Grows the heap to be at least `new_hp` bytes. + /// Panics if the heap would be shrunk. + fn grow_heap(&mut self, sp: Reg, new_hp: Word) -> Result<(), PanicReason>; + + /// Verify that the memory range is accessble and return it as a range. + fn verify( + &self, + addr: A, + count: B, + ) -> Result; + + /// Verify a constant-sized memory range. + fn verify_const( + &self, + addr: A, + ) -> Result { + self.verify(addr, C) + } + + /// Returns a reference to memory for reading, if possible. + fn read(&self, addr: A, count: C) + -> Result<&[u8], PanicReason>; + + /// Reads a constant-sized byte array from memory, if possible. + fn read_bytes( + &self, + at: A, + ) -> Result<[u8; C], PanicReason> { + let mut result = [0; C]; + result.copy_from_slice(self.read(at, C)?); + Ok(result) + } + + /// Gets write access to memory, if possible. + /// Doesn't perform any ownership checks. + fn write_noownerchecks( + &mut self, + addr: A, + len: B, + ) -> Result<&mut [u8], PanicReason>; + + /// Writes a constant-sized byte array to memory, if possible. + /// Doesn't perform any ownership checks. + fn write_bytes_noownerchecks( + &mut self, + addr: A, + data: [u8; C], + ) -> Result<(), PanicReason> { + self.write_noownerchecks(addr, C)?.copy_from_slice(&data); + Ok(()) + } + + /// Checks that memory is writable and returns a mutable slice to it. + fn write( + &mut self, + owner: OwnershipRegisters, + addr: A, + len: C, + ) -> Result<&mut [u8], PanicReason> { + let range = self.verify(addr, len)?; + owner.verify_ownership(&range.words())?; + self.write_noownerchecks(range.start(), range.len()) + } + + /// Writes a constant-sized byte array to memory, checking for ownership. + fn write_bytes( + &mut self, + owner: OwnershipRegisters, + addr: A, + data: [u8; C], + ) -> Result<(), PanicReason> { + self.write(owner, addr, data.len())?.copy_from_slice(&data); + Ok(()) + } + + /// Copies the memory from `src` to `dst`. + #[inline] + #[track_caller] + fn memcopy_noownerchecks( + &mut self, + dst: A, + src: B, + len: C, + ) -> Result<(), PanicReason> { + // TODO: Optimize + + let src = src.to_addr()?; + let dst = dst.to_addr()?; + let len = len.to_addr()?; + + let tmp = self.read(src, len)?.to_vec(); + self.write_noownerchecks(dst, len)?.copy_from_slice(&tmp); + Ok(()) + } + + /// Memory access to the raw stack buffer. + /// Note that for efficiency reasons this might not match sp value. + #[cfg(any(test, feature = "test-helpers"))] + fn stack_raw(&self) -> &[u8]; + + /// Memory access to the raw heap buffer. + /// Note that for efficiency reasons this might not match hp value. + #[cfg(any(test, feature = "test-helpers"))] + fn heap_raw(&self) -> &[u8]; +} + impl Memory { /// Create a new VM memory. pub fn new() -> Self { @@ -80,20 +198,22 @@ impl Memory { } } - /// Resets memory to initial state, keeping the original allocations. - pub fn reset(&mut self) { - self.stack.truncate(0); - self.hp = MEM_SIZE; - } - /// Offset of the heap section fn heap_offset(&self) -> usize { MEM_SIZE.saturating_sub(self.heap.len()) } +} + +impl VmMemory for Memory { + /// Resets memory to initial state, keeping the original allocations. + fn reset(&mut self) { + self.stack.truncate(0); + self.hp = MEM_SIZE; + } /// Returns a linear memory representation where stack is at the beginning and heap is /// at the end. - pub fn into_linear_memory(self) -> Vec { + fn into_linear_memory(self) -> Vec { let uninit_memory_size = MEM_SIZE .saturating_sub(self.stack.len()) .saturating_sub(self.heap.len()); @@ -105,7 +225,7 @@ impl Memory { } /// Grows the stack to be at least `new_sp` bytes. - pub fn grow_stack(&mut self, new_sp: Word) -> Result<(), PanicReason> { + fn grow_stack(&mut self, new_sp: Word) -> Result<(), PanicReason> { #[allow(clippy::cast_possible_truncation)] // Safety: MEM_SIZE is usize let new_sp = new_sp.min(MEM_SIZE as Word) as usize; if new_sp > self.stack.len() { @@ -120,7 +240,7 @@ impl Memory { /// Grows the heap to be at least `new_hp` bytes. /// Panics if the heap would be shrunk. - pub fn grow_heap(&mut self, sp: Reg, new_hp: Word) -> Result<(), PanicReason> { + fn grow_heap(&mut self, sp: Reg, new_hp: Word) -> Result<(), PanicReason> { let new_hp_word = new_hp.min(MEM_SIZE as Word); #[allow(clippy::cast_possible_truncation)] // Safety: MEM_SIZE is usize let new_hp = new_hp_word as usize; @@ -161,7 +281,7 @@ impl Memory { } /// Verify that the memory range is accessble and return it as a range. - pub fn verify( + fn verify( &self, addr: A, count: B, @@ -180,17 +300,9 @@ impl Memory { } } - /// Verify a constant-sized memory range. - pub fn verify_const( - &self, - addr: A, - ) -> Result { - self.verify(addr, C) - } - /// Returns a reference to memory for reading, if possible. #[allow(clippy::arithmetic_side_effects)] // Safety: subtractions are checked - pub fn read( + fn read( &self, addr: A, count: C, @@ -208,20 +320,10 @@ impl Memory { } } - /// Reads a constant-sized byte array from memory, if possible. - pub fn read_bytes( - &self, - at: A, - ) -> Result<[u8; C], PanicReason> { - let mut result = [0; C]; - result.copy_from_slice(self.read(at, C)?); - Ok(result) - } - /// Gets write access to memory, if possible. /// Doesn't perform any ownership checks. #[allow(clippy::arithmetic_side_effects)] // Safety: subtractions are checked - pub fn write_noownerchecks( + fn write_noownerchecks( &mut self, addr: A, len: B, @@ -238,71 +340,17 @@ impl Memory { } } - /// Writes a constant-sized byte array to memory, if possible. - /// Doesn't perform any ownership checks. - pub fn write_bytes_noownerchecks( - &mut self, - addr: A, - data: [u8; C], - ) -> Result<(), PanicReason> { - self.write_noownerchecks(addr, C)?.copy_from_slice(&data); - Ok(()) - } - - /// Checks that memory is writable and returns a mutable slice to it. - pub fn write( - &mut self, - owner: OwnershipRegisters, - addr: A, - len: C, - ) -> Result<&mut [u8], PanicReason> { - let range = self.verify(addr, len)?; - owner.verify_ownership(&range.words())?; - self.write_noownerchecks(range.start(), range.len()) - } - - /// Writes a constant-sized byte array to memory, checking for ownership. - pub fn write_bytes( - &mut self, - owner: OwnershipRegisters, - addr: A, - data: [u8; C], - ) -> Result<(), PanicReason> { - self.write(owner, addr, data.len())?.copy_from_slice(&data); - Ok(()) - } - - /// Copies the memory from `src` to `dst`. - #[inline] - #[track_caller] - pub fn memcopy_noownerchecks( - &mut self, - dst: A, - src: B, - len: C, - ) -> Result<(), PanicReason> { - // TODO: Optimize - - let src = src.to_addr()?; - let dst = dst.to_addr()?; - let len = len.to_addr()?; - - let tmp = self.read(src, len)?.to_vec(); - self.write_noownerchecks(dst, len)?.copy_from_slice(&tmp); - Ok(()) - } - /// Memory access to the raw stack buffer. /// Note that for efficiency reasons this might not match sp value. #[cfg(any(test, feature = "test-helpers"))] - pub fn stack_raw(&self) -> &[u8] { + fn stack_raw(&self) -> &[u8] { &self.stack } /// Memory access to the raw heap buffer. /// Note that for efficiency reasons this might not match hp value. #[cfg(any(test, feature = "test-helpers"))] - pub fn heap_raw(&self) -> &[u8] { + fn heap_raw(&self) -> &[u8] { &self.heap } } diff --git a/fuel-vm/src/interpreter/memory/impl_tests.rs b/fuel-vm/src/interpreter/memory/impl_tests.rs index eab5b366d4..42d88e57df 100644 --- a/fuel-vm/src/interpreter/memory/impl_tests.rs +++ b/fuel-vm/src/interpreter/memory/impl_tests.rs @@ -3,6 +3,7 @@ use crate::{ constraints::reg_key::*, consts::MEM_SIZE, + interpreter::VmMemory, }; use super::{ diff --git a/fuel-vm/src/interpreter/metadata.rs b/fuel-vm/src/interpreter/metadata.rs index 3c0a41b356..1998576f49 100644 --- a/fuel-vm/src/interpreter/metadata.rs +++ b/fuel-vm/src/interpreter/metadata.rs @@ -2,6 +2,7 @@ use super::{ internal::inc_pc, ExecutableTransaction, Interpreter, + VmMemory, }; use crate::{ call::CallFrame, diff --git a/fuel-vm/src/pool.rs b/fuel-vm/src/pool.rs index 0ad407ab3d..eac9f4d4b8 100644 --- a/fuel-vm/src/pool.rs +++ b/fuel-vm/src/pool.rs @@ -6,7 +6,10 @@ use alloc::{ }; use core::fmt; -use crate::interpreter::Memory; +use crate::interpreter::{ + Memory, + VmMemory, +}; /// Pool of VM memory instances for reuse. #[derive(Default, Clone)] diff --git a/fuel-vm/src/util.rs b/fuel-vm/src/util.rs index b36cc707c4..7b3ce4aed7 100644 --- a/fuel-vm/src/util.rs +++ b/fuel-vm/src/util.rs @@ -94,6 +94,7 @@ pub mod test_helpers { Checked, IntoChecked, }, + interpreter::VmMemory, memory_client::MemoryClient, pool::test_pool, state::StateTransition, From fc4a17e5c89db918a55c008531c1c794244be826 Mon Sep 17 00:00:00 2001 From: Hannes Karppila Date: Mon, 13 May 2024 15:50:27 +0300 Subject: [PATCH 08/25] Refactor Interpreter to allow using &mut Memory as well --- CHANGELOG.md | 4 +- fuel-types/src/fmt.rs | 5 +- fuel-vm/examples/external.rs | 5 +- fuel-vm/examples/single_step.rs | 1 - fuel-vm/src/interpreter.rs | 20 +- fuel-vm/src/interpreter/alu.rs | 2 +- fuel-vm/src/interpreter/alu/muldiv.rs | 2 +- fuel-vm/src/interpreter/alu/wideint.rs | 49 ++- fuel-vm/src/interpreter/balances.rs | 11 +- fuel-vm/src/interpreter/blockchain.rs | 39 ++- fuel-vm/src/interpreter/constructors.rs | 46 ++- fuel-vm/src/interpreter/contract.rs | 9 +- fuel-vm/src/interpreter/crypto.rs | 27 +- fuel-vm/src/interpreter/debug.rs | 6 +- fuel-vm/src/interpreter/diff.rs | 13 +- fuel-vm/src/interpreter/diff/storage.rs | 8 +- fuel-vm/src/interpreter/diff/tests.rs | 7 +- fuel-vm/src/interpreter/ecal.rs | 2 +- fuel-vm/src/interpreter/executors/debug.rs | 2 +- .../src/interpreter/executors/instruction.rs | 5 +- .../instruction/tests/reserved_registers.rs | 3 +- fuel-vm/src/interpreter/executors/main.rs | 53 +--- .../src/interpreter/executors/predicate.rs | 2 +- fuel-vm/src/interpreter/flow.rs | 20 +- fuel-vm/src/interpreter/gas.rs | 2 +- fuel-vm/src/interpreter/initialization.rs | 15 +- fuel-vm/src/interpreter/internal.rs | 9 +- fuel-vm/src/interpreter/internal/tests.rs | 25 +- fuel-vm/src/interpreter/log.rs | 7 +- fuel-vm/src/interpreter/memory.rs | 299 +++++++++--------- fuel-vm/src/interpreter/memory/impl_tests.rs | 1 - fuel-vm/src/interpreter/memory/tests.rs | 5 +- fuel-vm/src/interpreter/metadata.rs | 5 +- fuel-vm/src/interpreter/owned_or_mut.rs | 46 +++ fuel-vm/src/interpreter/post_execution.rs | 2 +- fuel-vm/src/lib.rs | 1 + fuel-vm/src/memory_client.rs | 50 +-- fuel-vm/src/pool.rs | 5 +- fuel-vm/src/predicate.rs | 3 +- fuel-vm/src/tests/blockchain.rs | 5 +- fuel-vm/src/tests/code_coverage.rs | 9 +- fuel-vm/src/tests/external.rs | 1 + fuel-vm/src/tests/gas_factor.rs | 7 +- fuel-vm/src/tests/memory.rs | 5 +- fuel-vm/src/tests/metadata.rs | 62 ++-- fuel-vm/src/tests/profile_gas.rs | 9 +- fuel-vm/src/tests/upgrade.rs | 17 +- fuel-vm/src/transactor.rs | 52 +-- fuel-vm/src/util.rs | 22 +- 49 files changed, 549 insertions(+), 456 deletions(-) create mode 100644 fuel-vm/src/interpreter/owned_or_mut.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 2a4316d15d..a9633829c9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,13 +13,13 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ### Added -- [#732](https://github.com/FuelLabs/fuel-vm/pull/732): Adds `reset` method to VM memory. +- [#732](https://github.com/FuelLabs/fuel-vm/pull/732): Adds `reset` method to VM memory. #### Breaking - [#725](https://github.com/FuelLabs/fuel-vm/pull/725): `UtxoId::from_str` now rejects inputs with multiple `0x` prefixes. Many `::from_str` implementations also reject extra data in the end of the input, instead of silently ignoring it. `UtxoId::from_str` allows a single `:` between the fields. Unused `GasUnit` struct removed. - [#726](https://github.com/FuelLabs/fuel-vm/pull/726): Removed code related to Binary Merkle Sum Trees (BMSTs). The BMST is deprecated and not used in production environments. -- [#732](https://github.com/FuelLabs/fuel-vm/pull/732): Adds `VmPool` to allow reusing relatively expensive-to-allocate VM memories… Functions and traits which require VM initalization such as `estimate_predicates` now take the `VmPool` as an argument. The old behavior cah be restored by just passing `Default::default()` as the argument. +- [#732](https://github.com/FuelLabs/fuel-vm/pull/732): The `Interpterter::eq` mehtod now only compares accessible memory regions. Adds `VmPool` to allow reusing relatively expensive-to-allocate VM memories… Functions and traits which require VM initalization such as `estimate_predicates` now take the `VmPool` as an argument. The old behavior can be restored by just passing `Default::default()` as the argument. Removes `Clone` from the VM to prevent accidental allocations. ## [Version 0.49.0] diff --git a/fuel-types/src/fmt.rs b/fuel-types/src/fmt.rs index e918777636..d3f58cb271 100644 --- a/fuel-types/src/fmt.rs +++ b/fuel-types/src/fmt.rs @@ -5,10 +5,7 @@ use core::{ }; /// Formatting utility to truncate a vector of bytes to a hex string of max length `N` -pub fn fmt_truncated_hex( - data: &Vec, - f: &mut Formatter, -) -> fmt::Result { +pub fn fmt_truncated_hex(data: &[u8], f: &mut Formatter) -> fmt::Result { let formatted = if data.len() > N { let mut s = hex::encode(&data[0..N.saturating_sub(3)]); s.push_str("..."); diff --git a/fuel-vm/examples/external.rs b/fuel-vm/examples/external.rs index 818f34fc17..05b1620d5c 100644 --- a/fuel-vm/examples/external.rs +++ b/fuel-vm/examples/external.rs @@ -33,10 +33,7 @@ use fuel_tx::{ }; use fuel_vm::{ error::SimpleResult, - interpreter::{ - EcalHandler, - VmMemory, - }, + interpreter::EcalHandler, pool::VmPool, prelude::{ Interpreter, diff --git a/fuel-vm/examples/single_step.rs b/fuel-vm/examples/single_step.rs index ca6f30a29e..d8f92177a4 100644 --- a/fuel-vm/examples/single_step.rs +++ b/fuel-vm/examples/single_step.rs @@ -14,7 +14,6 @@ use fuel_vm::{ interpreter::{ Interpreter, NotSupportedEcal, - VmMemory, }, pool::VmPool, prelude::*, diff --git a/fuel-vm/src/interpreter.rs b/fuel-vm/src/interpreter.rs index d5ca4f52e9..b97cb717ba 100644 --- a/fuel-vm/src/interpreter.rs +++ b/fuel-vm/src/interpreter.rs @@ -61,6 +61,7 @@ mod internal; mod log; mod memory; mod metadata; +mod owned_or_mut; mod post_execution; mod receipts; @@ -80,8 +81,8 @@ pub use ecal::{ pub use memory::{ Memory, MemoryRange, - VmMemory, }; +pub use owned_or_mut::OwnedOrMut; use crate::checked_transaction::{ CreateCheckedMetadata, @@ -112,10 +113,11 @@ pub struct NotSupportedEcal; /// /// These can be obtained with the help of a [`crate::transactor::Transactor`] /// or a client implementation. -#[derive(Debug, Clone)] -pub struct Interpreter { +#[derive(Debug)] +#[cfg_attr(any(test, featutre = "test-helpers"), derive(Clone))] +pub struct Interpreter<'a, S, Tx = (), Ecal = NotSupportedEcal> { registers: [Word; VM_REGISTER_COUNT], - memory: Memory, + memory: OwnedOrMut<'a, Memory>, frames: Vec, receipts: ReceiptsCtx, tx: Tx, @@ -203,15 +205,15 @@ pub(crate) enum PanicContext { ContractId(ContractId), } -impl Interpreter { +impl<'a, S, Tx, Ecal> Interpreter<'a, S, Tx, Ecal> { /// Returns the current state of the VM memory pub fn memory(&self) -> &Memory { - &self.memory + self.memory.as_ref() } /// Returns mutable access to the vm memory pub fn memory_mut(&mut self) -> &mut Memory { - &mut self.memory + self.memory.as_mut() } /// Returns the current state of the registers @@ -345,13 +347,13 @@ fn current_location( InstructionLocation::new(current_contract, offset) } -impl AsRef for Interpreter { +impl<'a, S, Tx, Ecal> AsRef for Interpreter<'a, S, Tx, Ecal> { fn as_ref(&self) -> &S { &self.storage } } -impl AsMut for Interpreter { +impl<'a, S, Tx, Ecal> AsMut for Interpreter<'a, S, Tx, Ecal> { fn as_mut(&mut self) -> &mut S { &mut self.storage } diff --git a/fuel-vm/src/interpreter/alu.rs b/fuel-vm/src/interpreter/alu.rs index 7c4e697009..9b379e04bf 100644 --- a/fuel-vm/src/interpreter/alu.rs +++ b/fuel-vm/src/interpreter/alu.rs @@ -22,7 +22,7 @@ mod wideint; #[cfg(test)] mod tests; -impl Interpreter +impl<'a, S, Tx, Ecal> Interpreter<'a, S, Tx, Ecal> where Tx: ExecutableTransaction, { diff --git a/fuel-vm/src/interpreter/alu/muldiv.rs b/fuel-vm/src/interpreter/alu/muldiv.rs index d097653234..c20e2bf7fb 100644 --- a/fuel-vm/src/interpreter/alu/muldiv.rs +++ b/fuel-vm/src/interpreter/alu/muldiv.rs @@ -15,7 +15,7 @@ use fuel_types::{ Word, }; -impl Interpreter +impl<'a, S, Tx, Ecal> Interpreter<'a, S, Tx, Ecal> where Tx: ExecutableTransaction, { diff --git a/fuel-vm/src/interpreter/alu/wideint.rs b/fuel-vm/src/interpreter/alu/wideint.rs index 85cbb95449..c4a0b4de7c 100644 --- a/fuel-vm/src/interpreter/alu/wideint.rs +++ b/fuel-vm/src/interpreter/alu/wideint.rs @@ -19,7 +19,6 @@ use super::super::{ use crate::{ constraints::reg_key::*, error::SimpleResult, - interpreter::VmMemory, }; // This macro is used to duplicate the implementation for both 128-bit and 256-bit @@ -63,7 +62,7 @@ macro_rules! wideint_ops { $t::from_le_bytes(truncated) } - impl Interpreter + impl<'a, S, Tx, Ecal> Interpreter<'a, S, Tx, Ecal> where Tx: ExecutableTransaction, { @@ -78,11 +77,11 @@ macro_rules! wideint_ops { let dest: &mut Word = &mut w[ra.try_into()?]; // LHS argument is always indirect, load it - let lhs: $t = $t::from_be_bytes(self.memory.read_bytes(b)?); + let lhs: $t = $t::from_be_bytes(self.memory.as_ref().read_bytes(b)?); // RHS is only indirect if the flag is set let rhs: $t = if args.indirect_rhs { - $t::from_be_bytes(self.memory.read_bytes(c)?) + $t::from_be_bytes(self.memory.as_ref().read_bytes(c)?) } else { c.into() }; @@ -104,11 +103,11 @@ macro_rules! wideint_ops { let (SystemRegisters { flag, mut of, mut err, pc, .. }, _) = split_registers(&mut self.registers); // LHS argument is always indirect, load it - let lhs: $t = $t::from_be_bytes(self.memory.read_bytes(b)?); + let lhs: $t = $t::from_be_bytes(self.memory.as_ref().read_bytes(b)?); // RHS is only indirect if the flag is set let rhs: $t = if args.indirect_rhs { - $t::from_be_bytes(self.memory.read_bytes(c)?) + $t::from_be_bytes(self.memory.as_ref().read_bytes(c)?) } else { c.into() }; @@ -122,7 +121,7 @@ macro_rules! wideint_ops { *of = overflow as Word; *err = 0; - self.memory.write_bytes(owner_regs, dest_addr, wrapped.to_be_bytes())?; + self.memory.as_mut().write_bytes(owner_regs, dest_addr, wrapped.to_be_bytes())?; Ok(inc_pc(pc)?) } @@ -139,13 +138,13 @@ macro_rules! wideint_ops { // LHS is only indirect if the flag is set let lhs: $t = if args.indirect_lhs { - $t::from_be_bytes(self.memory.read_bytes(b)?) + $t::from_be_bytes(self.memory.as_ref().read_bytes(b)?) } else { b.into() }; // RHS is only indirect if the flag is set let rhs: $t = if args.indirect_rhs { - $t::from_be_bytes(self.memory.read_bytes(c)?) + $t::from_be_bytes(self.memory.as_ref().read_bytes(c)?) } else { c.into() }; @@ -159,7 +158,7 @@ macro_rules! wideint_ops { *of = overflow as Word; *err = 0; - self.memory.write_bytes(owner_regs, dest_addr, wrapped.to_be_bytes())?; + self.memory.as_mut().write_bytes(owner_regs, dest_addr, wrapped.to_be_bytes())?; Ok(inc_pc(pc)?) } @@ -175,11 +174,11 @@ macro_rules! wideint_ops { let (SystemRegisters { flag, mut of, mut err, pc, .. }, _) = split_registers(&mut self.registers); // LHS is always indirect - let lhs: $t = $t::from_be_bytes(self.memory.read_bytes(b)?); + let lhs: $t = $t::from_be_bytes(self.memory.as_ref().read_bytes(b)?); // RHS is only indirect if the flag is set let rhs: $t = if args.indirect_rhs { - $t::from_be_bytes(self.memory.read_bytes(c)?) + $t::from_be_bytes(self.memory.as_ref().read_bytes(c)?) } else { c.into() }; @@ -204,7 +203,7 @@ macro_rules! wideint_ops { *of = 0; - self.memory.write_bytes(owner_regs, dest_addr, result.to_be_bytes())?; + self.memory.as_mut().write_bytes(owner_regs, dest_addr, result.to_be_bytes())?; Ok(inc_pc(pc)?) } @@ -219,9 +218,9 @@ macro_rules! wideint_ops { let owner_regs = self.ownership_registers(); let (SystemRegisters { flag, mut of, mut err, pc, .. }, _) = split_registers(&mut self.registers); - let lhs: $t = $t::from_be_bytes(self.memory.read_bytes(b)?); - let rhs: $t = $t::from_be_bytes(self.memory.read_bytes(c)?); - let modulus: $t = $t::from_be_bytes(self.memory.read_bytes(d)?); + let lhs: $t = $t::from_be_bytes(self.memory.as_ref().read_bytes(b)?); + let rhs: $t = $t::from_be_bytes(self.memory.as_ref().read_bytes(c)?); + let modulus: $t = $t::from_be_bytes(self.memory.as_ref().read_bytes(d)?); // Use wider types to avoid overflow let lhs = [](lhs); @@ -248,7 +247,7 @@ macro_rules! wideint_ops { *of = 0; - self.memory.write_bytes(owner_regs, dest_addr, result.to_be_bytes())?; + self.memory.as_mut().write_bytes(owner_regs, dest_addr, result.to_be_bytes())?; Ok(inc_pc(pc)?) } @@ -263,9 +262,9 @@ macro_rules! wideint_ops { let owner_regs = self.ownership_registers(); let (SystemRegisters { flag, mut of, mut err, pc, .. }, _) = split_registers(&mut self.registers); - let lhs: $t = $t::from_be_bytes(self.memory.read_bytes(b)?); - let rhs: $t = $t::from_be_bytes(self.memory.read_bytes(c)?); - let modulus: $t = $t::from_be_bytes(self.memory.read_bytes(d)?); + let lhs: $t = $t::from_be_bytes(self.memory.as_ref().read_bytes(b)?); + let rhs: $t = $t::from_be_bytes(self.memory.as_ref().read_bytes(c)?); + let modulus: $t = $t::from_be_bytes(self.memory.as_ref().read_bytes(d)?); let lhs = [](lhs); let rhs = [](rhs); @@ -290,7 +289,7 @@ macro_rules! wideint_ops { *of = 0; - self.memory.write_bytes(owner_regs, dest_addr, result.to_be_bytes())?; + self.memory.as_mut().write_bytes(owner_regs, dest_addr, result.to_be_bytes())?; Ok(inc_pc(pc)?) } @@ -305,9 +304,9 @@ macro_rules! wideint_ops { let owner_regs = self.ownership_registers(); let (SystemRegisters { mut of, mut err, pc, flag, .. }, _) = split_registers(&mut self.registers); - let lhs: $t = $t::from_be_bytes(self.memory.read_bytes(b)?); - let rhs: $t = $t::from_be_bytes(self.memory.read_bytes(c)?); - let divider: $t = $t::from_be_bytes(self.memory.read_bytes(d)?); + let lhs: $t = $t::from_be_bytes(self.memory.as_ref().read_bytes(b)?); + let rhs: $t = $t::from_be_bytes(self.memory.as_ref().read_bytes(c)?); + let divider: $t = $t::from_be_bytes(self.memory.as_ref().read_bytes(d)?); const S: usize = core::mem::size_of::<$t>(); @@ -332,7 +331,7 @@ macro_rules! wideint_ops { *of = overflows as Word; *err = 0; - self.memory.write_bytes(owner_regs, dest_addr, result.to_be_bytes())?; + self.memory.as_mut().write_bytes(owner_regs, dest_addr, result.to_be_bytes())?; Ok(inc_pc(pc)?) } diff --git a/fuel-vm/src/interpreter/balances.rs b/fuel-vm/src/interpreter/balances.rs index 3a02c5e656..fdd3a640b4 100644 --- a/fuel-vm/src/interpreter/balances.rs +++ b/fuel-vm/src/interpreter/balances.rs @@ -23,10 +23,7 @@ use alloc::collections::BTreeMap; use core::ops::Index; use hashbrown::HashMap; -use super::{ - Memory, - VmMemory, -}; +use super::Memory; #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub(crate) struct Balance { @@ -175,7 +172,7 @@ impl RuntimeBalances { let new_ssp = vm.registers[RegId::SSP].checked_add(len).expect( "Consensus parameters must not allow stack overflow during VM initialization", ); - vm.memory.grow_stack(new_ssp).expect( + vm.memory_mut().grow_stack(new_ssp).expect( "Consensus parameters must not allow stack overflow during VM initialization", ); vm.registers[RegId::SSP] = new_ssp; @@ -184,10 +181,10 @@ impl RuntimeBalances { let value = balance.value(); let ofs = balance.offset(); - vm.memory + vm.memory_mut() .write_bytes_noownerchecks(ofs, **asset) .expect("Checked above"); - vm.memory + vm.memory_mut() .write_bytes_noownerchecks( ofs.saturating_add(AssetId::LEN), value.to_be_bytes(), diff --git a/fuel-vm/src/interpreter/blockchain.rs b/fuel-vm/src/interpreter/blockchain.rs index 41724a77c9..3b707b00be 100644 --- a/fuel-vm/src/interpreter/blockchain.rs +++ b/fuel-vm/src/interpreter/blockchain.rs @@ -37,7 +37,6 @@ use crate::{ Interpreter, Memory, RuntimeBalances, - VmMemory, }, prelude::Profiler, storage::{ @@ -78,7 +77,7 @@ mod smo_tests; #[cfg(test)] mod test; -impl Interpreter +impl<'a, S, Tx, Ecal> Interpreter<'a, S, Tx, Ecal> where Tx: ExecutableTransaction, S: InterpreterStorage, @@ -104,7 +103,7 @@ where self.gas_charge(gas_cost.base())?; let contract_max_size = self.contract_max_size(); let current_contract = - current_contract(&self.context, self.registers.fp(), &self.memory)?; + current_contract(&self.context, self.registers.fp(), self.memory.as_ref())?; let ( SystemRegisters { cgas, @@ -119,7 +118,7 @@ where _, ) = split_registers(&mut self.registers); let input = LoadContractCodeCtx { - memory: &mut self.memory, + memory: self.memory.as_mut(), profiler: &mut self.profiler, storage: &mut self.storage, contract_max_size, @@ -146,7 +145,7 @@ where BurnCtx { storage: &mut self.storage, context: &self.context, - memory: &self.memory, + memory: self.memory.as_ref(), receipts: &mut self.receipts, fp: fp.as_ref(), pc, @@ -171,7 +170,7 @@ where MintCtx { storage: &mut self.storage, context: &self.context, - memory: &self.memory, + memory: self.memory.as_ref(), receipts: &mut self.receipts, profiler: &mut self.profiler, new_storage_gas_per_byte, @@ -197,7 +196,7 @@ where self.gas_charge(gas_cost.base())?; let current_contract = - current_contract(&self.context, self.registers.fp(), &self.memory)?; + current_contract(&self.context, self.registers.fp(), self.memory.as_ref())?; let owner = self.ownership_registers(); let ( SystemRegisters { @@ -206,7 +205,7 @@ where _, ) = split_registers(&mut self.registers); let input = CodeCopyCtx { - memory: &mut self.memory, + memory: self.memory.as_mut(), input_contracts: InputContracts::new( self.tx.input_contracts(), &mut self.panic_context, @@ -228,7 +227,7 @@ where let owner = self.ownership_registers(); block_hash( &self.storage, - &mut self.memory, + self.memory.as_mut(), owner, self.registers.pc_mut(), a, @@ -246,7 +245,7 @@ where let owner = self.ownership_registers(); coinbase( &self.storage, - &mut self.memory, + self.memory.as_mut(), owner, self.registers.pc_mut(), a, @@ -257,7 +256,7 @@ where let gas_cost = self.gas_costs().croo(); self.gas_charge(gas_cost.base())?; let current_contract = - current_contract(&self.context, self.registers.fp(), &self.memory)?; + current_contract(&self.context, self.registers.fp(), self.memory.as_ref())?; let owner = self.ownership_registers(); let ( SystemRegisters { @@ -266,7 +265,7 @@ where _, ) = split_registers(&mut self.registers); CodeRootCtx { - memory: &mut self.memory, + memory: self.memory.as_mut(), storage: &mut self.storage, gas_cost, profiler: &mut self.profiler, @@ -294,7 +293,7 @@ where // We will charge for the contracts size in the `code_size`. self.gas_charge(gas_cost.base())?; let current_contract = - current_contract(&self.context, self.registers.fp(), &self.memory)?; + current_contract(&self.context, self.registers.fp(), self.memory.as_ref())?; let ( SystemRegisters { cgas, ggas, pc, is, .. @@ -303,7 +302,7 @@ where ) = split_registers(&mut self.registers); let result = &mut w[WriteRegKey::try_from(ra)?]; let input = CodeSizeCtx { - memory: &mut self.memory, + memory: self.memory.as_mut(), storage: &mut self.storage, gas_cost, profiler: &mut self.profiler, @@ -337,7 +336,7 @@ where .. } = self; - state_clear_qword(&contract_id?, storage, memory, pc, result, input) + state_clear_qword(&contract_id?, storage, memory.as_ref(), pc, result, input) } pub(crate) fn state_read_word( @@ -362,7 +361,7 @@ where state_read_word( StateReadWordCtx { storage, - memory, + memory: memory.as_ref(), context, fp: fp.as_ref(), pc, @@ -394,7 +393,7 @@ where state_read_qword( &contract_id?, storage, - memory, + memory.as_mut(), pc, owner, result, @@ -434,7 +433,7 @@ where state_write_word( StateWriteWordCtx { storage, - memory, + memory: memory.as_ref(), context, profiler: &mut self.profiler, new_storage_gas_per_byte, @@ -483,7 +482,7 @@ where state_write_qword( &contract_id?, storage, - memory, + memory.as_ref(), &mut self.profiler, new_storage_per_byte, self.frames.last().map(|frame| frame.to()).copied(), @@ -520,7 +519,7 @@ where let input = MessageOutputCtx { base_asset_id, max_message_data_length, - memory: &mut self.memory, + memory: self.memory.as_mut(), receipts: &mut self.receipts, balances: &mut self.balances, storage: &mut self.storage, diff --git a/fuel-vm/src/interpreter/constructors.rs b/fuel-vm/src/interpreter/constructors.rs index 7495e83501..d49102084e 100644 --- a/fuel-vm/src/interpreter/constructors.rs +++ b/fuel-vm/src/interpreter/constructors.rs @@ -1,10 +1,12 @@ //! Exposed constructors API for the [`Interpreter`] #![allow(clippy::default_constructed_unit_structs)] // need for ::default() depends on cfg +#[cfg(feature = "test-helpers")] +use super::ExecutableTransaction; use super::{ - ExecutableTransaction, Interpreter, Memory, + OwnedOrMut, RuntimeBalances, }; use crate::{ @@ -14,6 +16,7 @@ use crate::{ InterpreterParams, PanicContext, }, + pool::test_pool, state::Debugger, }; @@ -30,7 +33,7 @@ use crate::{ storage::MemoryStorage, }; -impl Interpreter +impl<'a, S, Tx, Ecal> Interpreter<'a, S, Tx, Ecal> where Tx: Default, Ecal: Default, @@ -40,12 +43,16 @@ where /// If the provided storage implements /// [`crate::storage::InterpreterStorage`], the returned interpreter /// will provide full functionality. - pub fn with_storage(storage: S, interpreter_params: InterpreterParams) -> Self { - Self::with_storage_and_ecal(storage, interpreter_params, Ecal::default()) + pub fn with_storage( + memory: OwnedOrMut<'a, Memory>, + storage: S, + interpreter_params: InterpreterParams, + ) -> Self { + Self::with_storage_and_ecal(memory, storage, interpreter_params, Ecal::default()) } } -impl Interpreter +impl<'a, S, Tx, Ecal> Interpreter<'a, S, Tx, Ecal> where Tx: Default, { @@ -55,13 +62,14 @@ where /// [`crate::storage::InterpreterStorage`], the returned interpreter /// will provide full functionality. pub fn with_storage_and_ecal( + memory: OwnedOrMut<'a, Memory>, storage: S, interpreter_params: InterpreterParams, ecal_state: Ecal, ) -> Self { Self { registers: [0; VM_REGISTER_COUNT], - memory: Memory::new(), + memory, frames: vec![], receipts: Default::default(), tx: Default::default(), @@ -78,7 +86,7 @@ where } } -impl Interpreter { +impl<'a, S, Tx, Ecal> Interpreter<'a, S, Tx, Ecal> { /// Sets a profiler for the VM #[cfg(feature = "profile-any")] pub fn with_profiler

(&mut self, receiver: P) -> &mut Self @@ -90,27 +98,16 @@ impl Interpreter { } } -impl Interpreter -where - S: Clone, - Tx: ExecutableTransaction, - Ecal: Clone, -{ - /// Build the interpreter - pub fn build(&mut self) -> Self { - self.clone() - } -} - #[cfg(feature = "test-helpers")] -impl Default for Interpreter +impl<'a, S, Tx, Ecal> Default for Interpreter<'a, S, Tx, Ecal> where S: Default, Tx: ExecutableTransaction, Ecal: EcalHandler + Default, { fn default() -> Self { - Interpreter::::with_storage( + Interpreter::<'a, S, Tx, Ecal>::with_storage( + test_pool().get_new().into(), Default::default(), InterpreterParams::default(), ) @@ -118,7 +115,7 @@ where } #[cfg(test)] -impl Interpreter<(), Tx, Ecal> +impl<'a, Tx, Ecal> Interpreter<'a, (), Tx, Ecal> where Tx: ExecutableTransaction, Ecal: EcalHandler + Default, @@ -132,7 +129,7 @@ where } #[cfg(feature = "test-helpers")] -impl Interpreter +impl<'a, Tx, Ecal> Interpreter<'a, MemoryStorage, Tx, Ecal> where Tx: ExecutableTransaction, Ecal: EcalHandler + Default, @@ -146,7 +143,7 @@ where } #[cfg(feature = "test-helpers")] -impl Interpreter +impl<'a, Tx, Ecal> Interpreter<'a, MemoryStorage, Tx, Ecal> where Tx: ExecutableTransaction, Ecal: EcalHandler, @@ -156,6 +153,7 @@ where /// It will have full capabilities. pub fn with_memory_storage_and_ecal(ecal: Ecal) -> Self { Interpreter::::with_storage_and_ecal( + test_pool().get_new().into(), Default::default(), InterpreterParams::default(), ecal, diff --git a/fuel-vm/src/interpreter/contract.rs b/fuel-vm/src/interpreter/contract.rs index 77d88d6b0d..722fbc3f69 100644 --- a/fuel-vm/src/interpreter/contract.rs +++ b/fuel-vm/src/interpreter/contract.rs @@ -15,7 +15,6 @@ use super::{ Interpreter, Memory, RuntimeBalances, - VmMemory, }; use crate::{ constraints::reg_key::*, @@ -61,7 +60,7 @@ use alloc::borrow::Cow; #[cfg(test)] mod tests; -impl Interpreter +impl<'a, S, Tx, Ecal> Interpreter<'a, S, Tx, Ecal> where S: InterpreterStorage, Tx: ExecutableTransaction, @@ -76,7 +75,7 @@ where let result = &mut w[WriteRegKey::try_from(ra)?]; let input = ContractBalanceCtx { storage: &self.storage, - memory: &mut self.memory, + memory: self.memory.as_mut(), pc, input_contracts: InputContracts::new( self.tx.input_contracts(), @@ -108,7 +107,7 @@ where ) = split_registers(&mut self.registers); let input = TransferCtx { storage: &mut self.storage, - memory: &mut self.memory, + memory: self.memory.as_mut(), context: &self.context, balances: &mut self.balances, receipts: &mut self.receipts, @@ -147,7 +146,7 @@ where ) = split_registers(&mut self.registers); let input = TransferCtx { storage: &mut self.storage, - memory: &mut self.memory, + memory: self.memory.as_mut(), context: &self.context, balances: &mut self.balances, receipts: &mut self.receipts, diff --git a/fuel-vm/src/interpreter/crypto.rs b/fuel-vm/src/interpreter/crypto.rs index 84b4f1fbd5..7df83087aa 100644 --- a/fuel-vm/src/interpreter/crypto.rs +++ b/fuel-vm/src/interpreter/crypto.rs @@ -8,7 +8,6 @@ use super::{ ExecutableTransaction, Interpreter, Memory, - VmMemory, }; use crate::{ constraints::reg_key::*, @@ -30,7 +29,7 @@ use fuel_types::{ #[cfg(test)] mod tests; -impl Interpreter +impl<'a, S, Tx, Ecal> Interpreter<'a, S, Tx, Ecal> where Tx: ExecutableTransaction, { @@ -42,7 +41,7 @@ where ) -> SimpleResult<()> { let owner = self.ownership_registers(); let (SystemRegisters { err, pc, .. }, _) = split_registers(&mut self.registers); - secp256k1_recover(&mut self.memory, owner, err, pc, a, b, c) + secp256k1_recover(self.memory.as_mut(), owner, err, pc, a, b, c) } pub(crate) fn secp256r1_recover( @@ -53,7 +52,7 @@ where ) -> SimpleResult<()> { let owner = self.ownership_registers(); let (SystemRegisters { err, pc, .. }, _) = split_registers(&mut self.registers); - secp256r1_recover(&mut self.memory, owner, err, pc, a, b, c) + secp256r1_recover(self.memory.as_mut(), owner, err, pc, a, b, c) } pub(crate) fn ed25519_verify( @@ -63,17 +62,31 @@ where c: Word, ) -> SimpleResult<()> { let (SystemRegisters { err, pc, .. }, _) = split_registers(&mut self.registers); - ed25519_verify(&mut self.memory, err, pc, a, b, c) + ed25519_verify(self.memory.as_mut(), err, pc, a, b, c) } pub(crate) fn keccak256(&mut self, a: Word, b: Word, c: Word) -> SimpleResult<()> { let owner = self.ownership_registers(); - keccak256(&mut self.memory, owner, self.registers.pc_mut(), a, b, c) + keccak256( + self.memory.as_mut(), + owner, + self.registers.pc_mut(), + a, + b, + c, + ) } pub(crate) fn sha256(&mut self, a: Word, b: Word, c: Word) -> SimpleResult<()> { let owner = self.ownership_registers(); - sha256(&mut self.memory, owner, self.registers.pc_mut(), a, b, c) + sha256( + self.memory.as_mut(), + owner, + self.registers.pc_mut(), + a, + b, + c, + ) } } diff --git a/fuel-vm/src/interpreter/debug.rs b/fuel-vm/src/interpreter/debug.rs index 436191c719..e7a9fc88e0 100644 --- a/fuel-vm/src/interpreter/debug.rs +++ b/fuel-vm/src/interpreter/debug.rs @@ -11,7 +11,7 @@ use fuel_asm::RegId; #[cfg(test)] use crate::pool::test_pool; -impl Interpreter +impl<'a, S, Tx, Ecal> Interpreter<'a, S, Tx, Ecal> where Tx: ExecutableTransaction, { @@ -71,7 +71,7 @@ fn breakpoint_script() { use fuel_asm::op; use fuel_tx::ConsensusParameters; - let mut vm = Interpreter::<_, _>::with_memory_storage(); + let mut vm = Interpreter::<'_, _, _>::with_memory_storage(); let gas_limit = 1_000_000; let gas_price = 0; @@ -151,7 +151,7 @@ fn single_stepping() { use fuel_asm::op; use fuel_tx::ConsensusParameters; - let mut vm = Interpreter::<_, _>::with_memory_storage(); + let mut vm = Interpreter::<'_, _, _>::with_memory_storage(); let gas_limit = 1_000_000; let height = Default::default(); diff --git a/fuel-vm/src/interpreter/diff.rs b/fuel-vm/src/interpreter/diff.rs index e96e1d49ea..9cc3c6a56e 100644 --- a/fuel-vm/src/interpreter/diff.rs +++ b/fuel-vm/src/interpreter/diff.rs @@ -47,7 +47,6 @@ use super::{ ExecutableTransaction, Interpreter, PanicContext, - VmMemory, }; use storage::*; @@ -317,7 +316,7 @@ where .map(|((index, a), b)| (index, a.cloned(), b.cloned())) } -impl Interpreter { +impl<'a, S, Tx, Ecal> Interpreter<'a, S, Tx, Ecal> { /// The diff function generates a diff of VM state, represented by the Diff struct, /// between two VMs internal states. pub fn diff(&self, other: &Self) -> Diff @@ -349,8 +348,8 @@ impl Interpreter { ); diff.changes.extend(balances); - let other_memory = other.memory.clone().into_linear_memory(); - let this_memory = self.memory.clone().into_linear_memory(); + let other_memory = other.memory().clone().into_linear_memory(); + let this_memory = self.memory().clone().into_linear_memory(); let mut memory = this_memory.iter().enumerate().zip(other_memory.iter()); @@ -409,7 +408,7 @@ impl Interpreter { } Change::Balance(Previous(value)) => invert_map(self.balances.as_mut(), value), Change::Memory(Previous(Memory { start, bytes })) => self - .memory + .memory_mut() .write_noownerchecks(*start, bytes.len()) .expect("Memory must exist here") .copy_from_slice(&bytes[..]), @@ -474,14 +473,14 @@ fn invert_receipts_ctx(ctx: &mut ReceiptsCtx, value: &VecState>) invert_vec(ctx_mut.receipts_mut(), value); } -impl PartialEq for Interpreter +impl<'a, S, Tx, Ecal> PartialEq for Interpreter<'a, S, Tx, Ecal> where Tx: PartialEq, { /// Does not compare storage, debugger or profiler fn eq(&self, other: &Self) -> bool { self.registers == other.registers - && self.memory == other.memory + && self.memory.as_ref() == other.memory.as_ref() && self.frames == other.frames && self.receipts == other.receipts && self.tx == other.tx diff --git a/fuel-vm/src/interpreter/diff/storage.rs b/fuel-vm/src/interpreter/diff/storage.rs index cf0b5443b6..fc8ac9e06a 100644 --- a/fuel-vm/src/interpreter/diff/storage.rs +++ b/fuel-vm/src/interpreter/diff/storage.rs @@ -79,7 +79,7 @@ pub struct Record(pub(super) S, pub(super) Vec) where S: InterpreterStorage; -impl Interpreter, Tx, Ecal> +impl<'a, S, Tx, Ecal> Interpreter<'a, Record, Tx, Ecal> where S: InterpreterStorage, Tx: ExecutableTransaction, @@ -87,7 +87,7 @@ where /// Remove the [`Recording`] wrapper from the storage. /// Recording storage changes has an overhead so it's /// useful to be able to remove it once the diff is generated. - pub fn remove_recording(self) -> Interpreter { + pub fn remove_recording(self) -> Interpreter<'a, S, Tx, Ecal> { Interpreter { registers: self.registers, memory: self.memory, @@ -156,7 +156,7 @@ where } } -impl Interpreter +impl<'a, S, Tx, Ecal> Interpreter<'a, S, Tx, Ecal> where S: InterpreterStorage, Tx: ExecutableTransaction, @@ -165,7 +165,7 @@ where /// record any changes this VM makes to it's storage. /// Recording storage changes has an overhead so should /// be used in production. - pub fn add_recording(self) -> Interpreter, Tx, Ecal> { + pub fn add_recording(self) -> Interpreter<'a, Record, Tx, Ecal> { Interpreter { registers: self.registers, memory: self.memory, diff --git a/fuel-vm/src/interpreter/diff/tests.rs b/fuel-vm/src/interpreter/diff/tests.rs index 4d35196feb..48723d7a05 100644 --- a/fuel-vm/src/interpreter/diff/tests.rs +++ b/fuel-vm/src/interpreter/diff/tests.rs @@ -17,6 +17,7 @@ use test_case::test_case; use crate::{ consts::*, + pool::test_pool, storage::MemoryStorage, }; @@ -52,10 +53,12 @@ fn record_and_invert_storage() { InterpreterParams::new(arb_gas_price, &ConsensusParameters::standard()); let a = Interpreter::<_, Script>::with_storage( + test_pool().get_new().into(), Record::new(MemoryStorage::default()), interpreter_params.clone(), ); let mut b = Interpreter::<_, Script>::with_storage( + test_pool().get_new().into(), Record::new(MemoryStorage::default()), interpreter_params, ); @@ -215,9 +218,9 @@ fn test_invert_map(v: &[(u32, u32)], key: u32, value: Option) -> Vec<(u32, #[test] fn reset_vm_memory() { let mut a = Interpreter::<_, Script>::with_memory_storage(); - a.memory.grow_stack(132).unwrap(); + a.memory_mut().grow_stack(132).unwrap(); let mut b = a.clone(); - b.memory[100..132].copy_from_slice(&[1u8; 32]); + b.memory_mut()[100..132].copy_from_slice(&[1u8; 32]); let diff: Diff = a.diff(&b).into(); assert_ne!(a, b); b.reset_vm_state(&diff); diff --git a/fuel-vm/src/interpreter/ecal.rs b/fuel-vm/src/interpreter/ecal.rs index 36f8e9bd2a..a43f0c3ebc 100644 --- a/fuel-vm/src/interpreter/ecal.rs +++ b/fuel-vm/src/interpreter/ecal.rs @@ -68,7 +68,7 @@ impl EcalHandler for PredicateErrorEcal { } } -impl Interpreter +impl<'a, S, Tx, Ecal> Interpreter<'a, S, Tx, Ecal> where Ecal: EcalHandler, { diff --git a/fuel-vm/src/interpreter/executors/debug.rs b/fuel-vm/src/interpreter/executors/debug.rs index ef219056fa..fc663bf922 100644 --- a/fuel-vm/src/interpreter/executors/debug.rs +++ b/fuel-vm/src/interpreter/executors/debug.rs @@ -9,7 +9,7 @@ use crate::{ storage::InterpreterStorage, }; -impl Interpreter +impl<'a, S, Tx, Ecal> Interpreter<'a, S, Tx, Ecal> where S: InterpreterStorage, Tx: ExecutableTransaction, diff --git a/fuel-vm/src/interpreter/executors/instruction.rs b/fuel-vm/src/interpreter/executors/instruction.rs index 598dee284b..b34d2b5b0e 100644 --- a/fuel-vm/src/interpreter/executors/instruction.rs +++ b/fuel-vm/src/interpreter/executors/instruction.rs @@ -14,7 +14,6 @@ use crate::{ EcalHandler, ExecutableTransaction, Interpreter, - VmMemory, }, state::ExecuteState, storage::InterpreterStorage, @@ -32,7 +31,7 @@ use fuel_types::Word; use core::ops::Div; -impl Interpreter +impl<'a, S, Tx, Ecal> Interpreter<'a, S, Tx, Ecal> where S: InterpreterStorage, Tx: ExecutableTransaction, @@ -51,7 +50,7 @@ where ) -> Result> { let pc = self.registers[RegId::PC]; let instruction = RawInstruction::from_be_bytes( - self.memory.read_bytes(pc).map_err(|reason| { + self.memory().read_bytes(pc).map_err(|reason| { InterpreterError::PanicInstruction(PanicInstruction::error( reason, 0, // The value is meaningless since fetch was out-of-bounds diff --git a/fuel-vm/src/interpreter/executors/instruction/tests/reserved_registers.rs b/fuel-vm/src/interpreter/executors/instruction/tests/reserved_registers.rs index 244a6ccffb..791291342b 100644 --- a/fuel-vm/src/interpreter/executors/instruction/tests/reserved_registers.rs +++ b/fuel-vm/src/interpreter/executors/instruction/tests/reserved_registers.rs @@ -54,7 +54,8 @@ fn cant_write_to_reserved_registers(raw_random_instruction: u32) -> TestResult { let mut consensus_params = ConsensusParameters::default(); consensus_params.set_fee_params(fee_params); - let mut vm = Interpreter::<_, _>::with_storage( + let mut vm = Interpreter::<'_, _, _>::with_storage( + test_pool().get_new().into(), MemoryStorage::default(), InterpreterParams::new(zero_gas_price, &consensus_params), ); diff --git a/fuel-vm/src/interpreter/executors/main.rs b/fuel-vm/src/interpreter/executors/main.rs index 98ec0fb9e5..ccd4efb739 100644 --- a/fuel-vm/src/interpreter/executors/main.rs +++ b/fuel-vm/src/interpreter/executors/main.rs @@ -36,7 +36,6 @@ use crate::{ state::{ ExecuteState, ProgramState, - StateTransition, StateTransitionRef, }, storage::{ @@ -146,7 +145,7 @@ impl From<&PredicateRunKind<'_, Tx>> for PredicateAction { } } -impl Interpreter +impl<'a, Tx> Interpreter<'a, PredicateStorage, Tx> where Tx: ExecutableTransaction, { @@ -338,8 +337,11 @@ where let zero_gas_price = 0; let interpreter_params = InterpreterParams::new(zero_gas_price, params); - let mut vm = Self::with_storage(PredicateStorage {}, interpreter_params); - vm.memory = pool.get_new(); + let mut vm = Self::with_storage( + pool.get_new().into(), + PredicateStorage {}, + interpreter_params, + ); let available_gas = match predicate_action { PredicateAction::Verifying => { @@ -435,7 +437,7 @@ where } } -impl Interpreter +impl<'a, S, Tx, Ecal> Interpreter<'a, S, Tx, Ecal> where S: InterpreterStorage, { @@ -502,7 +504,7 @@ where } } -impl Interpreter +impl<'a, S, Tx, Ecal> Interpreter<'a, S, Tx, Ecal> where S: InterpreterStorage, { @@ -605,7 +607,7 @@ where } } -impl Interpreter +impl<'a, S, Tx, Ecal> Interpreter<'a, S, Tx, Ecal> where S: InterpreterStorage, { @@ -714,7 +716,7 @@ where } } -impl Interpreter +impl<'a, S, Tx, Ecal> Interpreter<'a, S, Tx, Ecal> where S: InterpreterStorage, Tx: ExecutableTransaction, @@ -911,32 +913,7 @@ where } } -impl Interpreter -where - S: InterpreterStorage, - Tx: ExecutableTransaction, - ::Metadata: CheckedMetadata, - Ecal: EcalHandler + Default, -{ - /// Allocate internally a new instance of [`Interpreter`] with the provided - /// storage, initialize it with the provided transaction and return the - /// result of th execution in form of [`StateTransition`] - pub fn transact_owned( - storage: S, - tx: Ready, - params: InterpreterParams, - ) -> Result, InterpreterError> { - let mut interpreter = Self::with_storage(storage, params); - interpreter - .transact(tx) - .map(ProgramState::from) - .map(|state| { - StateTransition::new(state, interpreter.tx, interpreter.receipts.into()) - }) - } -} - -impl Interpreter +impl<'a, S, Tx, Ecal> Interpreter<'a, S, Tx, Ecal> where S: InterpreterStorage, Tx: ExecutableTransaction, @@ -974,7 +951,7 @@ where } } -impl Interpreter +impl<'a, S, Tx, Ecal> Interpreter<'a, S, Tx, Ecal> where S: InterpreterStorage, { @@ -1006,7 +983,7 @@ where } } -impl Interpreter +impl<'a, S, Tx, Ecal> Interpreter<'a, S, Tx, Ecal> where S: InterpreterStorage, { @@ -1038,7 +1015,7 @@ where } } -impl Interpreter +impl<'a, S, Tx, Ecal> Interpreter<'a, S, Tx, Ecal> where S: InterpreterStorage, { @@ -1070,7 +1047,7 @@ where } } -impl Interpreter { +impl<'a, S: InterpreterStorage, Tx, Ecal> Interpreter<'a, S, Tx, Ecal> { fn verify_ready_tx( &self, tx: &Ready, diff --git a/fuel-vm/src/interpreter/executors/predicate.rs b/fuel-vm/src/interpreter/executors/predicate.rs index da73af1f7c..0d4dc7d184 100644 --- a/fuel-vm/src/interpreter/executors/predicate.rs +++ b/fuel-vm/src/interpreter/executors/predicate.rs @@ -18,7 +18,7 @@ use fuel_asm::{ Word, }; -impl Interpreter +impl<'a, Tx, Ecal> Interpreter<'a, PredicateStorage, Tx, Ecal> where Tx: ExecutableTransaction, Ecal: EcalHandler, diff --git a/fuel-vm/src/interpreter/flow.rs b/fuel-vm/src/interpreter/flow.rs index 4bd55371e0..e24164261e 100644 --- a/fuel-vm/src/interpreter/flow.rs +++ b/fuel-vm/src/interpreter/flow.rs @@ -73,8 +73,6 @@ use fuel_types::{ Word, }; -use super::VmMemory; - #[cfg(test)] mod jump_tests; #[cfg(test)] @@ -82,7 +80,7 @@ mod ret_tests; #[cfg(test)] mod tests; -impl Interpreter +impl<'a, S, Tx, Ecal> Interpreter<'a, S, Tx, Ecal> where Tx: ExecutableTransaction, { @@ -93,12 +91,12 @@ where pub(crate) fn ret(&mut self, a: Word) -> SimpleResult<()> { let current_contract = - current_contract(&self.context, self.registers.fp(), &self.memory)?; + current_contract(&self.context, self.registers.fp(), self.memory.as_ref())?; let input = RetCtx { receipts: &mut self.receipts, frames: &mut self.frames, registers: &mut self.registers, - memory: &self.memory, + memory: self.memory.as_ref(), context: &mut self.context, current_contract, }; @@ -107,11 +105,11 @@ where pub(crate) fn ret_data(&mut self, a: Word, b: Word) -> SimpleResult { let current_contract = - current_contract(&self.context, self.registers.fp(), &self.memory)?; + current_contract(&self.context, self.registers.fp(), self.memory.as_ref())?; let input = RetCtx { frames: &mut self.frames, registers: &mut self.registers, - memory: &mut self.memory, + memory: self.memory.as_mut(), receipts: &mut self.receipts, context: &mut self.context, current_contract, @@ -121,7 +119,7 @@ where pub(crate) fn revert(&mut self, a: Word) -> SimpleResult<()> { let current_contract = - current_contract(&self.context, self.registers.fp(), &self.memory) + current_contract(&self.context, self.registers.fp(), self.memory.as_ref()) .map_or_else(|_| Some(ContractId::zeroed()), |c| c); revert( &mut self.receipts, @@ -330,7 +328,7 @@ impl JumpArgs { } } -impl Interpreter +impl<'a, S, Tx, Ecal> Interpreter<'a, S, Tx, Ecal> where S: InterpreterStorage, Tx: ExecutableTransaction, @@ -371,13 +369,13 @@ where // We will charge for the frame size in the `prepare_call`. self.gas_charge(gas_cost.base())?; let current_contract = - current_contract(&self.context, self.registers.fp(), &self.memory)?; + current_contract(&self.context, self.registers.fp(), self.memory.as_ref())?; let input_contracts = self.tx.input_contracts().copied().collect::>(); PrepareCallCtx { params, registers: (&mut self.registers).into(), - memory: &mut self.memory, + memory: self.memory.as_mut(), context: &mut self.context, gas_cost, runtime_balances: &mut self.balances, diff --git a/fuel-vm/src/interpreter/gas.rs b/fuel-vm/src/interpreter/gas.rs index 0e029735fa..ce35e0bcb5 100644 --- a/fuel-vm/src/interpreter/gas.rs +++ b/fuel-vm/src/interpreter/gas.rs @@ -25,7 +25,7 @@ use fuel_types::{ #[cfg(test)] mod tests; -impl Interpreter { +impl<'a, S, Tx, Ecal> Interpreter<'a, S, Tx, Ecal> { /// Global remaining gas amount pub fn remaining_gas(&self) -> Word { self.registers[RegId::GGAS] diff --git a/fuel-vm/src/interpreter/initialization.rs b/fuel-vm/src/interpreter/initialization.rs index 26eedb5798..34476ceb23 100644 --- a/fuel-vm/src/interpreter/initialization.rs +++ b/fuel-vm/src/interpreter/initialization.rs @@ -19,12 +19,9 @@ use fuel_asm::RegId; use fuel_tx::field::ScriptGasLimit; use fuel_types::Word; -use crate::interpreter::{ - CheckedMetadata, - VmMemory, -}; +use crate::interpreter::CheckedMetadata; -impl Interpreter +impl<'a, S, Tx, Ecal> Interpreter<'a, S, Tx, Ecal> where Tx: ExecutableTransaction, S: InterpreterStorage, @@ -62,9 +59,9 @@ where let new_ssp = old_ssp .checked_add(data.len() as Word) .expect("VM initialization data must fit into the stack"); - self.memory.grow_stack(new_ssp)?; + self.memory_mut().grow_stack(new_ssp)?; self.registers[RegId::SSP] = new_ssp; - self.memory + self.memory_mut() .write_noownerchecks(old_ssp, data.len()) .expect("VM initialization data must fit into the stack") .copy_from_slice(data); @@ -92,7 +89,7 @@ where } } -impl Interpreter +impl<'a, S, Tx, Ecal> Interpreter<'a, S, Tx, Ecal> where Tx: ExecutableTransaction, S: InterpreterStorage, @@ -111,7 +108,7 @@ where } } -impl Interpreter +impl<'a, S, Tx, Ecal> Interpreter<'a, S, Tx, Ecal> where S: InterpreterStorage, ::DataError: From, diff --git a/fuel-vm/src/interpreter/internal.rs b/fuel-vm/src/interpreter/internal.rs index 6b2d4cf9b4..a4995c0d20 100644 --- a/fuel-vm/src/interpreter/internal.rs +++ b/fuel-vm/src/interpreter/internal.rs @@ -3,7 +3,6 @@ use super::{ Interpreter, Memory, RuntimeBalances, - VmMemory, }; use crate::{ constraints::reg_key::*, @@ -36,13 +35,13 @@ mod message_tests; #[cfg(test)] mod tests; -impl Interpreter +impl<'a, S, Tx, Ecal> Interpreter<'a, S, Tx, Ecal> where Tx: ExecutableTransaction, { pub(crate) fn update_memory_output(&mut self, idx: usize) -> SimpleResult<()> { let tx_offset = self.tx_offset(); - update_memory_output(&mut self.tx, &mut self.memory, tx_offset, idx) + update_memory_output(&mut self.tx, self.memory.as_mut(), tx_offset, idx) } } @@ -97,7 +96,7 @@ pub(crate) fn update_memory_output( Ok(()) } -impl Interpreter { +impl<'a, S, Tx, Ecal> Interpreter<'a, S, Tx, Ecal> { pub(crate) fn set_flag(&mut self, a: Word) -> SimpleResult<()> { let (SystemRegisters { flag, pc, .. }, _) = split_registers(&mut self.registers); set_flag(flag, pc, a) @@ -115,7 +114,7 @@ impl Interpreter { } pub(crate) fn internal_contract(&self) -> Result { - internal_contract(&self.context, self.registers.fp(), &self.memory) + internal_contract(&self.context, self.registers.fp(), self.memory.as_ref()) } pub(crate) fn get_block_height(&self) -> Result { diff --git a/fuel-vm/src/interpreter/internal/tests.rs b/fuel-vm/src/interpreter/internal/tests.rs index bc195e451b..221bf6e9b7 100644 --- a/fuel-vm/src/interpreter/internal/tests.rs +++ b/fuel-vm/src/interpreter/internal/tests.rs @@ -8,7 +8,6 @@ use crate::{ set_variable_output, }, InterpreterParams, - VmMemory, }, pool::test_pool, prelude::*, @@ -32,7 +31,7 @@ use super::inc_pc; fn external_balance() { let mut rng = StdRng::seed_from_u64(2322u64); - let mut vm = Interpreter::<_, _>::with_memory_storage(); + let mut vm = Interpreter::<'_, _, _>::with_memory_storage(); let gas_limit = 1_000_000; let maturity = Default::default(); @@ -69,30 +68,35 @@ fn external_balance() { for (asset_id, amount) in balances { assert!(external_asset_id_balance_sub( &mut vm.balances, - &mut vm.memory, + vm.memory.as_mut(), &asset_id, amount + 1, ) .is_err()); external_asset_id_balance_sub( &mut vm.balances, - &mut vm.memory, + vm.memory.as_mut(), &asset_id, amount - 10, ) .unwrap(); assert!(external_asset_id_balance_sub( &mut vm.balances, - &mut vm.memory, + vm.memory.as_mut(), &asset_id, 11, ) .is_err()); - external_asset_id_balance_sub(&mut vm.balances, &mut vm.memory, &asset_id, 10) - .unwrap(); + external_asset_id_balance_sub( + &mut vm.balances, + vm.memory.as_mut(), + &asset_id, + 10, + ) + .unwrap(); assert!(external_asset_id_balance_sub( &mut vm.balances, - &mut vm.memory, + vm.memory.as_mut(), &asset_id, 1, ) @@ -107,7 +111,8 @@ fn variable_output_updates_in_memory() { let zero_gas_price = 0; let consensus_params = ConsensusParameters::standard(); - let mut vm = Interpreter::<_, _>::with_storage( + let mut vm = Interpreter::<'_, _, _>::with_storage( + test_pool().get_new().into(), MemoryStorage::default(), InterpreterParams::new(zero_gas_price, &consensus_params), ); @@ -144,7 +149,7 @@ fn variable_output_updates_in_memory() { let variable = Output::variable(owner, amount_to_set, asset_id_to_update); let tx_offset = vm.tx_offset(); - set_variable_output(&mut vm.tx, &mut vm.memory, tx_offset, 0, variable).unwrap(); + set_variable_output(&mut vm.tx, vm.memory.as_mut(), tx_offset, 0, variable).unwrap(); // verify the referenced tx output is updated properly assert!(matches!( diff --git a/fuel-vm/src/interpreter/log.rs b/fuel-vm/src/interpreter/log.rs index a7bc62efc2..ba570c70aa 100644 --- a/fuel-vm/src/interpreter/log.rs +++ b/fuel-vm/src/interpreter/log.rs @@ -7,7 +7,6 @@ use super::{ ExecutableTransaction, Interpreter, Memory, - VmMemory, }; use crate::{ constraints::reg_key::*, @@ -21,7 +20,7 @@ use fuel_types::Word; #[cfg(test)] mod tests; -impl Interpreter +impl<'a, S, Tx, Ecal> Interpreter<'a, S, Tx, Ecal> where Tx: ExecutableTransaction, { @@ -29,7 +28,7 @@ where let (SystemRegisters { fp, is, pc, .. }, _) = split_registers(&mut self.registers); let input = LogInput { - memory: &mut self.memory, + memory: self.memory.as_mut(), context: &self.context, receipts: &mut self.receipts, fp: fp.as_ref(), @@ -49,7 +48,7 @@ where let (SystemRegisters { fp, is, pc, .. }, _) = split_registers(&mut self.registers); let input = LogInput { - memory: &mut self.memory, + memory: self.memory.as_mut(), context: &self.context, receipts: &mut self.receipts, fp: fp.as_ref(), diff --git a/fuel-vm/src/interpreter/memory.rs b/fuel-vm/src/interpreter/memory.rs index d4b7868593..bbfff7d93e 100644 --- a/fuel-vm/src/interpreter/memory.rs +++ b/fuel-vm/src/interpreter/memory.rs @@ -11,7 +11,6 @@ use crate::{ error::SimpleResult, }; -use derivative::Derivative; use fuel_asm::{ Imm12, Imm24, @@ -19,11 +18,15 @@ use fuel_asm::{ RegId, }; use fuel_types::{ + fmt_truncated_hex, RegisterId, Word, }; -use core::ops::Range; +use core::{ + fmt, + ops::Range, +}; #[cfg(any(test, feature = "test-helpers"))] use core::ops::{ @@ -51,13 +54,11 @@ mod allocation_tests; mod stack_tests; /// The memory of the VM, represented as stack and heap. -#[derive(Debug, Clone, PartialEq, Eq, Derivative)] +#[derive(Clone, Eq)] pub struct Memory { /// Stack. Grows upwards. - #[derivative(Debug(format_with = "fmt_truncated_hex::<16>"))] stack: Vec, /// Heap. Grows downwards from MEM_SIZE. - #[derivative(Debug(format_with = "fmt_truncated_hex::<16>"))] heap: Vec, /// Lowest allowed heap address, i.e. hp register value. /// This is needed since we can allocate extra heap for performance reasons. @@ -70,122 +71,27 @@ impl Default for Memory { } } -/// Memory access trait for the VM. -pub trait VmMemory { - /// Resets memory to initial state, keeping the original allocations. - fn reset(&mut self); - - /// Returns a linear memory representation where stack is at the beginning and heap is - /// at the end. - fn into_linear_memory(self) -> Vec; - - /// Grows the stack to be at least `new_sp` bytes. - fn grow_stack(&mut self, new_sp: Word) -> Result<(), PanicReason>; - - /// Grows the heap to be at least `new_hp` bytes. - /// Panics if the heap would be shrunk. - fn grow_heap(&mut self, sp: Reg, new_hp: Word) -> Result<(), PanicReason>; - - /// Verify that the memory range is accessble and return it as a range. - fn verify( - &self, - addr: A, - count: B, - ) -> Result; - - /// Verify a constant-sized memory range. - fn verify_const( - &self, - addr: A, - ) -> Result { - self.verify(addr, C) - } - - /// Returns a reference to memory for reading, if possible. - fn read(&self, addr: A, count: C) - -> Result<&[u8], PanicReason>; - - /// Reads a constant-sized byte array from memory, if possible. - fn read_bytes( - &self, - at: A, - ) -> Result<[u8; C], PanicReason> { - let mut result = [0; C]; - result.copy_from_slice(self.read(at, C)?); - Ok(result) +impl fmt::Debug for Memory { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "Memory {{ stack: ")?; + fmt_truncated_hex::<16>(&self.stack, f)?; + write!(f, ", heap: ")?; + let off = self.hp.saturating_sub(self.heap_offset()); + fmt_truncated_hex::<16>(&self.heap[off..], f)?; + write!(f, ", hp: {} }}", self.hp) } +} - /// Gets write access to memory, if possible. - /// Doesn't perform any ownership checks. - fn write_noownerchecks( - &mut self, - addr: A, - len: B, - ) -> Result<&mut [u8], PanicReason>; - - /// Writes a constant-sized byte array to memory, if possible. - /// Doesn't perform any ownership checks. - fn write_bytes_noownerchecks( - &mut self, - addr: A, - data: [u8; C], - ) -> Result<(), PanicReason> { - self.write_noownerchecks(addr, C)?.copy_from_slice(&data); - Ok(()) - } - - /// Checks that memory is writable and returns a mutable slice to it. - fn write( - &mut self, - owner: OwnershipRegisters, - addr: A, - len: C, - ) -> Result<&mut [u8], PanicReason> { - let range = self.verify(addr, len)?; - owner.verify_ownership(&range.words())?; - self.write_noownerchecks(range.start(), range.len()) - } - - /// Writes a constant-sized byte array to memory, checking for ownership. - fn write_bytes( - &mut self, - owner: OwnershipRegisters, - addr: A, - data: [u8; C], - ) -> Result<(), PanicReason> { - self.write(owner, addr, data.len())?.copy_from_slice(&data); - Ok(()) - } - - /// Copies the memory from `src` to `dst`. - #[inline] - #[track_caller] - fn memcopy_noownerchecks( - &mut self, - dst: A, - src: B, - len: C, - ) -> Result<(), PanicReason> { - // TODO: Optimize - - let src = src.to_addr()?; - let dst = dst.to_addr()?; - let len = len.to_addr()?; - - let tmp = self.read(src, len)?.to_vec(); - self.write_noownerchecks(dst, len)?.copy_from_slice(&tmp); - Ok(()) +impl PartialEq for Memory { + /// Equality comparison of the accessible memory. + #[allow(clippy::arithmetic_side_effects)] // Safety: hp is kept valid everywhere + fn eq(&self, other: &Self) -> bool { + self.stack == other.stack && { + let self_hs = self.hp - self.heap_offset(); + let other_hs = other.hp - other.heap_offset(); + self.heap[self_hs..] == other.heap[other_hs..] + } } - - /// Memory access to the raw stack buffer. - /// Note that for efficiency reasons this might not match sp value. - #[cfg(any(test, feature = "test-helpers"))] - fn stack_raw(&self) -> &[u8]; - - /// Memory access to the raw heap buffer. - /// Note that for efficiency reasons this might not match hp value. - #[cfg(any(test, feature = "test-helpers"))] - fn heap_raw(&self) -> &[u8]; } impl Memory { @@ -198,22 +104,20 @@ impl Memory { } } - /// Offset of the heap section - fn heap_offset(&self) -> usize { - MEM_SIZE.saturating_sub(self.heap.len()) - } -} - -impl VmMemory for Memory { /// Resets memory to initial state, keeping the original allocations. - fn reset(&mut self) { + pub fn reset(&mut self) { self.stack.truncate(0); self.hp = MEM_SIZE; } + /// Offset of the heap section + fn heap_offset(&self) -> usize { + MEM_SIZE.saturating_sub(self.heap.len()) + } + /// Returns a linear memory representation where stack is at the beginning and heap is /// at the end. - fn into_linear_memory(self) -> Vec { + pub fn into_linear_memory(self) -> Vec { let uninit_memory_size = MEM_SIZE .saturating_sub(self.stack.len()) .saturating_sub(self.heap.len()); @@ -225,7 +129,7 @@ impl VmMemory for Memory { } /// Grows the stack to be at least `new_sp` bytes. - fn grow_stack(&mut self, new_sp: Word) -> Result<(), PanicReason> { + pub fn grow_stack(&mut self, new_sp: Word) -> Result<(), PanicReason> { #[allow(clippy::cast_possible_truncation)] // Safety: MEM_SIZE is usize let new_sp = new_sp.min(MEM_SIZE as Word) as usize; if new_sp > self.stack.len() { @@ -240,7 +144,7 @@ impl VmMemory for Memory { /// Grows the heap to be at least `new_hp` bytes. /// Panics if the heap would be shrunk. - fn grow_heap(&mut self, sp: Reg, new_hp: Word) -> Result<(), PanicReason> { + pub fn grow_heap(&mut self, sp: Reg, new_hp: Word) -> Result<(), PanicReason> { let new_hp_word = new_hp.min(MEM_SIZE as Word); #[allow(clippy::cast_possible_truncation)] // Safety: MEM_SIZE is usize let new_hp = new_hp_word as usize; @@ -281,7 +185,7 @@ impl VmMemory for Memory { } /// Verify that the memory range is accessble and return it as a range. - fn verify( + pub fn verify( &self, addr: A, count: B, @@ -300,9 +204,17 @@ impl VmMemory for Memory { } } + /// Verify a constant-sized memory range. + pub fn verify_const( + &self, + addr: A, + ) -> Result { + self.verify(addr, C) + } + /// Returns a reference to memory for reading, if possible. #[allow(clippy::arithmetic_side_effects)] // Safety: subtractions are checked - fn read( + pub fn read( &self, addr: A, count: C, @@ -320,10 +232,20 @@ impl VmMemory for Memory { } } + /// Reads a constant-sized byte array from memory, if possible. + pub fn read_bytes( + &self, + at: A, + ) -> Result<[u8; C], PanicReason> { + let mut result = [0; C]; + result.copy_from_slice(self.read(at, C)?); + Ok(result) + } + /// Gets write access to memory, if possible. /// Doesn't perform any ownership checks. #[allow(clippy::arithmetic_side_effects)] // Safety: subtractions are checked - fn write_noownerchecks( + pub fn write_noownerchecks( &mut self, addr: A, len: B, @@ -340,17 +262,71 @@ impl VmMemory for Memory { } } + /// Writes a constant-sized byte array to memory, if possible. + /// Doesn't perform any ownership checks. + pub fn write_bytes_noownerchecks( + &mut self, + addr: A, + data: [u8; C], + ) -> Result<(), PanicReason> { + self.write_noownerchecks(addr, C)?.copy_from_slice(&data); + Ok(()) + } + + /// Checks that memory is writable and returns a mutable slice to it. + pub fn write( + &mut self, + owner: OwnershipRegisters, + addr: A, + len: C, + ) -> Result<&mut [u8], PanicReason> { + let range = self.verify(addr, len)?; + owner.verify_ownership(&range.words())?; + self.write_noownerchecks(range.start(), range.len()) + } + + /// Writes a constant-sized byte array to memory, checking for ownership. + pub fn write_bytes( + &mut self, + owner: OwnershipRegisters, + addr: A, + data: [u8; C], + ) -> Result<(), PanicReason> { + self.write(owner, addr, data.len())?.copy_from_slice(&data); + Ok(()) + } + + /// Copies the memory from `src` to `dst`. + #[inline] + #[track_caller] + pub fn memcopy_noownerchecks( + &mut self, + dst: A, + src: B, + len: C, + ) -> Result<(), PanicReason> { + // TODO: Optimize + + let src = src.to_addr()?; + let dst = dst.to_addr()?; + let len = len.to_addr()?; + + let tmp = self.read(src, len)?.to_vec(); + self.write_noownerchecks(dst, len)?.copy_from_slice(&tmp); + Ok(()) + } + /// Memory access to the raw stack buffer. /// Note that for efficiency reasons this might not match sp value. #[cfg(any(test, feature = "test-helpers"))] - fn stack_raw(&self) -> &[u8] { + pub fn stack_raw(&self) -> &[u8] { &self.stack } /// Memory access to the raw heap buffer. /// Note that for efficiency reasons this might not match hp value. #[cfg(any(test, feature = "test-helpers"))] - fn heap_raw(&self) -> &[u8] { + pub fn heap_raw(&self) -> &[u8] { &self.heap } } @@ -487,7 +463,7 @@ impl MemoryRange { } } -impl Interpreter { +impl<'a, S, Tx, Ecal> Interpreter<'a, S, Tx, Ecal> { /// Return the registers used to determine ownership. pub(crate) fn ownership_registers(&self) -> OwnershipRegisters { OwnershipRegisters::new(self) @@ -503,7 +479,15 @@ impl Interpreter { }, _, ) = split_registers(&mut self.registers); - stack_pointer_overflow(sp, ssp.as_ref(), hp.as_ref(), pc, f, v, &mut self.memory) + stack_pointer_overflow( + sp, + ssp.as_ref(), + hp.as_ref(), + pc, + f, + v, + self.memory.as_mut(), + ) } pub(crate) fn push_selected_registers( @@ -518,7 +502,7 @@ impl Interpreter { program_regs, ) = split_registers(&mut self.registers); push_selected_registers( - &mut self.memory, + self.memory.as_mut(), sp, ssp.as_ref(), hp.as_ref(), @@ -541,7 +525,7 @@ impl Interpreter { mut program_regs, ) = split_registers(&mut self.registers); pop_selected_registers( - &mut self.memory, + self.memory.as_mut(), sp, ssp.as_ref(), hp.as_ref(), @@ -560,7 +544,7 @@ impl Interpreter { ) -> SimpleResult<()> { let (SystemRegisters { pc, .. }, mut w) = split_registers(&mut self.registers); let result = &mut w[WriteRegKey::try_from(ra)?]; - load_byte(&self.memory, pc, result, b, c) + load_byte(self.memory.as_ref(), pc, result, b, c) } pub(crate) fn load_word( @@ -571,39 +555,60 @@ impl Interpreter { ) -> SimpleResult<()> { let (SystemRegisters { pc, .. }, mut w) = split_registers(&mut self.registers); let result = &mut w[WriteRegKey::try_from(ra)?]; - load_word(&self.memory, pc, result, b, c) + load_word(self.memory.as_ref(), pc, result, b, c) } pub(crate) fn store_byte(&mut self, a: Word, b: Word, c: Word) -> SimpleResult<()> { let owner = self.ownership_registers(); - store_byte(&mut self.memory, owner, self.registers.pc_mut(), a, b, c) + store_byte( + self.memory.as_mut(), + owner, + self.registers.pc_mut(), + a, + b, + c, + ) } pub(crate) fn store_word(&mut self, a: Word, b: Word, c: Imm12) -> SimpleResult<()> { let owner = self.ownership_registers(); - store_word(&mut self.memory, owner, self.registers.pc_mut(), a, b, c) + store_word( + self.memory.as_mut(), + owner, + self.registers.pc_mut(), + a, + b, + c, + ) } /// Expand heap by `a` bytes. pub fn allocate(&mut self, a: Word) -> SimpleResult<()> { let (SystemRegisters { hp, sp, .. }, _) = split_registers(&mut self.registers); - try_allocate(hp, sp.as_ref(), a, &mut self.memory) + try_allocate(hp, sp.as_ref(), a, self.memory.as_mut()) } pub(crate) fn malloc(&mut self, a: Word) -> SimpleResult<()> { let (SystemRegisters { hp, sp, pc, .. }, _) = split_registers(&mut self.registers); - malloc(hp, sp.as_ref(), pc, a, &mut self.memory) + malloc(hp, sp.as_ref(), pc, a, self.memory.as_mut()) } pub(crate) fn memclear(&mut self, a: Word, b: Word) -> SimpleResult<()> { let owner = self.ownership_registers(); - memclear(&mut self.memory, owner, self.registers.pc_mut(), a, b) + memclear(self.memory.as_mut(), owner, self.registers.pc_mut(), a, b) } pub(crate) fn memcopy(&mut self, a: Word, b: Word, c: Word) -> SimpleResult<()> { let owner = self.ownership_registers(); - memcopy(&mut self.memory, owner, self.registers.pc_mut(), a, b, c) + memcopy( + self.memory.as_mut(), + owner, + self.registers.pc_mut(), + a, + b, + c, + ) } pub(crate) fn memeq( @@ -615,7 +620,7 @@ impl Interpreter { ) -> SimpleResult<()> { let (SystemRegisters { pc, .. }, mut w) = split_registers(&mut self.registers); let result = &mut w[WriteRegKey::try_from(ra)?]; - memeq(&mut self.memory, result, pc, b, c, d) + memeq(self.memory.as_mut(), result, pc, b, c, d) } } diff --git a/fuel-vm/src/interpreter/memory/impl_tests.rs b/fuel-vm/src/interpreter/memory/impl_tests.rs index 42d88e57df..eab5b366d4 100644 --- a/fuel-vm/src/interpreter/memory/impl_tests.rs +++ b/fuel-vm/src/interpreter/memory/impl_tests.rs @@ -3,7 +3,6 @@ use crate::{ constraints::reg_key::*, consts::MEM_SIZE, - interpreter::VmMemory, }; use super::{ diff --git a/fuel-vm/src/interpreter/memory/tests.rs b/fuel-vm/src/interpreter/memory/tests.rs index 18b7237e35..0cf9df6044 100644 --- a/fuel-vm/src/interpreter/memory/tests.rs +++ b/fuel-vm/src/interpreter/memory/tests.rs @@ -24,7 +24,8 @@ fn memcopy() { let mut consensus_params = ConsensusParameters::default(); consensus_params.set_tx_params(tx_params); - let mut vm = Interpreter::<_, _>::with_storage( + let mut vm = Interpreter::<'_, _, _>::with_storage( + test_pool().get_new().into(), MemoryStorage::default(), InterpreterParams::new(zero_gas_price, &consensus_params), ); @@ -93,7 +94,7 @@ fn memcopy() { #[test] fn stack_alloc_ownership() { - let mut vm = Interpreter::<_, _>::with_memory_storage(); + let mut vm = Interpreter::<'_, _, _>::with_memory_storage(); let gas_price = 0; let consensus_params = ConsensusParameters::standard(); diff --git a/fuel-vm/src/interpreter/metadata.rs b/fuel-vm/src/interpreter/metadata.rs index 1998576f49..88fae34fcc 100644 --- a/fuel-vm/src/interpreter/metadata.rs +++ b/fuel-vm/src/interpreter/metadata.rs @@ -2,7 +2,6 @@ use super::{ internal::inc_pc, ExecutableTransaction, Interpreter, - VmMemory, }; use crate::{ call::CallFrame, @@ -46,7 +45,7 @@ use fuel_types::{ #[cfg(test)] mod tests; -impl Interpreter +impl<'a, S, Tx, Ecal> Interpreter<'a, S, Tx, Ecal> where Tx: ExecutableTransaction, { @@ -80,7 +79,7 @@ where // Tx size is stored just below the tx bytes let tx_size_ptr = tx_offset.checked_sub(8).expect("Tx offset is not valid"); let tx_size = Word::from_be_bytes( - self.memory + self.memory() .read_bytes(tx_size_ptr) .expect("Tx length not in memory"), ); diff --git a/fuel-vm/src/interpreter/owned_or_mut.rs b/fuel-vm/src/interpreter/owned_or_mut.rs new file mode 100644 index 0000000000..59aba0a607 --- /dev/null +++ b/fuel-vm/src/interpreter/owned_or_mut.rs @@ -0,0 +1,46 @@ +/// Owned or mutable reference. +#[derive(Debug)] +pub enum OwnedOrMut<'a, T> { + /// Owned value. + Owned(T), + /// Mutable reference. + Borrowed(&'a mut T), +} + +#[cfg(any(test, featutre = "test-helpers"))] +impl Clone for OwnedOrMut<'_, T> { + fn clone(&self) -> Self { + OwnedOrMut::Owned(match self { + OwnedOrMut::Owned(m) => m.clone(), + OwnedOrMut::Borrowed(m) => (**m).clone(), + }) + } +} + +impl From for OwnedOrMut<'_, T> { + fn from(t: T) -> Self { + OwnedOrMut::Owned(t) + } +} +impl Default for OwnedOrMut<'_, T> { + fn default() -> Self { + OwnedOrMut::Owned(T::default()) + } +} +impl AsRef for OwnedOrMut<'_, T> { + fn as_ref(&self) -> &T { + match self { + OwnedOrMut::Owned(m) => m, + OwnedOrMut::Borrowed(m) => m, + } + } +} + +impl AsMut for OwnedOrMut<'_, T> { + fn as_mut(&mut self) -> &mut T { + match self { + OwnedOrMut::Owned(m) => m, + OwnedOrMut::Borrowed(m) => m, + } + } +} diff --git a/fuel-vm/src/interpreter/post_execution.rs b/fuel-vm/src/interpreter/post_execution.rs index d1c5e5c08b..23db58804a 100644 --- a/fuel-vm/src/interpreter/post_execution.rs +++ b/fuel-vm/src/interpreter/post_execution.rs @@ -20,7 +20,7 @@ use fuel_types::{ Word, }; -impl Interpreter +impl<'a, S, T, Ecal> Interpreter<'a, S, T, Ecal> where S: InterpreterStorage, { diff --git a/fuel-vm/src/lib.rs b/fuel-vm/src/lib.rs index 0759c7476b..a6a57b2937 100644 --- a/fuel-vm/src/lib.rs +++ b/fuel-vm/src/lib.rs @@ -32,6 +32,7 @@ mod convert; pub mod crypto; pub mod error; pub mod interpreter; +#[cfg(any(test, feature = "test-helpers"))] pub mod memory_client; pub mod pool; pub mod predicate; diff --git a/fuel-vm/src/memory_client.rs b/fuel-vm/src/memory_client.rs index 30120fb952..efeec66670 100644 --- a/fuel-vm/src/memory_client.rs +++ b/fuel-vm/src/memory_client.rs @@ -7,8 +7,11 @@ use crate::{ interpreter::{ EcalHandler, InterpreterParams, + Memory, NotSupportedEcal, + OwnedOrMut, }, + pool::test_pool, state::StateTransitionRef, storage::MemoryStorage, transactor::Transactor, @@ -26,41 +29,49 @@ use fuel_tx::{ #[derive(Debug)] /// Client implementation with in-memory storage backend. -pub struct MemoryClient { - transactor: Transactor, +pub struct MemoryClient<'a, Ecal = NotSupportedEcal> { + transactor: Transactor<'a, MemoryStorage, Script, Ecal>, } -#[cfg(any(test, feature = "test-helpers"))] -impl Default for MemoryClient { +impl<'a> Default for MemoryClient<'a> { fn default() -> Self { - Self::new(MemoryStorage::default(), InterpreterParams::default()) + Self::from_txtor(Transactor::new( + test_pool().get_new().into(), + MemoryStorage::default(), + InterpreterParams::default(), + )) } } -impl AsRef for MemoryClient { +impl<'a, Ecal: EcalHandler> AsRef for MemoryClient<'a, Ecal> { fn as_ref(&self) -> &MemoryStorage { self.transactor.as_ref() } } -impl AsMut for MemoryClient { +impl<'a, Ecal: EcalHandler> AsMut for MemoryClient<'a, Ecal> { fn as_mut(&mut self) -> &mut MemoryStorage { self.transactor.as_mut() } } -impl MemoryClient { +impl<'a, Ecal: EcalHandler + Default> MemoryClient<'a, Ecal> { /// Create a new instance of the memory client out of a provided storage. - pub fn new(storage: MemoryStorage, interpreter_params: InterpreterParams) -> Self { + pub fn new( + memory: OwnedOrMut<'a, Memory>, + + storage: MemoryStorage, + interpreter_params: InterpreterParams, + ) -> Self { Self { - transactor: Transactor::new(storage, interpreter_params), + transactor: Transactor::new(memory, storage, interpreter_params), } } } -impl MemoryClient { +impl<'a, Ecal: EcalHandler> MemoryClient<'a, Ecal> { /// Create a new instance of the memory client out of a provided storage. - pub fn from_txtor(transactor: Transactor) -> Self { + pub fn from_txtor(transactor: Transactor<'a, MemoryStorage, Script, Ecal>) -> Self { Self { transactor } } @@ -154,17 +165,20 @@ impl MemoryClient { } } -#[cfg(feature = "test-helpers")] -impl From for MemoryClient { +impl<'a, Ecal: EcalHandler + Default> From for MemoryClient<'a, Ecal> { fn from(s: MemoryStorage) -> Self { - Self::new(s, InterpreterParams::default()) + Self::from_txtor(Transactor::new( + test_pool().get_new().into(), + s, + InterpreterParams::default(), + )) } } -impl From> - for Transactor +impl<'a, Ecal: EcalHandler> From> + for Transactor<'a, MemoryStorage, Script, Ecal> { - fn from(client: MemoryClient) -> Self { + fn from(client: MemoryClient<'a, Ecal>) -> Self { client.transactor } } diff --git a/fuel-vm/src/pool.rs b/fuel-vm/src/pool.rs index eac9f4d4b8..0ad407ab3d 100644 --- a/fuel-vm/src/pool.rs +++ b/fuel-vm/src/pool.rs @@ -6,10 +6,7 @@ use alloc::{ }; use core::fmt; -use crate::interpreter::{ - Memory, - VmMemory, -}; +use crate::interpreter::Memory; /// Pool of VM memory instances for reuse. #[derive(Default, Clone)] diff --git a/fuel-vm/src/predicate.rs b/fuel-vm/src/predicate.rs index bc8c4591a8..8d1a6269fc 100644 --- a/fuel-vm/src/predicate.rs +++ b/fuel-vm/src/predicate.rs @@ -146,7 +146,8 @@ mod tests { assert_eq!(idx, runtime.idx()); - let mut interpreter = Interpreter::<_, _>::with_storage( + let mut interpreter = Interpreter::<'_, _, _>::with_storage( + test_pool().get_new().into(), PredicateStorage, InterpreterParams::default(), ); diff --git a/fuel-vm/src/tests/blockchain.rs b/fuel-vm/src/tests/blockchain.rs index 9160aba94e..ffce1a0ebb 100644 --- a/fuel-vm/src/tests/blockchain.rs +++ b/fuel-vm/src/tests/blockchain.rs @@ -708,10 +708,11 @@ fn ldc_reason_helper(cmd: Vec, expected_reason: PanicReason) { let interpreter_params = InterpreterParams::new(gas_price, &consensus_params); - let mut client = MemoryClient::::new( + let mut client = MemoryClient::<'_, NotSupportedEcal>::from_txtor(Transactor::new( + test_pool().get_new().into(), MemoryStorage::default(), interpreter_params, - ); + )); let gas_limit = 1_000_000; let maturity = Default::default(); diff --git a/fuel-vm/src/tests/code_coverage.rs b/fuel-vm/src/tests/code_coverage.rs index 925a6ff445..4e0198dc8e 100644 --- a/fuel-vm/src/tests/code_coverage.rs +++ b/fuel-vm/src/tests/code_coverage.rs @@ -76,12 +76,9 @@ fn code_coverage() { let output = ProfilingOutput::default(); - let mut client = MemoryClient::from_txtor( - Interpreter::<_, _>::with_memory_storage() - .with_profiler(output.clone()) - .build() - .into(), - ); + let mut vm = Interpreter::<'_, _, _>::with_memory_storage(); + vm.with_profiler(output.clone()); + let mut client = MemoryClient::from_txtor(vm.into()); let receipts = client.transact(tx_script); diff --git a/fuel-vm/src/tests/external.rs b/fuel-vm/src/tests/external.rs index ca338dfe60..5a4c28902c 100644 --- a/fuel-vm/src/tests/external.rs +++ b/fuel-vm/src/tests/external.rs @@ -45,6 +45,7 @@ fn noop_ecal() { .collect(); let mut client = MemoryClient::::new( + test_pool().get_new().into(), fuel_vm::prelude::MemoryStorage::default(), Default::default(), ); diff --git a/fuel-vm/src/tests/gas_factor.rs b/fuel-vm/src/tests/gas_factor.rs index 0d2faa3aeb..0d5bf34f61 100644 --- a/fuel-vm/src/tests/gas_factor.rs +++ b/fuel-vm/src/tests/gas_factor.rs @@ -2,6 +2,7 @@ use crate::{ interpreter::InterpreterParams, + pool::test_pool, prelude::*, }; use core::iter; @@ -56,7 +57,11 @@ fn gas_factor_rounds_correctly() { let interpreter_params = InterpreterParams::new(gas_price, &consensus_params); let storage = MemoryStorage::default(); - let mut interpreter = Interpreter::<_, _>::with_storage(storage, interpreter_params); + let mut interpreter = Interpreter::<'_, _, _>::with_storage( + test_pool().get_new().into(), + storage, + interpreter_params, + ); let gas_costs = interpreter.gas_costs().clone(); let res = interpreter .with_profiler(profiler.clone()) diff --git a/fuel-vm/src/tests/memory.rs b/fuel-vm/src/tests/memory.rs index ca4e830f4f..83daa3e91b 100644 --- a/fuel-vm/src/tests/memory.rs +++ b/fuel-vm/src/tests/memory.rs @@ -22,7 +22,7 @@ use super::test_helpers::{ }; use fuel_tx::ConsensusParameters; -fn setup(program: Vec) -> Transactor { +fn setup<'a>(program: Vec) -> Transactor<'a, MemoryStorage, Script> { let storage = MemoryStorage::default(); let gas_price = 0; @@ -44,7 +44,8 @@ fn setup(program: Vec) -> Transactor { let interpreter_params = InterpreterParams::new(gas_price, &consensus_params); - let mut vm = Transactor::new(storage, interpreter_params); + let mut vm = + Transactor::new(test_pool().get_new().into(), storage, interpreter_params); vm.transact(tx); vm } diff --git a/fuel-vm/src/tests/metadata.rs b/fuel-vm/src/tests/metadata.rs index 211b1e2ea6..6ab5a57c86 100644 --- a/fuel-vm/src/tests/metadata.rs +++ b/fuel-vm/src/tests/metadata.rs @@ -97,11 +97,13 @@ fn metadata() { let interpreter_params = InterpreterParams::new(gas_price, &consensus_params); // Deploy the contract into the blockchain - assert!( - Transactor::<_, _>::new(&mut storage, interpreter_params.clone()) - .transact(tx) - .is_success() - ); + assert!(Transactor::<'_, _, _>::new( + test_pool().get_new().into(), + &mut storage, + interpreter_params.clone() + ) + .transact(tx) + .is_success()); let mut routine_call_metadata_contract = vec![ op::gm_args(0x10, GMArgs::IsCallerExternal), @@ -144,11 +146,13 @@ fn metadata() { .into_checked(height, &consensus_params, test_pool()) .expect("failed to check tx"); - assert!( - Transactor::<_, _>::new(&mut storage, interpreter_params.clone()) - .transact(tx) - .is_success() - ); + assert!(Transactor::<'_, _, _>::new( + test_pool().get_new().into(), + &mut storage, + interpreter_params.clone() + ) + .transact(tx) + .is_success()); let mut inputs = vec![]; let mut outputs = vec![]; @@ -205,11 +209,15 @@ fn metadata() { .into_checked(height, &consensus_params, test_pool()) .expect("failed to check tx"); - let receipts = Transactor::<_, _>::new(&mut storage, interpreter_params) - .transact(tx) - .receipts() - .expect("Failed to transact") - .to_owned(); + let receipts = Transactor::<'_, _, _>::new( + test_pool().get_new().into(), + &mut storage, + interpreter_params, + ) + .transact(tx) + .receipts() + .expect("Failed to transact") + .to_owned(); let ra = receipts[1] .ra() @@ -241,8 +249,11 @@ fn get_metadata_chain_id() { ..Default::default() }; - let mut client = - MemoryClient::::new(Default::default(), interpreter_params); + let mut client = MemoryClient::<'_, NotSupportedEcal>::new( + test_pool().get_new().into(), + Default::default(), + interpreter_params, + ); #[rustfmt::skip] let get_chain_id = vec![ @@ -295,7 +306,8 @@ fn get_metadata_base_asset_id() { .into_checked(height, ¶ms, test_pool()) .unwrap(); - let receipts = Transactor::<_, _>::new( + let receipts = Transactor::<'_, _, _>::new( + test_pool().get_new().into(), &mut storage, InterpreterParams { base_asset_id: *params.base_asset_id(), @@ -332,11 +344,15 @@ fn get_metadata_tx_start() { .into_checked(height, &ConsensusParameters::default(), test_pool()) .unwrap(); - let receipts = Transactor::<_, _>::new(&mut storage, InterpreterParams::default()) - .transact(script) - .receipts() - .expect("Failed to transact") - .to_owned(); + let receipts = Transactor::<'_, _, _>::new( + test_pool().get_new().into(), + &mut storage, + InterpreterParams::default(), + ) + .transact(script) + .receipts() + .expect("Failed to transact") + .to_owned(); if let Receipt::Return { val, .. } = receipts[0].clone() { assert_eq!(val, TxParameters::DEFAULT.tx_offset() as Word); diff --git a/fuel-vm/src/tests/profile_gas.rs b/fuel-vm/src/tests/profile_gas.rs index a606a7f840..e3303106b6 100644 --- a/fuel-vm/src/tests/profile_gas.rs +++ b/fuel-vm/src/tests/profile_gas.rs @@ -53,12 +53,9 @@ fn profile_gas() { let output = GasProfiler::default(); - let mut client = MemoryClient::from_txtor( - Interpreter::<_, _>::with_memory_storage() - .with_profiler(output.clone()) - .build() - .into(), - ); + let mut vm = Interpreter::<'_, _, _>::with_memory_storage(); + vm.with_profiler(output.clone()); + let mut client = MemoryClient::from_txtor(vm.into()); let receipts = client.transact(tx_deploy); diff --git a/fuel-vm/src/tests/upgrade.rs b/fuel-vm/src/tests/upgrade.rs index 50569cb8a3..098bf21c1e 100644 --- a/fuel-vm/src/tests/upgrade.rs +++ b/fuel-vm/src/tests/upgrade.rs @@ -34,7 +34,10 @@ use alloc::{ mod state_transition { use super::*; - use crate::storage::UploadedBytecode; + use crate::{ + pool::test_pool, + storage::UploadedBytecode, + }; use fuel_tx::UpgradePurpose; use fuel_types::Bytes32; @@ -87,6 +90,7 @@ mod state_transition { fn transact_updates_state_transition_version() { let state_transition_hash = [1; 32].into(); let mut client = Interpreter::<_, Upgrade>::with_storage( + test_pool().get_new().into(), valid_storage(state_transition_hash, vec![]), InterpreterParams::default(), ); @@ -113,6 +117,7 @@ mod state_transition { fn transact_with_zero_gas_price_doesnt_affect_change_output() { let state_transition_hash = [1; 32].into(); let mut client = Interpreter::<_, Upgrade>::with_storage( + test_pool().get_new().into(), valid_storage(state_transition_hash, vec![]), InterpreterParams::default(), ); @@ -142,6 +147,7 @@ mod state_transition { fn transact_with_non_zero_gas_price_affects_change_output() { let state_transition_hash = [1; 32].into(); let mut client = Interpreter::<_, Upgrade>::with_storage( + test_pool().get_new().into(), valid_storage(state_transition_hash, vec![]), InterpreterParams::default(), ); @@ -171,6 +177,7 @@ mod state_transition { fn transact_fails_for_unknown_root() { let known_state_transition_hash = [1; 32].into(); let mut client = Interpreter::<_, Upgrade>::with_storage( + test_pool().get_new().into(), valid_storage(known_state_transition_hash, vec![]), InterpreterParams::default(), ); @@ -206,6 +213,7 @@ mod state_transition { }, ); let mut client = Interpreter::<_, Upgrade>::with_storage( + test_pool().get_new().into(), storage, InterpreterParams::default(), ); @@ -226,6 +234,7 @@ mod state_transition { fn transact_fails_when_try_to_override_state_bytecode() { let state_transition_hash = [1; 32].into(); let mut client = Interpreter::<_, Upgrade>::with_storage( + test_pool().get_new().into(), valid_storage(state_transition_hash, vec![]), InterpreterParams::default(), ); @@ -251,6 +260,8 @@ mod state_transition { } mod consensus_parameters { + use crate::pool::test_pool; + use super::*; const CURRENT_CONSENSUS_PARAMETERS_VERSION: u32 = 123; @@ -299,6 +310,7 @@ mod consensus_parameters { #[test] fn transact_updates_consensus_parameters_version() { let mut client = Interpreter::<_, Upgrade>::with_storage( + test_pool().get_new().into(), valid_storage(), InterpreterParams::default(), ); @@ -324,6 +336,7 @@ mod consensus_parameters { #[test] fn transact_with_zero_gas_price_doesnt_affect_change_output() { let mut client = Interpreter::<_, Upgrade>::with_storage( + test_pool().get_new().into(), valid_storage(), InterpreterParams::default(), ); @@ -352,6 +365,7 @@ mod consensus_parameters { #[test] fn transact_with_non_zero_gas_price_affects_change_output() { let mut client = Interpreter::<_, Upgrade>::with_storage( + test_pool().get_new().into(), valid_storage(), InterpreterParams::default(), ); @@ -380,6 +394,7 @@ mod consensus_parameters { #[test] fn transact_fails_when_try_to_override_consensus_parameters() { let mut client = Interpreter::<_, Upgrade>::with_storage( + test_pool().get_new().into(), valid_storage(), InterpreterParams::default(), ); diff --git a/fuel-vm/src/transactor.rs b/fuel-vm/src/transactor.rs index bbcd4c9810..3fff94ad8d 100644 --- a/fuel-vm/src/transactor.rs +++ b/fuel-vm/src/transactor.rs @@ -12,7 +12,10 @@ use crate::{ EcalHandler, ExecutableTransaction, Interpreter, + Memory, + OwnedOrMut, }, + pool::test_pool, state::{ ProgramState, StateTransition, @@ -46,25 +49,30 @@ use fuel_tx::{ /// builder`. /// /// Based on -pub struct Transactor +pub struct Transactor<'a, S, Tx, Ecal = NotSupportedEcal> where S: InterpreterStorage, { - interpreter: Interpreter, + interpreter: Interpreter<'a, S, Tx, Ecal>, program_state: Option, error: Option>, } -impl Transactor +impl<'a, S, Tx, Ecal> Transactor<'a, S, Tx, Ecal> where S: InterpreterStorage, Tx: ExecutableTransaction, Ecal: EcalHandler + Default, { /// Transactor constructor - pub fn new(storage: S, interpreter_params: InterpreterParams) -> Self { + pub fn new( + memory: OwnedOrMut<'a, Memory>, + storage: S, + interpreter_params: InterpreterParams, + ) -> Self { Self { - interpreter: Interpreter::::with_storage( + interpreter: Interpreter::<'a, S, Tx, Ecal>::with_storage( + memory, storage, interpreter_params, ), @@ -73,7 +81,7 @@ where } } } -impl<'a, S, Tx, Ecal> Transactor +impl<'a, S, Tx, Ecal> Transactor<'a, S, Tx, Ecal> where S: InterpreterStorage, Tx: ExecutableTransaction, @@ -148,7 +156,7 @@ where } /// Gets the interpreter. - pub fn interpreter(&self) -> &Interpreter { + pub fn interpreter(&self) -> &Interpreter<'a, S, Tx, Ecal> { &self.interpreter } @@ -174,7 +182,7 @@ where } } -impl Transactor +impl<'a, S, Ecal> Transactor<'a, S, Script, Ecal> where S: InterpreterStorage, { @@ -198,7 +206,7 @@ where } } -impl Transactor +impl<'a, S, Tx, Ecal> Transactor<'a, S, Tx, Ecal> where S: InterpreterStorage, { @@ -275,7 +283,7 @@ where } } -impl Transactor +impl<'a, S, Tx, Ecal> Transactor<'a, S, Tx, Ecal> where S: InterpreterStorage, Tx: ExecutableTransaction, @@ -317,12 +325,12 @@ where } } -impl From> for Transactor +impl<'a, S, Tx, Ecal> From> for Transactor<'a, S, Tx, Ecal> where Tx: ExecutableTransaction, S: InterpreterStorage, { - fn from(interpreter: Interpreter) -> Self { + fn from(interpreter: Interpreter<'a, S, Tx, Ecal>) -> Self { let program_state = None; let error = None; @@ -334,28 +342,28 @@ where } } -impl From> for Interpreter +impl<'a, S, Tx, Ecal> From> for Interpreter<'a, S, Tx, Ecal> where Tx: ExecutableTransaction, S: InterpreterStorage, { - fn from(transactor: Transactor) -> Self { + fn from(transactor: Transactor<'a, S, Tx, Ecal>) -> Self { transactor.interpreter } } -impl AsRef> for Transactor +impl<'a, S, Tx, Ecal> AsRef> for Transactor<'a, S, Tx, Ecal> where Tx: ExecutableTransaction, S: InterpreterStorage, Ecal: EcalHandler, { - fn as_ref(&self) -> &Interpreter { + fn as_ref(&self) -> &Interpreter<'a, S, Tx, Ecal> { &self.interpreter } } -impl AsRef for Transactor +impl<'a, S, Tx, Ecal> AsRef for Transactor<'a, S, Tx, Ecal> where Tx: ExecutableTransaction, S: InterpreterStorage, @@ -365,7 +373,7 @@ where } } -impl AsMut for Transactor +impl<'a, S, Tx, Ecal> AsMut for Transactor<'a, S, Tx, Ecal> where Tx: ExecutableTransaction, S: InterpreterStorage, @@ -376,13 +384,17 @@ where } #[cfg(feature = "test-helpers")] -impl Default for Transactor +impl<'a, S, Tx, Ecal> Default for Transactor<'a, S, Tx, Ecal> where S: InterpreterStorage + Default, Tx: ExecutableTransaction, Ecal: EcalHandler + Default, { fn default() -> Self { - Self::new(S::default(), InterpreterParams::default()) + Self::new( + test_pool().get_new().into(), + S::default(), + InterpreterParams::default(), + ) } } diff --git a/fuel-vm/src/util.rs b/fuel-vm/src/util.rs index 7b3ce4aed7..f008964bcb 100644 --- a/fuel-vm/src/util.rs +++ b/fuel-vm/src/util.rs @@ -94,7 +94,6 @@ pub mod test_helpers { Checked, IntoChecked, }, - interpreter::VmMemory, memory_client::MemoryClient, pool::test_pool, state::StateTransition, @@ -526,8 +525,11 @@ pub mod test_helpers { ) -> anyhow::Result> { let interpreter_params = InterpreterParams::new(self.gas_price, &self.consensus_params); - let mut transactor = - Transactor::<_, _>::new(self.storage.clone(), interpreter_params); + let mut transactor = Transactor::<'_, _, _>::new( + test_pool().get_new().into(), + self.storage.clone(), + interpreter_params, + ); self.execute_tx_inner(&mut transactor, checked) } @@ -538,8 +540,11 @@ pub mod test_helpers { ) -> anyhow::Result> { let interpreter_params = InterpreterParams::new(self.gas_price, &self.consensus_params); - let mut transactor = - Transactor::<_, _>::new(self.storage.clone(), interpreter_params); + let mut transactor = Transactor::<'_, _, _>::new( + test_pool().get_new().into(), + self.storage.clone(), + interpreter_params, + ); self.execute_tx_inner(&mut transactor, checked) } @@ -551,8 +556,11 @@ pub mod test_helpers { ) -> anyhow::Result<(StateTransition