diff --git a/CHANGELOG.md b/CHANGELOG.md index c231bbbaa..0627562ef 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,11 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ## [Unreleased] +## [Version 0.58.2] + +### Fixed +- [#854](https://github.com/FuelLabs/fuel-vm/pull/854): Fixed a bug where LDC mode 2 padding bytes would be copied from memory instead of using zeroes. + ## [Version 0.58.1] ### Fixed diff --git a/fuel-vm/src/interpreter/blockchain.rs b/fuel-vm/src/interpreter/blockchain.rs index 073ed314d..ef0f73c80 100644 --- a/fuel-vm/src/interpreter/blockchain.rs +++ b/fuel-vm/src/interpreter/blockchain.rs @@ -809,6 +809,7 @@ where let current_contract = current_contract(self.context, self.fp, self.memory)?; let length = bytes::padded_len_word(length_unpadded).unwrap_or(Word::MAX); + let length_padding = length.saturating_sub(length_unpadded); // Fetch the storage blob let profiler = ProfileGas { @@ -833,8 +834,15 @@ where let owner = OwnershipRegisters::only_allow_stack_write(new_sp, ssp, *self.hp); let src = input_src_addr.saturating_add(input_offset); - // Copy the code. - self.memory.memcopy(dst, src, length, owner)?; + // Copy the code + self.memory.memcopy(dst, src, length_unpadded, owner)?; + + // Write padding + if length_padding > 0 { + self.memory + .write(owner, dst.saturating_add(length_unpadded), length_padding)? + .fill(0); + } // Mark stack space as allocated *self.sp = new_sp; diff --git a/fuel-vm/src/tests/blockchain/ldc_mode_2.rs b/fuel-vm/src/tests/blockchain/ldc_mode_2.rs index 631088203..c4dcab0c4 100644 --- a/fuel-vm/src/tests/blockchain/ldc_mode_2.rs +++ b/fuel-vm/src/tests/blockchain/ldc_mode_2.rs @@ -16,6 +16,11 @@ use crate::{ MemoryStorage, Transactor, }, + tests::test_helpers::{ + assert_success, + run_script, + set_full_word, + }, }; use alloc::{ vec, @@ -34,6 +39,50 @@ use fuel_tx::{ Receipt, TransactionBuilder, }; +use test_case::test_case; + +fn ldcv2_data_init() -> Vec { + vec![ + // Write bytes 0..16 to end of the heap + op::move_(0x11, RegId::HP), + op::movi(0x10, 16), + op::aloc(0x10), + // loop: + op::sub(0x10, 0x10, 1), + op::sub(0x11, 0x11, 1), + op::sb(0x11, 0x10, 0), + op::jnzb(0x10, RegId::ZERO, 2), // jmp loop + ] +} + +#[test_case(0, 0 => Vec::::new())] +#[test_case(0, 1 => vec![0; 8])] +#[test_case(0, 2 => vec![0, 1, 0, 0, 0, 0, 0, 0])] +#[test_case(0, 16 => vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15])] +#[test_case(1, 15 => vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 0])] +#[test_case(4, 12 => vec![4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 0, 0, 0, 0])] +#[test_case(7, 9 => vec![7, 8, 9, 10, 11, 12, 13, 14, 15, 0, 0, 0, 0, 0, 0, 0])] +#[test_case(8, 8 => vec![8, 9, 10, 11, 12, 13, 14, 15])] +#[test_case(9, 7 => vec![9, 10, 11, 12, 13, 14, 15, 0])] +fn ldcv2__has_correct_padding(offset: u32, len: u32) -> Vec { + let mut script = ldcv2_data_init(); + let padded_len = len.next_multiple_of(8) as u64; + script.extend(set_full_word(0x14, padded_len)); + script.extend([ + op::movi(0x11, offset), + op::movi(0x12, len), + op::move_(0x13, RegId::SSP), + op::ldc(RegId::HP, 0x11, 0x12, 2), + op::logd(RegId::SSP, RegId::ZERO, 0x13, 0x14), + op::ret(0x1), + ]); + let receipts = run_script(script); + assert_success(&receipts); + let Some(Receipt::LogData { data, .. }) = receipts.first() else { + panic!("Expected LogData receipt"); + }; + data.clone().unwrap() +} fn ldcv2_reason_helper(script: Vec) -> Result<(), PanicReason> { let gas_price = 0;