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

Add a new mode 2 to the LDC that allows to use the memory as a source for code #849

Merged
merged 27 commits into from
Oct 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
de86a1a
allow ldc in predicates, still need to deal with storage and checked txs
Voxelot Oct 4, 2024
4225346
checkpoint, plumbing storage and refactoring generics
Voxelot Oct 4, 2024
b4c4da3
plumbing of generics happy, now onto all the tests
Voxelot Oct 4, 2024
b0d0302
fix serde error in fuel-crypto
Voxelot Oct 5, 2024
340f401
expose predicate storage module
Voxelot Oct 5, 2024
2da51b1
avoid supertrait
Voxelot Oct 5, 2024
3073daa
removing clone constraint, trying to reason through the async issues
Voxelot Oct 5, 2024
74e7c3f
removed clone, made async happy
Voxelot Oct 5, 2024
0b987e2
more alloc!
Voxelot Oct 5, 2024
bba89e4
disable alloc but keep serde in fuel-crypto
Voxelot Oct 5, 2024
e715fb1
Revert "disable alloc but keep serde in fuel-crypto"
Voxelot Oct 5, 2024
090636c
battle of the feature flags
Voxelot Oct 5, 2024
39eb13e
enable BSIZ and BLDD
Voxelot Oct 5, 2024
156f2a8
Fixed compilation for tests.
xgreenx Oct 5, 2024
4c43718
Removed `StorageUnavailable`
xgreenx Oct 5, 2024
d62ee4d
Updated CHANGELOG.md
xgreenx Oct 5, 2024
6acc4ca
Add a new mode `2` to the LDC that allows to use the memory as a sour…
xgreenx Oct 5, 2024
ce1603a
Added more tests for mode 2
xgreenx Oct 5, 2024
a564f3f
Update fuel-asm/src/lib.rs
xgreenx Oct 5, 2024
2a32a64
Updated CHANGELOG.md
xgreenx Oct 5, 2024
5bbf301
Merge remote-tracking branch 'origin/feature/ldc-mode-2' into feature…
xgreenx Oct 5, 2024
1b01a9d
Added tests that contract LDC is not allowed
xgreenx Oct 5, 2024
4289674
Merge branch 'refs/heads/feature/predicate-ldc' into feature/ldc-mode-2
xgreenx Oct 5, 2024
24e5b21
Merge branch 'refs/heads/master' into feature/ldc-mode-2
xgreenx Oct 5, 2024
d1c6283
Return back removed CHANGELOG.md
xgreenx Oct 5, 2024
4e2d0df
Fix the comment
xgreenx Oct 5, 2024
65c78d3
Apply comments
xgreenx Oct 5, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
## [Unreleased]

### Added
- [#849](https://github.com/FuelLabs/fuel-vm/pull/849): Add a new mode `2` to the LDC that allows to use the memory as a source for code.
- [#848](https://github.com/FuelLabs/fuel-vm/pull/848): Allow usage of the blob opcode `BSIZ`, `BLDD`, and `LDC` with mode `1` in the predicates.
- [#838](https://github.com/FuelLabs/fuel-vm/pull/838): Implemented `AsRef<[u8]>` and `TryFrom<&[u8]>` for DA compression types: ScriptCode, PredicateCode, RegistryKey.
- [#820](https://github.com/FuelLabs/fuel-vm/pull/820): Add fuzzing in CI with ClusterFuzzLite.
Expand Down
4 changes: 2 additions & 2 deletions fuel-asm/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -182,8 +182,8 @@ impl_instructions! {
0x30 CSIZ csiz [dst: RegId contract_id_addr: RegId]
"Get current block proposer's address."
0x31 CB cb [dst: RegId]
"Load a contract's code as executable."
0x32 LDC ldc [contract_id_addr: RegId offset: RegId len: RegId mode: Imm06]
"Load code as executable either from contract, blob, or memory."
0x32 LDC ldc [src_addr: RegId offset: RegId len: RegId mode: Imm06]
"Log an event."
0x33 LOG log [a: RegId b: RegId c: RegId d: RegId]
"Log data."
Expand Down
98 changes: 90 additions & 8 deletions fuel-vm/src/interpreter/blockchain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ where
/// ```
pub(crate) fn load_contract_code(
&mut self,
id_addr: Word,
addr: Word,
offset: Word,
length_unpadded: Word,
mode: Imm06,
Expand Down Expand Up @@ -152,8 +152,9 @@ where
};

match mode.to_u8() {
0 => input.load_contract_code(id_addr, offset, length_unpadded),
1 => input.load_blob_code(id_addr, offset, length_unpadded),
0 => input.load_contract_code(addr, offset, length_unpadded),
1 => input.load_blob_code(addr, offset, length_unpadded),
2 => input.load_memory_code(addr, offset, length_unpadded),
_ => Err(PanicReason::InvalidImmediateValue.into()),
}
}
Expand Down Expand Up @@ -585,7 +586,6 @@ where
/// contract_code = contracts[contract_id]
/// mem[$ssp, $rC] = contract_code[$rB, $rC]
/// ```
/// Returns the total length of the contract code that was loaded from storage.
pub(crate) fn load_contract_code(
mut self,
contract_id_addr: Word,
Expand Down Expand Up @@ -684,11 +684,10 @@ where
/// Loads blob ID pointed by `a`, and then for that blob,
/// copies `c` bytes from it starting from offset `b` into the stack.
/// ```txt
/// contract_id = mem[$rA, 32]
/// contract_code = contracts[contract_id]
/// mem[$ssp, $rC] = contract_code[$rB, $rC]
/// blob_id = mem[$rA, 32]
/// blob_code = blobs[blob_id]
/// mem[$ssp, $rC] = blob_code[$rB, $rC]
/// ```
/// Returns the total length of the contract code that was loaded from storage.
pub(crate) fn load_blob_code(
mut self,
blob_id_addr: Word,
Expand Down Expand Up @@ -778,6 +777,89 @@ where

Ok(())
}

/// Copies `c` bytes from starting the memory `a` and offset `b` into the
/// stack.
///
/// ```txt
/// mem[$ssp, $rC] = memory[$rA + $rB, $rC]
/// ```
pub(crate) fn load_memory_code(
mut self,
input_src_addr: Word,
input_offset: Word,
length_unpadded: Word,
) -> IoResult<(), S::DataError>
where
S: InterpreterStorage,
{
let ssp = *self.ssp;
let sp = *self.sp;
let dst = ssp;

if ssp != sp {
return Err(PanicReason::ExpectedUnallocatedStack.into())
}

if length_unpadded == 0 {
inc_pc(self.pc)?;
return Ok(())
}

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

let length = bytes::padded_len_word(length_unpadded).unwrap_or(Word::MAX);

// Fetch the storage blob
let profiler = ProfileGas {
pc: self.pc.as_ref(),
is: self.is,
current_contract,
profiler: self.profiler,
};
let charge_len = length;
dependent_gas_charge_without_base(
self.cgas,
self.ggas,
profiler,
self.gas_cost,
charge_len,
)?;

let new_sp = ssp.saturating_add(length);
self.memory.grow_stack(new_sp)?;

// Set up ownership registers for the copy using old ssp
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)?;

// Mark stack space as allocated
*self.sp = new_sp;
*self.ssp = new_sp;

// 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");
let new_code_size = old_code_size
.checked_add(length as Word)
.ok_or(PanicReason::MemoryOverflow)?;

self.memory
.write_bytes_noownerchecks(code_size_ptr, new_code_size.to_be_bytes())?;
}

inc_pc(self.pc)?;

Ok(())
}
}

struct BurnCtx<'vm, S> {
Expand Down
93 changes: 66 additions & 27 deletions fuel-vm/src/interpreter/memory.rs
Original file line number Diff line number Diff line change
Expand Up @@ -326,23 +326,74 @@ impl MemoryInstance {
Ok(())
}

/// Copies the memory from `src` to `dst`.
/// Copies the memory from `src` to `dst` verifying ownership.
#[inline]
#[track_caller]
pub fn memcopy_noownerchecks<A: ToAddr, B: ToAddr, C: ToAddr>(
pub fn memcopy(
&mut self,
dst: A,
src: B,
len: C,
dst: Word,
src: Word,
length: Word,
owner: OwnershipRegisters,
) -> Result<(), PanicReason> {
// TODO: Optimize
let dst_range = self.verify(dst, length)?;
let src_range = self.verify(src, length)?;

if dst_range.start() <= src_range.start() && src_range.start() < dst_range.end()
|| src_range.start() <= dst_range.start()
&& dst_range.start() < src_range.end()
|| dst_range.start() < src_range.end() && src_range.end() <= dst_range.end()
|| src_range.start() < dst_range.end() && dst_range.end() <= src_range.end()
{
return Err(PanicReason::MemoryWriteOverlap)
}

let src = src.to_addr()?;
let dst = dst.to_addr()?;
let len = len.to_addr()?;
owner.verify_ownership(&dst_range)?;

if src_range.end() <= self.stack.len() {
Copy link
Member

Choose a reason for hiding this comment

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

Is all the following validation logic new?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I moved the logic from self.read(src, len) and self.write_noownerchecks(dst, len)? here to avoid to_vec for tmp slice

if dst_range.end() <= self.stack.len() {
self.stack
.copy_within(src_range.usizes(), dst_range.start());
} else if dst_range.start() >= self.heap_offset() {
#[allow(clippy::arithmetic_side_effects)]
// Safety: subtractions are checked above
let dst_start = dst_range.start() - self.heap_offset();
#[allow(clippy::arithmetic_side_effects)]
// Safety: subtractions are checked above
let dst_end = dst_range.end() - self.heap_offset();

let src_array = &self.stack[src_range.usizes()];
let dst_array = &mut self.heap[dst_start..dst_end];
dst_array.copy_from_slice(src_array);
} else {
unreachable!("Range was verified to be valid")
}
} else if src_range.start() >= self.heap_offset() {
#[allow(clippy::arithmetic_side_effects)]
// Safety: subtractions are checked above
let src_start = src_range.start() - self.heap_offset();
#[allow(clippy::arithmetic_side_effects)]
// Safety: subtractions are checked above
let src_end = src_range.end() - self.heap_offset();

if dst_range.end() <= self.stack.len() {
let src_array = &self.heap[src_start..src_end];

let dst_array = &mut self.stack[dst_range.usizes()];
dst_array.copy_from_slice(src_array);
} else if dst_range.start() >= self.heap_offset() {
#[allow(clippy::arithmetic_side_effects)]
// Safety: subtractions are checked above
let dst_start = dst_range.start() - self.heap_offset();

self.heap.copy_within(src_start..src_end, dst_start);
} else {
unreachable!("Range was verified to be valid")
}
} else {
unreachable!("Range was verified to be valid")
}

let tmp = self.read(src, len)?.to_vec();
self.write_noownerchecks(dst, len)?.copy_from_slice(&tmp);
Ok(())
}

Expand Down Expand Up @@ -857,23 +908,11 @@ pub(crate) fn memcopy(
memory: &mut MemoryInstance,
owner: OwnershipRegisters,
pc: RegMut<PC>,
a: Word,
b: Word,
c: Word,
dst: Word,
src: Word,
length: Word,
) -> SimpleResult<()> {
let dst_range = memory.verify(a, c)?;
let src_range = memory.verify(b, c)?;

if dst_range.start() <= src_range.start() && src_range.start() < dst_range.end()
|| src_range.start() <= dst_range.start() && dst_range.start() < src_range.end()
|| dst_range.start() < src_range.end() && src_range.end() <= dst_range.end()
|| src_range.start() < dst_range.end() && dst_range.end() <= src_range.end()
{
return Err(PanicReason::MemoryWriteOverlap.into())
}

owner.verify_ownership(&dst_range)?;
memory.memcopy_noownerchecks(a, b, c)?;
memory.memcopy(dst, src, length, owner)?;

Ok(inc_pc(pc)?)
}
Expand Down
78 changes: 76 additions & 2 deletions fuel-vm/src/predicate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -321,7 +321,7 @@ mod tests {
Ok(()),
),
(
// PC exceeding predicate bounds
// Use `LDC` with mode `1` to load the blob into the predicate.
vec![
// Allocate 32 byte on the heap.
op::movi(0x10, 32),
Expand All @@ -345,6 +345,42 @@ mod tests {
CORRECT_GAS,
Ok(()),
),
(
// Use `LDC` with mode `2` to load the part of the predicate from the
// transaction.
vec![
// Skip the return opcodes. One of two opcodes is a good opcode that
// returns `0x1`. This opcode is our source for the `LDC`
// opcode. We will copy return good opcode to the end
// of the `ssp` via `LDC`. And jump there to
// return `true` from the predicate.
op::jmpf(ZERO, 2),
// Bad return opcode that we want to skip.
op::ret(0x0),
// Good return opcode that we want to use for the `LDC`.
op::ret(0x1),
// Take the start of the code and move it for 2 opcodes to get the
// desired opcode to copy.
op::move_(0x10, IS),
// We don't need to copy `jmpf` and bad `ret` opcodes via `LDC`.
op::addi(0x10, 0x10, 2 * Instruction::SIZE as u16),
// Store end of the code
op::move_(0x12, SSP),
// Subtract the start of the code from the end of the code
op::sub(0x12, 0x12, IS),
// Divide the code by the instruction size to get the number of
// instructions
op::divi(0x12, 0x12, Instruction::SIZE as u16),
// We want to load only on good `ret` opcode.
op::movi(0x11, Instruction::SIZE as u32),
// Load the code from the memory address `0x10` with the `0x11` size
op::ldc(0x10, ZERO, 0x11, 2),
// Jump to a new code location
op::jmp(0x12),
],
CORRECT_GAS,
Ok(()),
),
];

assert_inputs_are_validated_for_predicates(inputs, good_blob)
Expand Down Expand Up @@ -399,7 +435,7 @@ mod tests {
)),
),
(
// PC exceeding predicate bounds
// Use `LDC` with mode `1` to load the blob into the predicate.
vec![
// Allocate 32 byte on the heap.
op::movi(0x10, 32),
Expand All @@ -425,6 +461,44 @@ mod tests {
PanicReason::PredicateReturnedNonOne,
)),
),
(
// Use `LDC` with mode `2` to load the part of the predicate from the
// transaction.
vec![
// Skip the return opcodes. One of two opcodes is a bad opcode that
// returns `0x0`. This opcode is our source for the `LDC`
// opcode. We will copy return bad opcode to the end
// of the `ssp` via `LDC`. And jump there to
// return `false` from the predicate adn fail.
op::jmpf(ZERO, 2),
// Good return opcode that we want to skip.
op::ret(0x1),
// Bad return opcode that we want to use for the `LDC`.
op::ret(0x0),
// Take the start of the code and move it for 2 opcodes to get the
// desired opcode to copy.
op::move_(0x10, IS),
// We don't need to copy `jmpf` and bad `ret` opcodes via `LDC`.
op::addi(0x10, 0x10, 2 * Instruction::SIZE as u16),
// Store end of the code
op::move_(0x12, SSP),
// Subtract the start of the code from the end of the code
op::sub(0x12, 0x12, IS),
// Divide the code by the instruction size to get the number of
// instructions
op::divi(0x12, 0x12, Instruction::SIZE as u16),
// We want to load only on bad `ret` opcode.
op::movi(0x11, Instruction::SIZE as u32),
// Load the code from the memory address `0x10` with the `0x11` size
op::ldc(0x10, ZERO, 0x11, 2),
// Jump to a new code location
op::jmp(0x12),
],
CORRECT_GAS,
Err(PredicateVerificationFailed::Panic(
PanicReason::PredicateReturnedNonOne,
)),
),
];

assert_inputs_are_validated_for_predicates(inputs, bad_blob)
Expand Down
Loading
Loading