Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix ldc opcode in internal contexts #736

Merged
merged 12 commits into from
May 29, 2024
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,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.
- [#729](https://github.com/FuelLabs/fuel-vm/pull/729): Removed default implementation of `Node::key_size_bits`, implementors must now define it themselves. Also some helper traits have been merged together, or their types changed.
- [#736](https://github.com/FuelLabs/fuel-vm/pull/736): Bugfix: LDC instruction now works in internal contexts as well.
xgreenx marked this conversation as resolved.
Show resolved Hide resolved

## [Version 0.49.0]

Expand Down
13 changes: 13 additions & 0 deletions fuel-types/src/bytes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,19 @@ pub const fn padded_len_usize(len: usize) -> Option<usize> {
}
}

/// Return the word-padded length of an arbitrary length.
/// Returns None if the length is too large to be represented as `Word`.
#[allow(clippy::arithmetic_side_effects)] // Safety: (a % b) < b
pub const fn padded_len_word(len: Word) -> Option<Word> {
let modulo = len % WORD_SIZE as Word;
if modulo == 0 {
Some(len)
} else {
let padding = WORD_SIZE as Word - modulo;
len.checked_add(padding)
}
}

#[cfg(feature = "unsafe")]
#[allow(unsafe_code)]
/// Add a conversion from arbitrary slices into arrays
Expand Down
29 changes: 15 additions & 14 deletions fuel-vm/src/interpreter/blockchain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ use fuel_tx::{
};
use fuel_types::{
bytes,
bytes::padded_len_word,
Address,
AssetId,
BlockHeight,
Expand Down Expand Up @@ -102,8 +103,6 @@ where
// We will charge for the contracts size in the `load_contract_code`.
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)?;
let (
SystemRegisters {
cgas,
Expand All @@ -119,6 +118,7 @@ where
) = split_registers(&mut self.registers);
let input = LoadContractCodeCtx {
memory: &mut self.memory,
context: &self.context,
profiler: &mut self.profiler,
storage: &mut self.storage,
contract_max_size,
Expand All @@ -127,7 +127,6 @@ where
&mut self.panic_context,
),
gas_cost,
current_contract,
cgas,
ggas,
ssp,
Expand Down Expand Up @@ -538,10 +537,10 @@ where
struct LoadContractCodeCtx<'vm, S, I> {
contract_max_size: u64,
memory: &'vm mut Memory,
context: &'vm Context,
profiler: &'vm mut Profiler,
input_contracts: InputContracts<'vm, I>,
storage: &'vm S,
current_contract: Option<ContractId>,
gas_cost: DependentCost,
cgas: RegMut<'vm, CGAS>,
ggas: RegMut<'vm, GGAS>,
Expand Down Expand Up @@ -576,7 +575,6 @@ where
{
let ssp = *self.ssp;
let sp = *self.sp;
let fp = *self.fp;
let region_start = ssp;

if ssp != sp {
Expand All @@ -588,6 +586,8 @@ where
.try_into()
.map_err(|_| PanicReason::MemoryOverflow)?;

let current_contract = current_contract(self.context, self.fp, self.memory)?;

let length = bytes::padded_len_usize(
length_unpadded
.try_into()
Expand All @@ -612,7 +612,7 @@ where
let profiler = ProfileGas {
pc: self.pc.as_ref(),
is: self.is,
current_contract: self.current_contract,
current_contract,
profiler: self.profiler,
};
dependent_gas_charge_without_base(
Expand All @@ -636,19 +636,20 @@ where
length,
)?;

// Update frame pointer, if we have a stack frame (e.g. fp > 0)
if fp > 0 {
let size = CallFrame::code_size_offset().saturating_add(WORD_SIZE);

let old_code_size = Word::from_be_bytes(self.memory.read_bytes(fp)?);

// Update frame code size, if we have a stack frame (i.e. fp > 0)
if self.context.is_internal() {
let code_size_ptr =
(*self.fp).saturating_add(CallFrame::code_size_offset() as Word);
let old_code_size =
Word::from_be_bytes(self.memory.read_bytes(code_size_ptr)?);
let old_code_size = padded_len_word(old_code_size)
.expect("Code size cannot overflow with padding");
Comment on lines +644 to +646
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would assume the size should already be padded when written in memory. Is it really correct to just compute padding here?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Our code uses unpadded value in the CallFrame

image

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep. Shouldn't CallFrame use the padded value?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know=) The question is to the compiler team: do they use it somehow or not? Or is it used by anyone=)

Our specification says "Code size in bytes.", that sounds like exact size of the contract without padding.

image

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll ask the team, but a quick search from Sway codebase seems like they don't use this for anything. Time to remove? :D

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doesnt "word-aligned" mean the same thing as padding here?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep.

Moved to use padded size in 7335f9f.

let new_code_size = old_code_size
.checked_add(length as Word)
.ok_or(PanicReason::MemoryOverflow)?;

self.memory
.write_noownerchecks(fp, size)?
.copy_from_slice(&new_code_size.to_be_bytes());
.write_bytes_noownerchecks(code_size_ptr, new_code_size.to_be_bytes())?;
}

inc_pc(self.pc)?;
Expand Down
62 changes: 60 additions & 2 deletions fuel-vm/src/interpreter/blockchain/code_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ use crate::{
use fuel_tx::Contract;

#[test]
fn test_load_contract() -> IoResult<(), Infallible> {
fn test_load_contract_in_script() -> IoResult<(), Infallible> {
MitchTurner marked this conversation as resolved.
Show resolved Hide resolved
let mut storage = MemoryStorage::default();
let mut memory: Memory = vec![1u8; MEM_SIZE].try_into().unwrap();
let mut pc = 4;
Expand Down Expand Up @@ -48,9 +48,67 @@ fn test_load_contract() -> IoResult<(), Infallible> {
contract_max_size: 100,
storage: &storage,
memory: &mut memory,
context: &Context::Script {
block_height: Default::default(),
},
profiler: &mut Profiler::default(),
input_contracts: InputContracts::new(input_contracts.iter(), &mut panic_context),
gas_cost: DependentCost::from_units_per_gas(13, 1),
cgas: RegMut::new(&mut cgas),
ggas: RegMut::new(&mut ggas),
ssp: RegMut::new(&mut ssp),
sp: RegMut::new(&mut sp),
fp: Reg::new(&fp),
pc: RegMut::new(&mut pc),
is: Reg::new(&is),
};
input.load_contract_code(contract_id_mem_address, offset, num_bytes)?;
assert_eq!(pc, 8);
assert_eq!(cgas, 1000 - CONTRACT_SIZE /* price per byte */);
assert_eq!(ggas, 1000 - CONTRACT_SIZE /* price per byte */);

Ok(())
}
#[test]
fn test_load_contract_in_call() -> IoResult<(), Infallible> {
let mut storage = MemoryStorage::default();
let mut memory: Memory = vec![1u8; MEM_SIZE].try_into().unwrap();
let mut pc = 4;
let mut cgas = 1000;
let mut ggas = 1000;
let mut ssp = 1000;
let mut sp = 1000;
let fp = 32;
let is = 0;

let contract_id = ContractId::from([4u8; 32]);

let contract_id_mem_address: Word = 32;
let offset = 20;
let num_bytes = 40;
const CONTRACT_SIZE: u64 = 400;

memory[contract_id_mem_address as usize
..contract_id_mem_address as usize + ContractId::LEN]
.copy_from_slice(contract_id.as_ref());
storage
.storage_contract_insert(
&contract_id,
&Contract::from(vec![5u8; CONTRACT_SIZE as usize]),
)
.unwrap();

let mut panic_context = PanicContext::None;
let input_contracts = [contract_id];
let input = LoadContractCodeCtx {
contract_max_size: 100,
storage: &storage,
memory: &mut memory,
context: &Context::Call {
block_height: Default::default(),
},
profiler: &mut Profiler::default(),
input_contracts: InputContracts::new(input_contracts.iter(), &mut panic_context),
current_contract: None,
gas_cost: DependentCost::from_units_per_gas(13, 1),
cgas: RegMut::new(&mut cgas),
ggas: RegMut::new(&mut ggas),
Expand Down
8 changes: 2 additions & 6 deletions fuel-vm/src/interpreter/internal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,7 @@ pub(crate) fn current_contract(
memory: &Memory,
) -> Result<Option<ContractId>, PanicReason> {
if context.is_internal() {
Ok(Some(internal_contract(context, fp, memory)?))
Ok(Some(ContractId::new(memory.read_bytes(*fp)?)))
} else {
Ok(None)
}
Expand All @@ -197,11 +197,7 @@ pub(crate) fn internal_contract(
fp: Reg<FP>,
memory: &Memory,
) -> Result<ContractId, PanicReason> {
if context.is_internal() {
Ok(ContractId::new(memory.read_bytes(*fp)?))
} else {
Err(PanicReason::ExpectedInternalContext)
}
current_contract(context, fp, memory)?.ok_or(PanicReason::ExpectedInternalContext)
}

pub(crate) fn set_frame_pointer(
Expand Down
Loading
Loading