diff --git a/CHANGELOG.md b/CHANGELOG.md index 5403b56ede..31a20bf8b0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,12 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ## [Unreleased] +### Changed +#### Breaking + +- [#1632](https://github.com/FuelLabs/fuel-core/pull/1632): Removed `ContractsInfo` table. Contract salts and roots are no longer stored in on-chain data. +- [#1632](https://github.com/FuelLabs/fuel-core/pull/1632): Opcode `CROO` now calculates the given contract's root on demand. `CROO` has therefore been changed to a `DependentCost` gas cost. + ## [Version 0.45.0] ### Changed diff --git a/fuel-tx/src/transaction/consensus_parameters/gas.rs b/fuel-tx/src/transaction/consensus_parameters/gas.rs index 7ebb610af5..0238a4985a 100644 --- a/fuel-tx/src/transaction/consensus_parameters/gas.rs +++ b/fuel-tx/src/transaction/consensus_parameters/gas.rs @@ -216,7 +216,6 @@ pub struct GasCostsValues { pub cb: Word, pub cfei: Word, pub cfsi: Word, - pub croo: Word, pub div: Word, pub divi: Word, pub eck1: Word, @@ -301,6 +300,7 @@ pub struct GasCostsValues { // Dependent pub call: DependentCost, pub ccp: DependentCost, + pub croo: DependentCost, pub csiz: DependentCost, pub k256: DependentCost, pub ldc: DependentCost, @@ -380,7 +380,6 @@ impl GasCostsValues { cb: 0, cfei: 0, cfsi: 0, - croo: 0, div: 0, divi: 0, eck1: 0, @@ -459,6 +458,7 @@ impl GasCostsValues { xori: 0, call: DependentCost::free(), ccp: DependentCost::free(), + croo: DependentCost::free(), csiz: DependentCost::free(), k256: DependentCost::free(), ldc: DependentCost::free(), @@ -498,7 +498,6 @@ impl GasCostsValues { cb: 1, cfei: 1, cfsi: 1, - croo: 1, div: 1, divi: 1, eck1: 1, @@ -577,6 +576,7 @@ impl GasCostsValues { xori: 1, call: DependentCost::unit(), ccp: DependentCost::unit(), + croo: DependentCost::unit(), csiz: DependentCost::unit(), k256: DependentCost::unit(), ldc: DependentCost::unit(), @@ -657,13 +657,17 @@ impl DependentCost { pub fn resolve_without_base(&self, units: Word) -> Word { match self { DependentCost::LightOperation { units_per_gas, .. } => { - // Apply the linear transformation f(x) = 1/m * x = x/m = where: + // Apply the linear transformation: + // f(x) = 1/m * x = x/m + // where: // x is the number of units // 1/m is the gas_per_unit units.saturating_div(*units_per_gas) } DependentCost::HeavyOperation { gas_per_unit, .. } => { - // Apply the linear transformation f(x) = mx, where: + // Apply the linear transformation: + // f(x) = mx + // where: // x is the number of units // m is the gas per unit units.saturating_mul(*gas_per_unit) diff --git a/fuel-tx/src/transaction/consensus_parameters/gas/default_gas_costs.rs b/fuel-tx/src/transaction/consensus_parameters/gas/default_gas_costs.rs index 0be80cb95b..3b5f319a51 100644 --- a/fuel-tx/src/transaction/consensus_parameters/gas/default_gas_costs.rs +++ b/fuel-tx/src/transaction/consensus_parameters/gas/default_gas_costs.rs @@ -16,7 +16,6 @@ pub fn default_gas_costs() -> GasCostsValues { cb: 1, cfei: 1, cfsi: 1, - croo: 16, div: 1, divi: 1, eck1: 951, @@ -108,6 +107,10 @@ pub fn default_gas_costs() -> GasCostsValues { base: 15, units_per_gas: 103, }, + croo: DependentCost::LightOperation { + base: 1, + units_per_gas: 1, + }, csiz: DependentCost::LightOperation { base: 17, units_per_gas: 790, diff --git a/fuel-vm/src/interpreter/blockchain.rs b/fuel-vm/src/interpreter/blockchain.rs index 3bb532d67b..9bcbb32fea 100644 --- a/fuel-vm/src/interpreter/blockchain.rs +++ b/fuel-vm/src/interpreter/blockchain.rs @@ -76,6 +76,8 @@ use fuel_types::{ #[cfg(test)] mod code_tests; #[cfg(test)] +mod croo_tests; +#[cfg(test)] mod other_tests; #[cfg(test)] mod smo_tests; @@ -272,16 +274,33 @@ where } pub(crate) fn code_root(&mut self, a: Word, b: Word) -> IoResult<(), S::DataError> { + 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.as_ref())? + .copied(); let owner = self.ownership_registers(); + let ( + SystemRegisters { + cgas, ggas, pc, is, .. + }, + _, + ) = split_registers(&mut self.registers); CodeRootCtx { memory: &mut self.memory, + storage: &mut self.storage, + gas_cost, + profiler: &mut self.profiler, input_contracts: InputContracts::new( self.tx.input_contracts(), &mut self.panic_context, ), - storage: &self.storage, + current_contract, + cgas, + ggas, owner, - pc: self.registers.pc_mut(), + pc, + is: is.as_ref(), } .code_root(a, b) } @@ -876,11 +895,17 @@ pub(crate) fn coinbase( } struct CodeRootCtx<'vm, S, I> { + storage: &'vm S, memory: &'vm mut [u8; MEM_SIZE], + gas_cost: DependentCost, + profiler: &'vm mut Profiler, input_contracts: InputContracts<'vm, I>, - storage: &'vm S, + current_contract: Option, + cgas: RegMut<'vm, CGAS>, + ggas: RegMut<'vm, GGAS>, owner: OwnershipRegisters, pc: RegMut<'vm, PC>, + is: Reg<'vm, IS>, } impl<'vm, S, I: Iterator> CodeRootCtx<'vm, S, I> { @@ -895,13 +920,27 @@ impl<'vm, S, I: Iterator> CodeRootCtx<'vm, S, I> { self.input_contracts.check(contract_id)?; - let (_, root) = self + let len = contract_size(self.storage, contract_id)? as Word; + let profiler = ProfileGas { + pc: self.pc.as_ref(), + is: self.is, + current_contract: self.current_contract, + profiler: self.profiler, + }; + dependent_gas_charge_without_base( + self.cgas, + self.ggas, + profiler, + self.gas_cost, + len, + )?; + let root = self .storage - .storage_contract_root(contract_id) + .storage_contract(contract_id) .transpose() .ok_or(PanicReason::ContractNotFound)? .map_err(RuntimeError::Storage)? - .into_owned(); + .root(); try_mem_write(a, root.as_ref(), self.owner, self.memory)?; @@ -1126,7 +1165,7 @@ where )?; if self.msg_data_len > self.max_message_data_length { - return Err(RuntimeError::Recoverable(PanicReason::MessageDataTooLong)) + return Err(RuntimeError::Recoverable(PanicReason::MessageDataTooLong)); } let msg_data_range = MemoryRange::new(self.msg_data_ptr, self.msg_data_len)?; diff --git a/fuel-vm/src/interpreter/blockchain/croo_tests.rs b/fuel-vm/src/interpreter/blockchain/croo_tests.rs new file mode 100644 index 0000000000..4587845fe4 --- /dev/null +++ b/fuel-vm/src/interpreter/blockchain/croo_tests.rs @@ -0,0 +1,204 @@ +use super::*; +use crate::{ + interpreter::{ + memory::Memory, + PanicContext, + }, + storage::MemoryStorage, +}; +use fuel_tx::{ + Contract, + GasCosts, +}; + +use alloc::vec; + +const CONTRACT_LEN: usize = 512; +const INITIAL_GAS: Word = 1_000_000; + +// Subset of SystemRegisters used during CROO tests +struct SystemRegisters { + pc: Word, + is: Word, + cgas: Word, + ggas: Word, +} + +fn initialize_system_registers() -> SystemRegisters { + SystemRegisters { + pc: 4, + is: 0, + cgas: INITIAL_GAS, + ggas: INITIAL_GAS, + } +} + +fn initialize_ownership_registers() -> OwnershipRegisters { + OwnershipRegisters { + sp: 1000, + ssp: 1, + hp: 2000, + prev_hp: 3000, + context: Context::Script { + block_height: Default::default(), + }, + } +} + +fn new_contract_id() -> ContractId { + ContractId::new([3u8; ContractId::LEN]) +} + +#[test] +fn test_code_root() { + // Given + let contract_id = new_contract_id(); + + let mut storage = MemoryStorage::new(Default::default(), Default::default()); + let mut memory: Memory = vec![1u8; MEM_SIZE].try_into().unwrap(); + memory[0..ContractId::LEN].copy_from_slice(contract_id.as_slice()); + + let data = alloc::vec![0xffu8; CONTRACT_LEN]; + let contract: Contract = data.into(); + let root = contract.root(); + storage + .storage_contract_insert(&contract_id, &contract) + .expect("Failed to insert contract"); + + let gas_cost = GasCosts::default().croo; + let ownership_registers = initialize_ownership_registers(); + let SystemRegisters { + mut pc, + is, + mut cgas, + mut ggas, + } = initialize_system_registers(); + let croo_address = 0xFFusize; + let croo_range = croo_address..croo_address + 32; + + let input_contracts = [contract_id]; + let mut panic_context = PanicContext::None; + + // When + CodeRootCtx { + memory: &mut memory, + storage: &storage, + gas_cost, + profiler: &mut Default::default(), + input_contracts: InputContracts::new(input_contracts.iter(), &mut panic_context), + current_contract: None, + cgas: RegMut::new(&mut cgas), + ggas: RegMut::new(&mut ggas), + owner: ownership_registers, + pc: RegMut::new(&mut pc), + is: Reg::new(&is), + } + .code_root(croo_address as Word, 0) + .unwrap(); + + // Then + assert_eq!(pc, 8); + assert_eq!(memory[croo_range], *root.as_slice()); + assert_eq!( + cgas, + INITIAL_GAS - gas_cost.resolve_without_base(CONTRACT_LEN as Word) + ); + assert_eq!( + ggas, + INITIAL_GAS - gas_cost.resolve_without_base(CONTRACT_LEN as Word) + ); +} + +#[test] +fn test_code_root_contract_not_found() { + // Given + let contract_id = new_contract_id(); + + let storage = MemoryStorage::new(Default::default(), Default::default()); + let mut memory: Memory = vec![1u8; MEM_SIZE].try_into().unwrap(); + memory[0..ContractId::LEN].copy_from_slice(contract_id.as_slice()); + + let gas_cost = GasCosts::default().croo; + let ownership_registers = initialize_ownership_registers(); + let SystemRegisters { + mut pc, + is, + mut cgas, + mut ggas, + } = initialize_system_registers(); + let croo_address = 0xFFusize; + let croo_range = croo_address..croo_address + 32; + + let input_contracts = [contract_id]; + let mut panic_context = PanicContext::None; + + // When + let _ = CodeRootCtx { + memory: &mut memory, + storage: &storage, + gas_cost, + profiler: &mut Default::default(), + input_contracts: InputContracts::new(input_contracts.iter(), &mut panic_context), + current_contract: None, + cgas: RegMut::new(&mut cgas), + ggas: RegMut::new(&mut ggas), + owner: ownership_registers, + pc: RegMut::new(&mut pc), + is: Reg::new(&is), + } + .code_root(croo_address as Word, 0) + .expect_err("Contract is not found"); + + // Then + assert_eq!(pc, 4); + assert_eq!(memory[croo_range], [1u8; 32]); + assert_eq!(cgas, INITIAL_GAS); + assert_eq!(ggas, INITIAL_GAS); +} + +#[test] +fn test_code_root_contract_not_in_inputs() { + // Given + let contract_id = new_contract_id(); + + let storage = MemoryStorage::new(Default::default(), Default::default()); + let mut memory: Memory = vec![1u8; MEM_SIZE].try_into().unwrap(); + memory[0..ContractId::LEN].copy_from_slice(contract_id.as_slice()); + + let gas_cost = GasCosts::default().croo; + let ownership_registers = initialize_ownership_registers(); + let SystemRegisters { + mut pc, + is, + mut cgas, + mut ggas, + } = initialize_system_registers(); + let croo_address = 0xFFusize; + let croo_range = croo_address..croo_address + 32; + + let input_contracts = []; + let mut panic_context = PanicContext::None; + + // When + let _ = CodeRootCtx { + memory: &mut memory, + storage: &storage, + gas_cost, + profiler: &mut Default::default(), + input_contracts: InputContracts::new(input_contracts.iter(), &mut panic_context), + current_contract: None, + cgas: RegMut::new(&mut cgas), + ggas: RegMut::new(&mut ggas), + owner: ownership_registers, + pc: RegMut::new(&mut pc), + is: Reg::new(&is), + } + .code_root(croo_address as Word, 0) + .expect_err("Contract is not in inputs"); + + // Then + assert_eq!(pc, 4); + assert_eq!(memory[croo_range], [1u8; 32]); + assert_eq!(cgas, INITIAL_GAS); + assert_eq!(ggas, INITIAL_GAS); +} diff --git a/fuel-vm/src/interpreter/blockchain/other_tests.rs b/fuel-vm/src/interpreter/blockchain/other_tests.rs index 66dd0d3b86..1d9497052c 100644 --- a/fuel-vm/src/interpreter/blockchain/other_tests.rs +++ b/fuel-vm/src/interpreter/blockchain/other_tests.rs @@ -12,7 +12,6 @@ use core::{ use super::*; use crate::interpreter::PanicContext; use fuel_storage::StorageAsMut; -use fuel_types::Salt; use test_case::test_case; #[test_case(false, 0, None, 0, [0; 32] => Ok(()); "Burn nothing")] @@ -235,83 +234,6 @@ fn test_coinbase() { assert_eq!(memory[20..20 + 32], [0u8; 32]); } -#[test] -fn test_code_root() { - let contract_id = ContractId::new([3u8; ContractId::LEN]); - let mut storage = MemoryStorage::new(Default::default(), Default::default()); - let mut memory: Memory = vec![1u8; MEM_SIZE].try_into().unwrap(); - memory[0..ContractId::LEN].copy_from_slice(contract_id.as_slice()); - let owner = OwnershipRegisters { - sp: 1000, - ssp: 1, - hp: 2000, - prev_hp: 3000, - context: Context::Script { - block_height: Default::default(), - }, - }; - let mut pc = 4; - let input_contracts = [contract_id]; - let mut panic_context = PanicContext::None; - let _ = CodeRootCtx { - memory: &mut memory, - input_contracts: InputContracts::new(input_contracts.iter(), &mut panic_context), - storage: &storage, - owner, - pc: RegMut::new(&mut pc), - } - .code_root(20, 0) - .expect_err("Contract is not found"); - assert_eq!(pc, 4); - - storage - .storage_contract_root_insert( - &ContractId::from([3u8; 32]), - &Salt::from([5u8; 32]), - &Bytes32::from([6u8; 32]), - ) - .unwrap(); - let owner = OwnershipRegisters { - sp: 1000, - ssp: 1, - hp: 2000, - prev_hp: 3000, - context: Context::Script { - block_height: Default::default(), - }, - }; - CodeRootCtx { - memory: &mut memory, - input_contracts: InputContracts::new(input_contracts.iter(), &mut panic_context), - storage: &storage, - owner, - pc: RegMut::new(&mut pc), - } - .code_root(20, 0) - .unwrap(); - assert_eq!(pc, 8); - assert_eq!(memory[20..20 + 32], [6u8; 32]); - - let owner = OwnershipRegisters { - sp: 1000, - ssp: 1, - hp: 2000, - prev_hp: 3000, - context: Context::Script { - block_height: Default::default(), - }, - }; - let _ = CodeRootCtx { - memory: &mut memory, - input_contracts: InputContracts::new(iter::empty(), &mut panic_context), - storage: &storage, - owner, - pc: RegMut::new(&mut pc), - } - .code_root(20, 0) - .expect_err("Contract is not in inputs"); -} - #[test] fn test_code_size() { let contract_id = ContractId::new([3u8; ContractId::LEN]); diff --git a/fuel-vm/src/interpreter/diff.rs b/fuel-vm/src/interpreter/diff.rs index 070715e3e5..2176b402f4 100644 --- a/fuel-vm/src/interpreter/diff.rs +++ b/fuel-vm/src/interpreter/diff.rs @@ -37,7 +37,6 @@ use crate::{ context::Context, storage::{ ContractsAssets, - ContractsInfo, ContractsRawCode, ContractsState, }, diff --git a/fuel-vm/src/interpreter/diff/storage.rs b/fuel-vm/src/interpreter/diff/storage.rs index 2a557e916f..ed13cfae4a 100644 --- a/fuel-vm/src/interpreter/diff/storage.rs +++ b/fuel-vm/src/interpreter/diff/storage.rs @@ -29,7 +29,6 @@ use super::{ pub(super) enum StorageDelta { State(MappableDelta), Assets(MappableDelta), - Info(MappableDelta), RawCode(MappableDelta), } @@ -38,7 +37,6 @@ pub(super) enum StorageDelta { pub(super) enum StorageState { State(MappableState), Assets(MappableState), - Info(MappableState), RawCode(MappableState), } @@ -114,10 +112,6 @@ where from: HashMap::new(), to: HashMap::new(), }; - let mut contracts_info = Delta { - from: HashMap::new(), - to: HashMap::new(), - }; let mut contracts_raw_code = Delta { from: HashMap::new(), to: HashMap::new(), @@ -131,16 +125,12 @@ where StorageDelta::Assets(delta) => { mappable_delta_to_hashmap(&mut contracts_assets, delta) } - StorageDelta::Info(delta) => { - mappable_delta_to_hashmap(&mut contracts_info, delta) - } StorageDelta::RawCode(delta) => { mappable_delta_to_hashmap(&mut contracts_raw_code, delta) } } } storage_state_to_changes(&mut diff, contracts_state, StorageState::State); - storage_state_to_changes(&mut diff, contracts_info, StorageState::Info); storage_state_to_changes(&mut diff, contracts_assets, StorageState::Assets); storage_state_to_changes(&mut diff, contracts_raw_code, StorageState::RawCode); diff @@ -204,16 +194,6 @@ where .unwrap(); } } - StorageState::Info(MappableState { key, value }) => { - if let Some(value) = value { - StorageMutate::::insert( - &mut self.storage, - key, - value, - ) - .unwrap(); - } - } StorageState::RawCode(MappableState { key, value }) => { if let Some(value) = value { StorageMutate::::insert( @@ -471,23 +451,6 @@ impl StorageType for ContractsAssets { } } -impl StorageType for ContractsInfo { - fn record_insert( - key: &ContractId, - value: &(fuel_types::Salt, Bytes32), - existing: Option<(fuel_types::Salt, Bytes32)>, - ) -> StorageDelta { - StorageDelta::Info(MappableDelta::Insert(*key, *value, existing)) - } - - fn record_remove( - key: &ContractId, - value: (fuel_types::Salt, Bytes32), - ) -> StorageDelta { - StorageDelta::Info(MappableDelta::Remove(*key, value)) - } -} - impl StorageType for ContractsRawCode { fn record_insert( key: &ContractId, diff --git a/fuel-vm/src/interpreter/executors/instruction.rs b/fuel-vm/src/interpreter/executors/instruction.rs index 243e52c035..6a2e90d31b 100644 --- a/fuel-vm/src/interpreter/executors/instruction.rs +++ b/fuel-vm/src/interpreter/executors/instruction.rs @@ -767,7 +767,6 @@ where } Instruction::CROO(croo) => { - self.gas_charge(self.gas_costs().croo)?; let (a, b) = croo.unpack(); self.code_root(r!(a), r!(b))?; } diff --git a/fuel-vm/src/interpreter/executors/main.rs b/fuel-vm/src/interpreter/executors/main.rs index 6e99246bfe..ac0338bd3c 100644 --- a/fuel-vm/src/interpreter/executors/main.rs +++ b/fuel-vm/src/interpreter/executors/main.rs @@ -444,7 +444,7 @@ where } storage - .deploy_contract_with_id(salt, storage_slots, &contract, &root, &id) + .deploy_contract_with_id(storage_slots, &contract, &id) .map_err(RuntimeError::Storage)?; Self::finalize_outputs( create, diff --git a/fuel-vm/src/storage.rs b/fuel-vm/src/storage.rs index f017e8c820..c8a15afc0c 100644 --- a/fuel-vm/src/storage.rs +++ b/fuel-vm/src/storage.rs @@ -6,7 +6,6 @@ use fuel_types::{ AssetId, Bytes32, ContractId, - Salt, Word, }; @@ -31,18 +30,6 @@ impl Mappable for ContractsRawCode { type Value = [u8]; } -/// The storage table for contract's additional information as salt, root hash, etc. -pub struct ContractsInfo; - -impl Mappable for ContractsInfo { - type Key = Self::OwnedKey; - type OwnedKey = ContractId; - type OwnedValue = Self::Value; - /// `Salt` - is the salt used during creation of the contract for uniques. - /// `Bytes32` - is the root hash of the contract's code. - type Value = (Salt, Bytes32); -} - /// The storage table for contract's assets balances. /// /// Lifetime is for optimization to avoid `clone`. diff --git a/fuel-vm/src/storage/interpreter.rs b/fuel-vm/src/storage/interpreter.rs index e10ee809a1..24a88293a5 100644 --- a/fuel-vm/src/storage/interpreter.rs +++ b/fuel-vm/src/storage/interpreter.rs @@ -17,7 +17,6 @@ use fuel_types::{ BlockHeight, Bytes32, ContractId, - Salt, Word, }; @@ -28,7 +27,6 @@ use crate::{ }, storage::{ ContractsAssets, - ContractsInfo, ContractsRawCode, ContractsState, }, @@ -48,7 +46,6 @@ pub trait InterpreterStorage: StorageMutate + StorageSize + StorageRead - + StorageMutate + MerkleRootStorage + ContractsAssetsStorage { @@ -78,14 +75,11 @@ pub trait InterpreterStorage: /// Deploy a contract into the storage with contract id fn deploy_contract_with_id( &mut self, - salt: &Salt, slots: &[StorageSlot], contract: &Contract, - root: &Bytes32, id: &ContractId, ) -> Result<(), Self::DataError> { self.storage_contract_insert(id, contract)?; - self.storage_contract_root_insert(id, salt, root)?; // On the `fuel-core` side it is done in more optimal way slots.iter().try_for_each(|s| { @@ -137,25 +131,6 @@ pub trait InterpreterStorage: self.storage::().contains_key(id) } - /// Fetch a previously inserted salt+root tuple from the chain state for a - /// given contract. - fn storage_contract_root( - &self, - id: &ContractId, - ) -> Result>, Self::DataError> { - StorageInspect::::get(self, id) - } - - /// Append the salt+root of a contract that was appended to the chain. - fn storage_contract_root_insert( - &mut self, - id: &ContractId, - salt: &Salt, - root: &Bytes32, - ) -> Result, Self::DataError> { - StorageMutate::::insert(self, id, &(*salt, *root)) - } - /// Fetch the value form a key-value mapping in a contract storage. fn merkle_contract_state( &self, diff --git a/fuel-vm/src/storage/memory.rs b/fuel-vm/src/storage/memory.rs index c23445f1a0..648aaf1fca 100644 --- a/fuel-vm/src/storage/memory.rs +++ b/fuel-vm/src/storage/memory.rs @@ -3,7 +3,6 @@ use crate::{ storage::{ ContractsAssetKey, ContractsAssets, - ContractsInfo, ContractsRawCode, ContractsState, ContractsStateKey, @@ -28,7 +27,6 @@ use fuel_types::{ BlockHeight, Bytes32, ContractId, - Salt, Word, }; use itertools::Itertools; @@ -48,7 +46,6 @@ struct MemoryStorageInner { contracts: BTreeMap, balances: BTreeMap, contract_state: BTreeMap, - contract_code_root: BTreeMap, } #[derive(Debug, Clone)] @@ -216,38 +213,6 @@ impl StorageRead for MemoryStorage { } } -impl StorageInspect for MemoryStorage { - type Error = Infallible; - - fn get( - &self, - key: &ContractId, - ) -> Result>, Infallible> { - Ok(self.memory.contract_code_root.get(key).map(Cow::Borrowed)) - } - - fn contains_key(&self, key: &ContractId) -> Result { - Ok(self.memory.contract_code_root.contains_key(key)) - } -} - -impl StorageMutate for MemoryStorage { - fn insert( - &mut self, - key: &ContractId, - value: &(Salt, Bytes32), - ) -> Result, Infallible> { - Ok(self.memory.contract_code_root.insert(*key, *value)) - } - - fn remove( - &mut self, - key: &ContractId, - ) -> Result, Infallible> { - Ok(self.memory.contract_code_root.remove(key)) - } -} - impl StorageInspect for MemoryStorage { type Error = Infallible; diff --git a/fuel-vm/src/tests/blockchain.rs b/fuel-vm/src/tests/blockchain.rs index 91f233b29a..d2a89f5382 100644 --- a/fuel-vm/src/tests/blockchain.rs +++ b/fuel-vm/src/tests/blockchain.rs @@ -8,7 +8,10 @@ use crate::{ }, prelude::*, script_with_data_offset, - util::test_helpers::check_expected_reason_for_instructions, + util::test_helpers::{ + check_expected_reason_for_instructions, + check_expected_reason_for_instructions_with_client, + }, }; use alloc::{ vec, @@ -54,6 +57,48 @@ use rand::{ SeedableRng, }; +fn deploy_contract( + client: &mut MemoryClient, + contract: Witness, + salt: Salt, + storage_slots: Vec, +) { + let code_root = Contract::root_from_code(contract.as_ref()); + let state_root = Contract::initial_state_root(storage_slots.iter()); + let contract_id = + Contract::from(contract.as_ref()).id(&salt, &code_root, &state_root); + + let tx_params = TxParameters::default(); + let height = Default::default(); + let contract_deployer = TransactionBuilder::create(contract, salt, storage_slots) + .with_tx_params(tx_params) + .add_output(Output::contract_created(contract_id, state_root)) + .add_random_fee_input() + .finalize_checked(height); + + client + .deploy(contract_deployer) + .expect("valid contract deployment"); +} + +fn write_contract_id( + script: &mut Vec, + register: u8, + contract_id: ContractId, +) { + const COUNT: Immediate12 = ContractId::LEN as Immediate12; + script.extend([op::ori(register, register, COUNT), op::aloc(register)]); + for (i, byte) in contract_id.as_ref().iter().enumerate() { + let index = i as Immediate12; + let value = *byte as Immediate12; + script.extend([ + op::movi(register, value.into()), + op::sb(RegId::HP, register, index), + ]); + } + script.push(op::move_(register, RegId::HP)); +} + const SET_STATUS_REG: u8 = 0x39; // log2(VM_MAX_MEM) - used to set a pointer to the memory boundary via SHL: // 1<>().into(); + + let salt = Default::default(); + let code_root = Contract::root_from_code(contract.as_ref()); + let storage_slots = vec![]; + let state_root = Contract::initial_state_root(storage_slots.iter()); + let contract_id = + Contract::from(contract.as_ref()).id(&salt, &code_root, &state_root); + + deploy_contract(&mut client, contract, salt, storage_slots); + let reg_a = 0x20; + let reg_contract = 0x21; + + let mut code_root_script = vec![]; + write_contract_id(&mut code_root_script, reg_contract, contract_id); + // When // cover contract_id_end beyond max ram - let code_root = vec![op::not(reg_a, RegId::ZERO), op::croo(reg_a, RegId::ZERO)]; + code_root_script.extend([op::not(reg_a, RegId::ZERO), op::croo(reg_a, reg_contract)]); - check_expected_reason_for_instructions(code_root, MemoryOverflow); + // Then + check_expected_reason_for_instructions_with_client( + client, + code_root_script, + MemoryOverflow, + ); } #[test] @@ -1043,17 +1111,40 @@ fn code_root_b_plus_32_overflow() { #[test] fn code_root_a_over_max_ram() { - // Then deploy another contract that attempts to read the first one + // Given + let mut client = MemoryClient::default(); + let instructions = vec![op::noop(), op::noop(), op::noop()]; + let contract: Witness = instructions.into_iter().collect::>().into(); + + let salt = Default::default(); + let code_root = Contract::root_from_code(contract.as_ref()); + let storage_slots = vec![]; + let state_root = Contract::initial_state_root(storage_slots.iter()); + let contract_id = + Contract::from(contract.as_ref()).id(&salt, &code_root, &state_root); + + deploy_contract(&mut client, contract, salt, storage_slots); + let reg_a = 0x20; + let reg_contract = 0x21; + + let mut code_root_script = vec![]; + write_contract_id(&mut code_root_script, reg_contract, contract_id); + // When // cover contract_id_end beyond max ram - let code_root = vec![ + code_root_script.extend([ op::slli(reg_a, RegId::ONE, MAX_MEM_SHL), op::subi(reg_a, reg_a, 31 as Immediate12), - op::croo(reg_a, RegId::ZERO), - ]; + op::croo(reg_a, reg_contract), + ]); - check_expected_reason_for_instructions(code_root, MemoryOverflow); + // Then + check_expected_reason_for_instructions_with_client( + client, + code_root_script, + MemoryOverflow, + ); } #[test] diff --git a/fuel-vm/src/util.rs b/fuel-vm/src/util.rs index 6c83fe9463..9b708d4e22 100644 --- a/fuel-vm/src/util.rs +++ b/fuel-vm/src/util.rs @@ -584,7 +584,7 @@ pub mod test_helpers { ); } - fn check_expected_reason_for_instructions_with_client( + pub fn check_expected_reason_for_instructions_with_client( mut client: MemoryClient, instructions: Vec, expected_reason: PanicReason,