Skip to content

Commit

Permalink
Use padded codesize in CallFrame
Browse files Browse the repository at this point in the history
  • Loading branch information
Dentosal committed May 28, 2024
1 parent 8ec075c commit 7335f9f
Show file tree
Hide file tree
Showing 2 changed files with 55 additions and 129 deletions.
126 changes: 48 additions & 78 deletions fuel-vm/src/interpreter/flow.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ use fuel_tx::{
Receipt,
};
use fuel_types::{
bytes::padded_len_usize,
canonical::Serialize,
AssetId,
Bytes32,
Expand Down Expand Up @@ -475,12 +476,13 @@ where
let asset_id =
AssetId::new(self.memory.read_bytes(self.params.asset_id_pointer)?);

let mut frame = call_frame(
self.registers.copy_registers(),
&self.storage,
call,
asset_id,
)?;
let code_size = contract_size(&self.storage, &call.to())?;
let code_size_padded =
padded_len_usize(code_size).expect("code_size cannot overflow with padding");

let total_size_in_stack = CallFrame::serialized_size()
.checked_add(code_size_padded)
.ok_or_else(|| Bug::new(BugVariant::CodeSizeOverflow))?;

let profiler = ProfileGas {
pc: self.registers.system_registers.pc.as_ref(),
Expand All @@ -493,22 +495,22 @@ where
self.registers.system_registers.ggas.as_mut(),
profiler,
self.gas_cost,
frame.total_code_size() as Word,
code_size_padded as Word,
)?;

if let Some(source_contract) = self.current_contract {
balance_decrease(
self.storage,
&source_contract,
frame.asset_id(),
&asset_id,
self.params.amount_of_coins_to_forward,
)?;
} else {
let amount = self.params.amount_of_coins_to_forward;
external_asset_id_balance_sub(
self.runtime_balances,
self.memory,
frame.asset_id(),
&asset_id,
amount,
)?;
}
Expand Down Expand Up @@ -550,18 +552,21 @@ where
.checked_sub(forward_gas_amount)
.ok_or_else(|| Bug::new(BugVariant::ContextGasUnderflow))?;

// Construct frame
let mut frame = CallFrame::new(
*call.to(),
asset_id,
self.registers.copy_registers(),
code_size_padded,
call.a(),
call.b(),
);
*frame.context_gas_mut() = *self.registers.system_registers.cgas;
*frame.global_gas_mut() = *self.registers.system_registers.ggas;

debug_assert!(frame.size() % 8 == 0);
let len = frame
.size()
.checked_add(frame.total_code_size())
.ok_or_else(|| Bug::new(BugVariant::CodeSizeOverflow))?;
let lenw = len as Word;

// Allocate stack memory
let old_sp = *self.registers.system_registers.sp;
let new_sp = old_sp.saturating_add(lenw);
let new_sp = old_sp.saturating_add(total_size_in_stack as Word);
self.memory.grow_stack(new_sp)?;
*self.registers.system_registers.sp = new_sp;
*self.registers.system_registers.ssp = new_sp;
Expand All @@ -579,26 +584,35 @@ where
old_sp,
);

let frame_end = write_call_to_memory(
&frame,
self.registers.system_registers.fp.as_ref(),
len,
self.memory,
self.storage,
// Write the frame to memory
// Ownership checks are disabled because we just allocated the memory above.
let dst = self.memory.write_noownerchecks(
*self.registers.system_registers.fp,
total_size_in_stack,
)?;
let (mem_frame, mem_code) = dst.split_at_mut(CallFrame::serialized_size());
mem_frame.copy_from_slice(&frame.to_bytes());
let (mem_code, mem_code_padding) = mem_code.split_at_mut(code_size);
read_contract(&call.to(), self.storage, mem_code)?;
mem_code_padding.fill(0);

#[allow(clippy::arithmetic_side_effects)] // Checked above
let frame_end =
(*self.registers.system_registers.fp) + CallFrame::serialized_size() as Word;

*self.registers.system_registers.bal = self.params.amount_of_coins_to_forward;
*self.registers.system_registers.pc = frame_end;
*self.registers.system_registers.is = *self.registers.system_registers.pc;
*self.registers.system_registers.cgas = forward_gas_amount;

let receipt = Receipt::call(
id,
*frame.to(),
*call.to(),
self.params.amount_of_coins_to_forward,
*frame.asset_id(),
asset_id,
forward_gas_amount,
frame.a(),
frame.b(),
call.a(),
call.b(),
*self.registers.system_registers.pc,
*self.registers.system_registers.is,
);
Expand All @@ -611,67 +625,23 @@ where
}
}

fn write_call_to_memory<S>(
frame: &CallFrame,
fp: Reg<FP>,
len: usize,
memory: &mut Memory,
fn read_contract<S>(
contract: &ContractId,
storage: &S,
) -> IoResult<Word, S::Error>
dst: &mut [u8],
) -> IoResult<(), S::Error>
where
S: StorageSize<ContractsRawCode> + StorageRead<ContractsRawCode> + StorageAsRef,
{
// Addition is safe because code size + padding is always less than len
let frame_len_with_padding = frame.total_code_size();
let content_size = len.saturating_sub(frame_len_with_padding);
memory
.write_noownerchecks(*fp, content_size)?
.copy_from_slice(&frame.to_bytes());

let code_start = fp.saturating_add(CallFrame::serialized_size() as Word);
let code_len = frame.code_size();
let bytes_read = storage
.storage::<ContractsRawCode>()
.read(
frame.to(),
memory
.write_noownerchecks(code_start, code_len)
.expect("Write access checked above"),
)
.read(contract, dst)
.map_err(RuntimeError::Storage)?
.ok_or(PanicReason::ContractNotFound)?;
if bytes_read != frame.code_size() {
if bytes_read != dst.len() {
return Err(PanicReason::ContractMismatch.into())
}

let padding_start = code_start.saturating_add(code_len as Word);
let padding_size = frame.code_size_padding();
if padding_size > 0 {
memory
.write_noownerchecks(padding_start, padding_size)?
.fill(0);
}
Ok((*fp)
.checked_add(content_size as Word)
.expect("Checked above"))
}

fn call_frame<S>(
registers: [Word; VM_REGISTER_COUNT],
storage: &S,
call: Call,
asset_id: AssetId,
) -> IoResult<CallFrame, S::Error>
where
S: StorageSize<ContractsRawCode> + ?Sized,
{
let (to, a, b) = call.into_inner();

let code_size = contract_size(storage, &to)?;

let frame = CallFrame::new(to, asset_id, registers, code_size, a, b);

Ok(frame)
Ok(())
}

impl<'a> From<&'a PrepareCallRegisters<'_>> for SystemRegistersRef<'a> {
Expand Down
58 changes: 7 additions & 51 deletions fuel-vm/src/interpreter/flow/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,7 @@ use fuel_tx::{
Script,
};
use fuel_types::{
canonical::{
Deserialize,
Serialize,
},
canonical::Serialize,
ContractId,
};
use test_case::test_case;
Expand Down Expand Up @@ -122,7 +119,7 @@ impl Default for Output {
Default::default(),
Default::default(),
make_reg(&[(HP, 1000), (SP, 100), (SSP, 100), (CGAS, 20), (GGAS, 20)]),
10,
16,
0,
0,
)],
Expand Down Expand Up @@ -187,7 +184,7 @@ fn mem(set: &[(usize, Vec<u8>)]) -> Memory {
script: Some(Default::default()),
..Default::default()
} => using check_output({
let frame = CallFrame::new(ContractId::from([1u8; 32]), AssetId::from([2u8; 32]), make_reg(&[(HP, 1000), (SP, 200), (SSP, 200), (CGAS, 161), (GGAS, 191)]), 100, 4, 5);
let frame = CallFrame::new(ContractId::from([1u8; 32]), AssetId::from([2u8; 32]), make_reg(&[(HP, 1000), (SP, 200), (SSP, 200), (CGAS, 161), (GGAS, 191)]), 104, 4, 5);
let receipt = Receipt::call(ContractId::zeroed(), ContractId::from([1u8; 32]), 20, AssetId::from([2u8; 32]), 30, 4, 5, 800, 800);
let mut script = Script::default();
*script.receipts_root_mut() = crypto::ephemeral_merkle_root([receipt.to_bytes()].into_iter());
Expand Down Expand Up @@ -216,7 +213,7 @@ fn mem(set: &[(usize, Vec<u8>)]) -> Memory {
} => using check_output(Ok(Output{
reg: RegInput{hp: 1000, sp: 716, ssp: 716, fp: 100, pc: 700, is: 700, bal: 20, cgas: 0, ggas: 10 },
receipts: vec![Receipt::call(Default::default(), Default::default(), 20, Default::default(), 0, 0, 0, 700, 700)].into(),
frames: vec![CallFrame::new(Default::default(), Default::default(), make_reg(&[(HP, 1000), (SP, 100), (SSP, 100), (CGAS, 10), (GGAS, 10)]), 10, 0, 0)],
frames: vec![CallFrame::new(Default::default(), Default::default(), make_reg(&[(HP, 1000), (SP, 100), (SSP, 100), (CGAS, 10), (GGAS, 10)]), 16, 0, 0)],
..Default::default()
})); "transfers with enough balance external"
)]
Expand All @@ -235,7 +232,7 @@ fn mem(set: &[(usize, Vec<u8>)]) -> Memory {
} => using check_output(Ok(Output{
reg: RegInput{hp: 1000, sp: 716, ssp: 716, fp: 100, pc: 700, is: 700, bal: 20, cgas: 10, ggas: 79 },
receipts: vec![Receipt::call(Default::default(), Default::default(), 20, Default::default(), 10, 0, 0, 700, 700)].into(),
frames: vec![CallFrame::new(Default::default(), Default::default(), make_reg(&[(HP, 1000), (SP, 100), (SSP, 100), (CGAS, 29), (GGAS, 79)]), 10, 0, 0)],
frames: vec![CallFrame::new(Default::default(), Default::default(), make_reg(&[(HP, 1000), (SP, 100), (SSP, 100), (CGAS, 29), (GGAS, 79)]), 16, 0, 0)],
..Default::default()
})); "forwards gas"
)]
Expand All @@ -254,7 +251,7 @@ fn mem(set: &[(usize, Vec<u8>)]) -> Memory {
} => using check_output(Ok(Output{
reg: RegInput{hp: 1000, sp: 716, ssp: 716, fp: 100, pc: 700, is: 700, bal: 20, cgas: 39, ggas: 79 },
receipts: vec![Receipt::call(Default::default(), Default::default(), 20, Default::default(), 39, 0, 0, 700, 700)].into(),
frames: vec![CallFrame::new(Default::default(), Default::default(), make_reg(&[(HP, 1000), (SP, 100), (SSP, 100), (CGAS, 0), (GGAS, 79)]), 10, 0, 0)],
frames: vec![CallFrame::new(Default::default(), Default::default(), make_reg(&[(HP, 1000), (SP, 100), (SSP, 100), (CGAS, 0), (GGAS, 79)]), 16, 0, 0)],
..Default::default()
})); "the receipt shows forwarded gas correctly when limited by available gas"
)]
Expand All @@ -273,7 +270,7 @@ fn mem(set: &[(usize, Vec<u8>)]) -> Memory {
} => using check_output(Ok(Output{
reg: RegInput{hp: 1000, sp: 716, ssp: 716, fp: 100, pc: 700, is: 700, bal: 20, cgas: 0, ggas: 10 },
receipts: vec![Receipt::call(Default::default(), Default::default(), 20, Default::default(), 0, 0, 0, 700, 700)].into(),
frames: vec![CallFrame::new(Default::default(), Default::default(), make_reg(&[(HP, 1000), (SP, 100), (SSP, 100), (CGAS, 10), (GGAS, 10)]), 10, 0, 0)],
frames: vec![CallFrame::new(Default::default(), Default::default(), make_reg(&[(HP, 1000), (SP, 100), (SSP, 100), (CGAS, 10), (GGAS, 10)]), 16, 0, 0)],
..Default::default()
})); "transfers with enough balance internal"
)]
Expand Down Expand Up @@ -423,44 +420,3 @@ fn check_output(
t => assert_eq!(t.0, t.1),
}
}

#[test_case(
CallFrame::new(
ContractId::from([1u8; 32]),
AssetId::from([2u8; 32]),
[1; VM_REGISTER_COUNT],
40,
4,
5,
),
Reg::<FP>::new(&0),
640
=> Ok(600); "call"
)]
fn test_write_call_to_memory(
call_frame: CallFrame,
fp: Reg<FP>,
len: usize,
) -> IoResult<Word, Infallible> {
let mut storage = MemoryStorage::default();
let code = vec![6u8; call_frame.code_size()];
StorageAsMut::storage::<ContractsRawCode>(&mut storage)
.insert(call_frame.to(), &code)
.unwrap();
let mut memory: Memory = vec![0u8; MEM_SIZE].try_into().unwrap();
let end = write_call_to_memory(&call_frame, fp, len, &mut memory, &storage)?;
check_memory(memory, call_frame, code);
Ok(end)
}

fn check_memory(memory: Memory, expected: CallFrame, code: Vec<u8>) {
let frame =
CallFrame::from_bytes(memory.read(0, CallFrame::serialized_size()).unwrap())
.unwrap();
assert_eq!(frame, expected);
assert_eq!(
&memory[CallFrame::serialized_size()
..(CallFrame::serialized_size() + frame.total_code_size())],
&code[..]
);
}

0 comments on commit 7335f9f

Please sign in to comment.