Skip to content

Commit

Permalink
feat(levm): opcodes revert invalid selfdestruct (#946)
Browse files Browse the repository at this point in the history
**Motivation**

<!-- Why does this pull request exist? What are its goals? -->
Implement opcodes

This PR is going to be resumed when Db changes are merged to main.

**Description**

<!-- A clear and concise general description of the changes this PR
introduces -->
- Opcodes Revert, Invalid and Selfdestruct implementation

Caveat:
- Revert opcode has yet to be tested in different scenarios. Most
important ones are CREATE and XCALL. This is linked in issue #1061


<!-- Link to issues: Resolves #111, Resolves #222 -->

Closes #535 
Closes #536 
Closes #537
  • Loading branch information
JereSalo authored Nov 4, 2024
1 parent 7a07e51 commit 5ded681
Show file tree
Hide file tree
Showing 9 changed files with 248 additions and 57 deletions.
2 changes: 1 addition & 1 deletion crates/vm/levm/docs/substate.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
## Substate

`accessed_addresses` and `accessed_storage_keys` follow the structure defined in [EIP 2929](https://eips.ethereum.org/EIPS/eip-2929#specification)
`accessed_addresses` and `accessed_storage_keys` belong to the Substate but in our VM implementation they are not there because we already know what the warm addresses and storage keys are by looking at the `Cache` structure.
3 changes: 3 additions & 0 deletions crates/vm/levm/src/constants.rs
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,9 @@ pub mod gas_cost {
pub const CODECOPY_DYNAMIC_BASE: U256 = U256([3, 0, 0, 0]);
pub const GASPRICE: U256 = U256([2, 0, 0, 0]);
pub const EXTCODECOPY_DYNAMIC_BASE: U256 = U256([3, 0, 0, 0]);
pub const SELFDESTRUCT_STATIC: U256 = U256([5000, 0, 0, 0]);
pub const SELFDESTRUCT_DYNAMIC: U256 = U256([25000, 0, 0, 0]);
pub const COLD_ADDRESS_ACCESS_COST: U256 = U256([2600, 0, 0, 0]);
}

// Costs in gas for call opcodes (in wei)
Expand Down
2 changes: 1 addition & 1 deletion crates/vm/levm/src/db.rs
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ impl Database for Db {
}
}

#[derive(Debug, Default, Clone)]
#[derive(Debug, Default, Clone, Eq, PartialEq)]
pub struct Cache {
pub accounts: HashMap<Address, Account>,
}
Expand Down
5 changes: 4 additions & 1 deletion crates/vm/levm/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,11 @@ pub enum VMError {
OverflowInArithmeticOp,
FatalError,
InvalidTransaction,
RevertOpcode,
InvalidOpcode,
MissingBlobHashes,
BlobHashIndexOutOfBounds,
RevertOpcode,
SenderAccountDoesNotExist,
AddressDoesNotMatchAnAccount,
SenderAccountShouldNotHaveBytecode,
SenderBalanceShouldContainTransferValue,
Expand All @@ -40,6 +42,7 @@ pub enum ResultReason {
Stop,
Revert,
Return,
SelfDestruct,
}

#[derive(Debug, Clone, PartialEq, Eq)]
Expand Down
104 changes: 101 additions & 3 deletions crates/vm/levm/src/opcode_handlers/system.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
use crate::{
call_frame::CallFrame,
constants::{call_opcode, SUCCESS_FOR_RETURN},
constants::{call_opcode, gas_cost, SUCCESS_FOR_RETURN},
errors::{OpcodeSuccess, ResultReason, VMError},
vm::VM,
vm::{word_to_address, VM},
};
use ethereum_rust_core::{Address, U256};
use ethereum_rust_core::{types::TxKind, Address, U256};

// System Operations (10)
// Opcodes: CREATE, CALL, CALLCODE, RETURN, DELEGATECALL, CREATE2, STATICCALL, REVERT, INVALID, SELFDESTRUCT
Expand Down Expand Up @@ -39,6 +39,10 @@ impl VM {
.try_into()
.unwrap_or(usize::MAX);

if current_call_frame.is_static && !value.is_zero() {
return Err(VMError::OpcodeNotAllowedInStaticContext);
}

let memory_byte_size = (args_offset + args_size).max(ret_offset + ret_size);
let memory_expansion_cost = current_call_frame.memory.expansion_cost(memory_byte_size)?;

Expand Down Expand Up @@ -253,4 +257,98 @@ impl VM {
current_call_frame,
)
}

// REVERT operation
pub fn op_revert(
&mut self,
current_call_frame: &mut CallFrame,
) -> Result<OpcodeSuccess, VMError> {
// Description: Gets values from stack, calculates gas cost and sets return data.
// Returns: VMError RevertOpcode if executed correctly.
// Notes:
// The actual reversion of changes is made in the execute() function.

let offset = current_call_frame.stack.pop()?.as_usize();

let size = current_call_frame.stack.pop()?.as_usize();

let gas_cost = current_call_frame.memory.expansion_cost(offset + size)?;

self.increase_consumed_gas(current_call_frame, gas_cost)?;

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

Err(VMError::RevertOpcode)
}

/// ### INVALID operation
/// Reverts consuming all gas, no return data.
pub fn op_invalid(&mut self) -> Result<OpcodeSuccess, VMError> {
Err(VMError::InvalidOpcode)
}

// SELFDESTRUCT operation
pub fn op_selfdestruct(
&mut self,
current_call_frame: &mut CallFrame,
) -> Result<OpcodeSuccess, VMError> {
// Sends all ether in the account to the target address
// Steps:
// 1. Pop the target address from the stack
// 2. Get current account and: Store the balance in a variable, set it's balance to 0
// 3. Get the target account, checking if it is empty and if it is cold. Update gas cost accordingly.
// 4. Add the balance of the current account to the target account
// 5. Register account to be destroyed in accrued substate.

// Notes:
// If context is Static, return error.
// If executed in the same transaction a contract was created, the current account is registered to be destroyed
if current_call_frame.is_static {
return Err(VMError::OpcodeNotAllowedInStaticContext);
}

// Gas costs variables
let static_gas_cost = gas_cost::SELFDESTRUCT_STATIC;
let dynamic_gas_cost = gas_cost::SELFDESTRUCT_DYNAMIC;
let cold_gas_cost = gas_cost::COLD_ADDRESS_ACCESS_COST;
let mut gas_cost = static_gas_cost;

// 1. Pop the target address from the stack
let target_address = word_to_address(current_call_frame.stack.pop()?);

// 2. Get current account and: Store the balance in a variable, set it's balance to 0
let mut current_account = self.get_account(&current_call_frame.to);
let current_account_balance = current_account.info.balance;

current_account.info.balance = U256::zero();

// 3 & 4. Get target account and add the balance of the current account to it
// TODO: If address is cold, there is an additional cost of 2600.
if !self.cache.is_account_cached(&target_address) {
gas_cost += cold_gas_cost;
}

let mut target_account = self.get_account(&target_address);
if target_account.is_empty() {
gas_cost += dynamic_gas_cost;
}
target_account.info.balance += current_account_balance;

// 5. Register account to be destroyed in accrued substate IF executed in the same transaction a contract was created
if self.tx_kind == TxKind::Create {
self.accrued_substate
.selfdestrutct_set
.insert(current_call_frame.to);
}
// Accounts in SelfDestruct set should be destroyed at the end of the transaction.

// Update cache after modifying accounts.
self.cache
.add_account(&current_call_frame.to, &current_account);
self.cache.add_account(&target_address, &target_account);

self.increase_consumed_gas(current_call_frame, gas_cost)?;

Ok(OpcodeSuccess::Result(ResultReason::SelfDestruct))
}
}
9 changes: 6 additions & 3 deletions crates/vm/levm/src/opcodes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -165,9 +165,9 @@ pub enum Opcode {
DELEGATECALL = 0xF4,
CREATE2 = 0xF5,
STATICCALL = 0xFA,
// REVERT = 0xFD,
// INVALID = 0xFE,
// SELFDESTRUCT = 0xFF,
REVERT = 0xFD,
INVALID = 0xFE,
SELFDESTRUCT = 0xFF,
}

impl Copy for Opcode {}
Expand Down Expand Up @@ -321,6 +321,9 @@ impl From<u8> for Opcode {
0xF5 => Opcode::CREATE2,
0xF4 => Opcode::DELEGATECALL,
0xFA => Opcode::STATICCALL,
0xFD => Opcode::REVERT,
0xFE => Opcode::INVALID,
0xFF => Opcode::SELFDESTRUCT,
_ => panic!("Unknown opcode: 0x{:02X}", byte),
}
}
Expand Down
12 changes: 6 additions & 6 deletions crates/vm/levm/src/operations.rs
Original file line number Diff line number Diff line change
Expand Up @@ -85,9 +85,9 @@ pub enum Operation {
DelegateCall,
Create2,
StaticCall,
// Revert,
// Invalid,
// SelfDestruct,
Revert,
Invalid,
SelfDestruct,
}

impl Operation {
Expand Down Expand Up @@ -201,9 +201,9 @@ impl Operation {
Operation::DelegateCall => Bytes::copy_from_slice(&[Opcode::DELEGATECALL as u8]),
Operation::Create2 => Bytes::copy_from_slice(&[Opcode::CREATE2 as u8]),
Operation::StaticCall => Bytes::copy_from_slice(&[Opcode::STATICCALL as u8]),
// Operation::Revert => Bytes::copy_from_slice(&[Opcode::REVERT as u8]),
// Operation::Invalid => Bytes::copy_from_slice(&[Opcode::INVALID as u8]),
// Operation::SelfDestruct => Bytes::copy_from_slice(&[Opcode::SELFDESTRUCT as u8]),
Operation::Revert => Bytes::copy_from_slice(&[Opcode::REVERT as u8]),
Operation::Invalid => Bytes::copy_from_slice(&[Opcode::INVALID as u8]),
Operation::SelfDestruct => Bytes::copy_from_slice(&[Opcode::SELFDESTRUCT as u8]),
}
}
}
Loading

0 comments on commit 5ded681

Please sign in to comment.