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(levm): fixes several bugs found when running the EF tests #1085

Open
wants to merge 34 commits into
base: levm_ef_tests
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
fb27487
Handle empty contract code when is CREATE
ilitteri Nov 5, 2024
3a278c8
Fix CALLDATACOPY
ilitteri Nov 5, 2024
2211ce4
Handle SWAP stack underflow
ilitteri Nov 5, 2024
68dbd2b
Fix CALLDATACOPY
ilitteri Nov 5, 2024
3f6ca94
Fix CREATE
ilitteri Nov 6, 2024
648b18e
Fix CREATE2
ilitteri Nov 6, 2024
f94b464
Fix CALLCODE
ilitteri Nov 6, 2024
420158f
Handle invalid opcodes
ilitteri Nov 6, 2024
8c55ccc
Fix SDIV & DIV
ilitteri Nov 6, 2024
597dd31
Fix negation
ilitteri Nov 6, 2024
589917b
Fix MSTORE
ilitteri Nov 6, 2024
b78c5e7
Fix CODECOPY
ilitteri Nov 6, 2024
8c16739
Fix CODECOPY v2
ilitteri Nov 6, 2024
1ab8cb0
Fix local stack overflow
ilitteri Nov 6, 2024
97e7fed
Fix RETURNDATACOPY
ilitteri Nov 6, 2024
26c62b6
Fix tx validation
ilitteri Nov 6, 2024
1d49dd2
Fix CODECOPY v3
ilitteri Nov 6, 2024
f1bd149
Fix DELEGATECALL
ilitteri Nov 6, 2024
0f97145
Fix STATICCALL
ilitteri Nov 6, 2024
fb2b184
Fix unbounded memory access
ilitteri Nov 6, 2024
12b2d94
Fix MSTORE out of bounds
ilitteri Nov 6, 2024
b1976bd
Improve EFTestsReport Display impl
ilitteri Nov 6, 2024
b660a82
Execute VM
ilitteri Nov 6, 2024
71963e9
Handle gas_used overflow
ilitteri Nov 6, 2024
f35a006
Improve EFTestsReport printing
ilitteri Nov 6, 2024
d233634
Merge branch 'levm_ef_tests' of github.com:lambdaclass/lambda_ethereu…
ilitteri Nov 6, 2024
7ed25ca
Start ensure_post_state implementation
ilitteri Nov 6, 2024
869393a
Merge branch 'levm_ef_tests' of github.com:lambdaclass/lambda_ethereu…
ilitteri Nov 6, 2024
02860db
Merge branch 'levm_ef_tests' of github.com:lambdaclass/lambda_ethereu…
ilitteri Nov 8, 2024
68daa73
Revert "Fix MSTORE out of bounds"
ilitteri Nov 8, 2024
a88e65b
Fix `op_swap`
ilitteri Nov 8, 2024
02b9c29
Derive Debug & Clone for OpcodeSuccess
ilitteri Nov 8, 2024
9dcc04e
Handle very large numbers in op_log
ilitteri Nov 8, 2024
d249c65
Reduce call frame depth
ilitteri Nov 8, 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
14 changes: 11 additions & 3 deletions crates/vm/levm/src/call_frame.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,12 @@ impl Stack {
self.stack.get(index).ok_or(VMError::StackUnderflow)
}

pub fn swap(&mut self, a: usize, b: usize) {
self.stack.swap(a, b)
pub fn swap(&mut self, a: usize, b: usize) -> Result<(), VMError> {
if a >= self.stack.len() || b >= self.stack.len() {
return Err(VMError::StackUnderflow);
}
self.stack.swap(a, b);
Ok(())
}
}

Expand Down Expand Up @@ -144,6 +148,10 @@ impl CallFrame {
}

fn opcode_at(&self, offset: usize) -> Option<Opcode> {
self.bytecode.get(offset).copied().map(Opcode::from)
self.bytecode
.get(offset)
.copied()
.map(Opcode::try_from)?
.ok()
}
}
3 changes: 3 additions & 0 deletions crates/vm/levm/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,11 @@ pub enum VMError {
ContractOutputTooBig,
InvalidInitialByte,
NonceOverflow,
MemoryLoadOutOfBounds,
GasLimitPriceProductOverflow,
}

#[derive(Debug, Clone)]
pub enum OpcodeSuccess {
Continue,
Result(ResultReason),
Expand Down
13 changes: 8 additions & 5 deletions crates/vm/levm/src/memory.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,20 +30,23 @@ impl Memory {
}
}

pub fn load(&mut self, offset: usize) -> U256 {
pub fn load(&mut self, offset: usize) -> Result<U256, VMError> {
self.resize(offset + 32);
let value_bytes: [u8; 32] = self
.data
.get(offset..offset + 32)
.unwrap()
.ok_or(VMError::MemoryLoadOutOfBounds)?
.try_into()
.unwrap();
U256::from(value_bytes)
Ok(U256::from(value_bytes))
}

pub fn load_range(&mut self, offset: usize, size: usize) -> Vec<u8> {
pub fn load_range(&mut self, offset: usize, size: usize) -> Result<Vec<u8>, VMError> {
self.resize(offset + size);
self.data.get(offset..offset + size).unwrap().into()
self.data
.get(offset..offset + size)
.ok_or(VMError::MemoryLoadOutOfBounds)
.map(|slice| slice.to_vec())
}

pub fn store_bytes(&mut self, offset: usize, value: &[u8]) {
Expand Down
13 changes: 10 additions & 3 deletions crates/vm/levm/src/opcode_handlers/arithmetic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,10 @@ impl VM {
current_call_frame.stack.push(U256::zero())?;
return Ok(OpcodeSuccess::Continue);
}
let quotient = dividend / divisor;
let Some(quotient) = dividend.checked_div(divisor) else {
current_call_frame.stack.push(U256::zero())?;
return Ok(OpcodeSuccess::Continue);
};
current_call_frame.stack.push(quotient)?;

Ok(OpcodeSuccess::Continue)
Expand Down Expand Up @@ -88,7 +91,10 @@ impl VM {
} else {
divisor
};
let quotient = dividend / divisor;
let Some(quotient) = dividend.checked_div(divisor) else {
current_call_frame.stack.push(U256::zero())?;
return Ok(OpcodeSuccess::Continue);
};
let quotient_is_negative = dividend_is_negative ^ divisor_is_negative;
let quotient = if quotient_is_negative {
negate(quotient)
Expand Down Expand Up @@ -264,5 +270,6 @@ fn abs(value: U256) -> U256 {

/// Negates a number in two's complement
fn negate(value: U256) -> U256 {
!value + U256::one()
let inverted = !value;
inverted.saturating_add(U256::one())
}
48 changes: 37 additions & 11 deletions crates/vm/levm/src/opcode_handlers/environment.rs
Original file line number Diff line number Diff line change
Expand Up @@ -150,17 +150,17 @@ impl VM {
.stack
.pop()?
.try_into()
.unwrap_or(usize::MAX);
.map_err(|_err| VMError::VeryLargeNumber)?;
let calldata_offset: usize = current_call_frame
.stack
.pop()?
.try_into()
.unwrap_or(usize::MAX);
.map_err(|_err| VMError::VeryLargeNumber)?;
let size: usize = current_call_frame
.stack
.pop()?
.try_into()
.unwrap_or(usize::MAX);
.map_err(|_err| VMError::VeryLargeNumber)?;

let minimum_word_size = (size + WORD_SIZE - 1) / WORD_SIZE;
let memory_expansion_cost = current_call_frame
Expand All @@ -176,10 +176,23 @@ impl VM {
return Ok(OpcodeSuccess::Continue);
}

let data = current_call_frame
.calldata
.slice(calldata_offset..calldata_offset + size);
current_call_frame.memory.store_bytes(dest_offset, &data);
// This check is because if offset is larger than the calldata then we should push 0 to the stack.
let result = if calldata_offset < current_call_frame.calldata.len() {
// Read calldata from offset to the end
let calldata = current_call_frame.calldata.slice(calldata_offset..);

// Get the 32 bytes from the data slice, padding with 0 if fewer than 32 bytes are available
let mut padded_calldata = vec![0u8; size];
let data_len_to_copy = calldata.len().min(size);

padded_calldata[..data_len_to_copy].copy_from_slice(&calldata[..data_len_to_copy]);

padded_calldata
} else {
vec![0u8; size]
};

current_call_frame.memory.store_bytes(dest_offset, &result);

Ok(OpcodeSuccess::Continue)
}
Expand Down Expand Up @@ -235,7 +248,14 @@ impl VM {

self.increase_consumed_gas(current_call_frame, gas_cost)?;

let code = current_call_frame.bytecode.slice(offset..offset + size);
let bytecode_len = current_call_frame.bytecode.len();
let code = if offset < bytecode_len {
current_call_frame
.bytecode
.slice(offset..(offset + size).min(bytecode_len))
} else {
vec![0u8; size].into()
};

current_call_frame.memory.store_bytes(dest_offset, &code);

Expand Down Expand Up @@ -385,9 +405,15 @@ impl VM {
return Ok(OpcodeSuccess::Continue);
}

let data = current_call_frame
.sub_return_data
.slice(returndata_offset..returndata_offset + size);
let sub_return_data_len = current_call_frame.sub_return_data.len();
let data = if returndata_offset < sub_return_data_len {
current_call_frame
.sub_return_data
.slice(returndata_offset..(returndata_offset + size).min(sub_return_data_len))
} else {
vec![0u8; size].into()
};

current_call_frame.memory.store_bytes(dest_offset, &data);

Ok(OpcodeSuccess::Continue)
Expand Down
17 changes: 9 additions & 8 deletions crates/vm/levm/src/opcode_handlers/exchange.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,18 +18,19 @@ impl VM {
) -> Result<OpcodeSuccess, VMError> {
self.increase_consumed_gas(current_call_frame, gas_cost::SWAPN)?;

let depth = (op as u8) - (Opcode::SWAP1 as u8) + 1;

if current_call_frame.stack.len() < depth as usize {
return Err(VMError::StackUnderflow);
}
let stack_top_index = current_call_frame.stack.len();
let depth = op as u8 - Opcode::SWAP1 as u8 + 1;
let stack_top_index = current_call_frame
.stack
.len()
.checked_sub(1)
.ok_or(VMError::StackUnderflow)?;
let to_swap_index = stack_top_index
.checked_sub(depth as usize)
.checked_sub(depth.into())
.ok_or(VMError::StackUnderflow)?;

current_call_frame
.stack
.swap(stack_top_index - 1, to_swap_index - 1);
.swap(stack_top_index, to_swap_index)?;

Ok(OpcodeSuccess::Continue)
}
Expand Down
2 changes: 1 addition & 1 deletion crates/vm/levm/src/opcode_handlers/keccak.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ impl VM {

self.increase_consumed_gas(current_call_frame, gas_cost)?;

let value_bytes = current_call_frame.memory.load_range(offset, size);
let value_bytes = current_call_frame.memory.load_range(offset, size)?;

let mut hasher = Keccak256::new();
hasher.update(value_bytes);
Expand Down
6 changes: 3 additions & 3 deletions crates/vm/levm/src/opcode_handlers/logging.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,12 @@ impl VM {
.stack
.pop()?
.try_into()
.unwrap_or(usize::MAX);
.map_err(|_err| VMError::VeryLargeNumber)?;
let size = current_call_frame
.stack
.pop()?
.try_into()
.unwrap_or(usize::MAX);
.map_err(|_err| VMError::VeryLargeNumber)?;
let mut topics = Vec::new();
for _ in 0..number_of_topics {
let topic = current_call_frame.stack.pop()?;
Expand All @@ -49,7 +49,7 @@ impl VM {

self.increase_consumed_gas(current_call_frame, gas_cost)?;

let data = current_call_frame.memory.load_range(offset, size);
let data = current_call_frame.memory.load_range(offset, size)?;
let log = Log {
address: current_call_frame.msg_sender, // Should change the addr if we are on a Call/Create transaction (Call should be the contract we are calling, Create should be the original caller)
topics,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ impl VM {

self.increase_consumed_gas(current_call_frame, gas_cost)?;

let value = current_call_frame.memory.load(offset);
let value = current_call_frame.memory.load(offset)?;
current_call_frame.stack.push(value)?;

Ok(OpcodeSuccess::Continue)
Expand All @@ -82,7 +82,11 @@ impl VM {
&mut self,
current_call_frame: &mut CallFrame,
) -> Result<OpcodeSuccess, VMError> {
let offset = current_call_frame.stack.pop()?.try_into().unwrap();
let offset = current_call_frame
.stack
.pop()?
.try_into()
.map_err(|_err| VMError::VeryLargeNumber)?;
let memory_expansion_cost = current_call_frame
.memory
.expansion_cost(offset + WORD_SIZE)?;
Expand Down
84 changes: 66 additions & 18 deletions crates/vm/levm/src/opcode_handlers/system.rs
Original file line number Diff line number Diff line change
Expand Up @@ -102,10 +102,26 @@ impl VM {
let gas = current_call_frame.stack.pop()?;
let code_address = word_to_address(current_call_frame.stack.pop()?);
let value = current_call_frame.stack.pop()?;
let args_offset = current_call_frame.stack.pop()?.try_into().unwrap();
let args_size = current_call_frame.stack.pop()?.try_into().unwrap();
let ret_offset = current_call_frame.stack.pop()?.try_into().unwrap();
let ret_size = current_call_frame.stack.pop()?.try_into().unwrap();
let args_offset = current_call_frame
.stack
.pop()?
.try_into()
.map_err(|_err| VMError::VeryLargeNumber)?;
let args_size = current_call_frame
.stack
.pop()?
.try_into()
.map_err(|_err| VMError::VeryLargeNumber)?;
let ret_offset = current_call_frame
.stack
.pop()?
.try_into()
.map_err(|_err| VMError::VeryLargeNumber)?;
let ret_size = current_call_frame
.stack
.pop()?
.try_into()
.map_err(|_err| VMError::VeryLargeNumber)?;

// Sender and recipient are the same in this case. But the code executed is from another account.
let msg_sender = current_call_frame.to;
Expand Down Expand Up @@ -148,7 +164,7 @@ impl VM {

self.increase_consumed_gas(current_call_frame, gas_cost)?;

let return_data = current_call_frame.memory.load_range(offset, size).into();
let return_data = current_call_frame.memory.load_range(offset, size)?.into();
current_call_frame.returndata = return_data;
current_call_frame
.stack
Expand All @@ -165,10 +181,26 @@ impl VM {
) -> Result<OpcodeSuccess, VMError> {
let gas = current_call_frame.stack.pop()?;
let code_address = word_to_address(current_call_frame.stack.pop()?);
let args_offset = current_call_frame.stack.pop()?.try_into().unwrap();
let args_size = current_call_frame.stack.pop()?.try_into().unwrap();
let ret_offset = current_call_frame.stack.pop()?.try_into().unwrap();
let ret_size = current_call_frame.stack.pop()?.try_into().unwrap();
let args_offset = current_call_frame
.stack
.pop()?
.try_into()
.map_err(|_err| VMError::VeryLargeNumber)?;
let args_size = current_call_frame
.stack
.pop()?
.try_into()
.map_err(|_err| VMError::VeryLargeNumber)?;
let ret_offset = current_call_frame
.stack
.pop()?
.try_into()
.map_err(|_err| VMError::VeryLargeNumber)?;
let ret_size = current_call_frame
.stack
.pop()?
.try_into()
.map_err(|_err| VMError::VeryLargeNumber)?;

let msg_sender = current_call_frame.msg_sender;
let value = current_call_frame.msg_value;
Expand Down Expand Up @@ -199,10 +231,26 @@ impl VM {
) -> Result<OpcodeSuccess, VMError> {
let gas = current_call_frame.stack.pop()?;
let code_address = word_to_address(current_call_frame.stack.pop()?);
let args_offset = current_call_frame.stack.pop()?.try_into().unwrap();
let args_size = current_call_frame.stack.pop()?.try_into().unwrap();
let ret_offset = current_call_frame.stack.pop()?.try_into().unwrap();
let ret_size = current_call_frame.stack.pop()?.try_into().unwrap();
let args_offset = current_call_frame
.stack
.pop()?
.try_into()
.map_err(|_err| VMError::VeryLargeNumber)?;
let args_size = current_call_frame
.stack
.pop()?
.try_into()
.map_err(|_err| VMError::VeryLargeNumber)?;
let ret_offset = current_call_frame
.stack
.pop()?
.try_into()
.map_err(|_err| VMError::VeryLargeNumber)?;
let ret_size = current_call_frame
.stack
.pop()?
.try_into()
.map_err(|_err| VMError::VeryLargeNumber)?;

let value = U256::zero();
let msg_sender = current_call_frame.to; // The new sender will be the current contract.
Expand Down Expand Up @@ -231,8 +279,8 @@ impl VM {
current_call_frame: &mut CallFrame,
) -> Result<OpcodeSuccess, VMError> {
let value_in_wei_to_send = current_call_frame.stack.pop()?;
let code_offset_in_memory = current_call_frame.stack.pop()?.try_into().unwrap();
let code_size_in_memory = current_call_frame.stack.pop()?.try_into().unwrap();
let code_offset_in_memory = current_call_frame.stack.pop()?;
let code_size_in_memory = current_call_frame.stack.pop()?;

self.create(
value_in_wei_to_send,
Expand All @@ -250,8 +298,8 @@ impl VM {
current_call_frame: &mut CallFrame,
) -> Result<OpcodeSuccess, VMError> {
let value_in_wei_to_send = current_call_frame.stack.pop()?;
let code_offset_in_memory = current_call_frame.stack.pop()?.try_into().unwrap();
let code_size_in_memory = current_call_frame.stack.pop()?.try_into().unwrap();
let code_offset_in_memory = current_call_frame.stack.pop()?;
let code_size_in_memory = current_call_frame.stack.pop()?;
let salt = current_call_frame.stack.pop()?;

self.create(
Expand Down Expand Up @@ -281,7 +329,7 @@ impl VM {

self.increase_consumed_gas(current_call_frame, gas_cost)?;

current_call_frame.returndata = current_call_frame.memory.load_range(offset, size).into();
current_call_frame.returndata = current_call_frame.memory.load_range(offset, size)?.into();

Err(VMError::RevertOpcode)
}
Expand Down
Loading
Loading