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

chore: bump revm dec 2024 #246

Open
wants to merge 19 commits into
base: main
Choose a base branch
from
20 changes: 18 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,13 @@ alloy-rpc-types-eth = "0.9"
alloy-rpc-types-trace = "0.9"
alloy-sol-types = "0.8"
alloy-primitives = { version = "0.8", features = ["map"] }
revm = { version = "19.0.0", default-features = false, features = ["std"] }
revm = { git = "https://github.com/bluealloy/revm.git", rev = "a8f9824", default-features = false, features = [
"std",
] }
revm-inspector = { git = "https://github.com/bluealloy/revm.git", rev = "a8f9824", default-features = false, features = [
"std",
"serde-json",
] }

anstyle = { version = "1.0", optional = true }
colorchoice = "1.0"
Expand All @@ -50,9 +56,19 @@ boa_gc = { version = "0.20", optional = true }

[dev-dependencies]
snapbox = { version = "0.6", features = ["term-svg"] }
revm-database = { git = "https://github.com/bluealloy/revm.git", rev = "a8f9824", default-features = false, features = [
"std",
] }

[features]
default = ["std"]
std = ["alloy-primitives/std", "anstyle/std", "serde/std", "serde_json/std", "revm/std", "thiserror/std"]
std = [
"alloy-primitives/std",
"anstyle/std",
"serde/std",
"serde_json/std",
"revm/std",
"thiserror/std",
]
serde = ["dep:serde", "revm/serde"]
js-tracer = ["dep:boa_engine", "dep:boa_gc"]
54 changes: 33 additions & 21 deletions src/access_list.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,17 @@ use alloy_primitives::{
};
use alloy_rpc_types_eth::{AccessList, AccessListItem};
use revm::{
interpreter::{opcode, Interpreter},
Database, EvmContext, Inspector,
bytecode::opcode,
context_interface::{Journal, JournalGetter, Transaction, TransactionGetter},
interpreter::{
interpreter::EthInterpreter,
interpreter_types::{InputsTrait, Jumps},
Interpreter,
},
};
use revm_inspector::{
journal::{JournalExt, JournalExtGetter},
Inspector,
};

/// An [Inspector] that collects touched accounts and storage slots.
Expand Down Expand Up @@ -65,67 +74,70 @@ impl AccessListInspector {
/// top-level call.
///
/// Those include caller, callee and precompiles.
fn collect_excluded_addresses<DB: Database>(&mut self, context: &EvmContext<DB>) {
let from = context.env.tx.caller;
let to = if let TxKind::Call(to) = context.env.tx.transact_to {
fn collect_excluded_addresses<CTX: JournalGetter + JournalExtGetter + TransactionGetter>(
&mut self,
context: &CTX,
) {
let from = context.tx().caller();
let to = if let TxKind::Call(to) = context.tx().kind() {
to
} else {
// We need to exclude the created address if this is a CREATE frame.
//
// This assumes that caller has already been loaded but nonce was not increased yet.
let nonce = context.journaled_state.account(from).info.nonce;
let nonce = context.journal_ext().evm_state().get(&from).unwrap().info.nonce;
from.create(nonce)
};
let precompiles = context.precompiles.addresses().copied();
let precompiles = context.journal_ref().precompile_addresses().clone();
self.excluded = [from, to].into_iter().chain(precompiles).collect();
}
}

impl<DB> Inspector<DB> for AccessListInspector
impl<CTX> Inspector<CTX, EthInterpreter> for AccessListInspector
where
DB: Database,
CTX: JournalExtGetter + JournalGetter + TransactionGetter,
{
fn call(
&mut self,
context: &mut EvmContext<DB>,
context: &mut CTX,
_inputs: &mut revm::interpreter::CallInputs,
) -> Option<revm::interpreter::CallOutcome> {
// At the top-level frame, fill the excluded addresses
if context.journaled_state.depth() == 0 {
if context.journal().depth() == 0 {
self.collect_excluded_addresses(context)
}
None
}

fn create(
&mut self,
context: &mut EvmContext<DB>,
context: &mut CTX,
_inputs: &mut revm::interpreter::CreateInputs,
) -> Option<revm::interpreter::CreateOutcome> {
// At the top-level frame, fill the excluded addresses
if context.journaled_state.depth() == 0 {
if context.journal().depth() == 0 {
self.collect_excluded_addresses(context)
}
None
}

fn eofcreate(
&mut self,
context: &mut EvmContext<DB>,
context: &mut CTX,
_inputs: &mut revm::interpreter::EOFCreateInputs,
) -> Option<revm::interpreter::CreateOutcome> {
// At the top-level frame, fill the excluded addresses
if context.journaled_state.depth() == 0 {
if context.journal().depth() == 0 {
self.collect_excluded_addresses(context)
}
None
}

fn step(&mut self, interp: &mut Interpreter, _context: &mut EvmContext<DB>) {
match interp.current_opcode() {
fn step(&mut self, interp: &mut Interpreter<EthInterpreter>, _context: &mut CTX) {
match interp.bytecode.opcode() {
opcode::SLOAD | opcode::SSTORE => {
if let Ok(slot) = interp.stack().peek(0) {
let cur_contract = interp.contract.target_address;
if let Ok(slot) = interp.stack.peek(0) {
let cur_contract = interp.input.target_address();
self.access_list
.entry(cur_contract)
.or_default()
Expand All @@ -137,15 +149,15 @@ where
| opcode::EXTCODESIZE
| opcode::BALANCE
| opcode::SELFDESTRUCT => {
if let Ok(slot) = interp.stack().peek(0) {
if let Ok(slot) = interp.stack.peek(0) {
let addr = Address::from_word(B256::from(slot.to_be_bytes()));
if !self.excluded.contains(&addr) {
self.access_list.entry(addr).or_default();
}
}
}
opcode::DELEGATECALL | opcode::CALL | opcode::STATICCALL | opcode::CALLCODE => {
if let Ok(slot) = interp.stack().peek(1) {
if let Ok(slot) = interp.stack.peek(1) {
let addr = Address::from_word(B256::from(slot.to_be_bytes()));
if !self.excluded.contains(&addr) {
self.access_list.entry(addr).or_default();
Expand Down
27 changes: 13 additions & 14 deletions src/edge_cov.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@ use alloc::{vec, vec::Vec};
use alloy_primitives::{map::DefaultHashBuilder, Address, U256};
use core::hash::{BuildHasher, Hash, Hasher};
use revm::{
bytecode::opcode::{self},
interpreter::{
opcode::{self},
interpreter::EthInterpreter,
interpreter_types::{InputsTrait, Jumps},
Interpreter,
},
Database, EvmContext, Inspector,
};
use revm_inspector::Inspector;

// This is the maximum number of edges that can be tracked. There is a tradeoff between performance
// and precision (less collisions).
Expand Down Expand Up @@ -63,26 +65,23 @@ impl Default for EdgeCovInspector {
}
}

impl<DB> Inspector<DB> for EdgeCovInspector
where
DB: Database,
{
fn step(&mut self, interp: &mut Interpreter, _context: &mut EvmContext<DB>) {
let address = interp.contract.target_address; // TODO track context for delegatecall?
let current_pc = interp.program_counter();
impl<CTX> Inspector<CTX, EthInterpreter> for EdgeCovInspector {
fn step(&mut self, interp: &mut Interpreter<EthInterpreter>, _context: &mut CTX) {
let address = interp.input.target_address(); // TODO track context for delegatecall?
let current_pc = interp.bytecode.pc();

match interp.current_opcode() {
match interp.bytecode.opcode() {
opcode::JUMP => {
// unconditional jump
if let Ok(jump_dest) = interp.stack().peek(0) {
if let Ok(jump_dest) = interp.stack.peek(0) {
self.store_hit(address, current_pc, jump_dest);
}
}
opcode::JUMPI => {
if let Ok(stack_value) = interp.stack().peek(0) {
let jump_dest = if stack_value != U256::from(0) {
if let Ok(stack_value) = interp.stack.peek(0) {
let jump_dest = if !stack_value.is_zero() {
// branch taken
interp.stack().peek(1)
interp.stack.peek(1)
} else {
// fall through
Ok(U256::from(current_pc + 1))
Expand Down
2 changes: 1 addition & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
//! revm [Inspector](revm::Inspector) implementations, such as call tracers
//! revm [Inspector](revm_inspector::Inspector) implementations, such as call tracers
//!
//! ## Feature Flags
//!
Expand Down
98 changes: 57 additions & 41 deletions src/opcode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@ use alloc::string::ToString;
use alloy_primitives::map::HashMap;
use alloy_rpc_types_trace::opcode::OpcodeGas;
use revm::{
bytecode::opcode::{self, OpCode},
interpreter::{
opcode::{self, OpCode},
interpreter::EthInterpreter,
interpreter_types::{Immediates, Jumps, LoopControl},
Interpreter,
},
Database, EvmContext, Inspector,
};
use revm_inspector::Inspector;

/// An Inspector that counts opcodes and measures gas usage per opcode.
#[derive(Clone, Debug, Default)]
Expand Down Expand Up @@ -58,80 +60,82 @@ impl OpcodeGasInspector {
}
}

impl<DB> Inspector<DB> for OpcodeGasInspector
where
DB: Database,
{
fn step(&mut self, interp: &mut Interpreter, _context: &mut EvmContext<DB>) {
let opcode_value = interp.current_opcode();
impl<CTX> Inspector<CTX, EthInterpreter> for OpcodeGasInspector {
fn step(&mut self, interp: &mut Interpreter<EthInterpreter>, _context: &mut CTX) {
let opcode_value = interp.bytecode.opcode();
if let Some(opcode) = OpCode::new(opcode_value) {
// keep track of opcode counts
*self.opcode_counts.entry(opcode).or_default() += 1;

// keep track of the last opcode executed
self.last_opcode_gas_remaining = Some((opcode, interp.gas().remaining()));
self.last_opcode_gas_remaining = Some((opcode, interp.control.gas().remaining()));
}
}

fn step_end(&mut self, interp: &mut Interpreter, _context: &mut EvmContext<DB>) {
fn step_end(&mut self, interp: &mut Interpreter<EthInterpreter>, _context: &mut CTX) {
// update gas usage for the last opcode
if let Some((opcode, gas_remaining)) = self.last_opcode_gas_remaining.take() {
let gas_cost = gas_remaining.saturating_sub(interp.gas().remaining());
let gas_cost = gas_remaining.saturating_sub(interp.control.gas().remaining());
*self.opcode_gas.entry(opcode).or_default() += gas_cost;
}
}
}

/// Accepts [OpCode] and a slice of bytecode immediately after it and returns the size of immediate
/// Accepts Bytecode that implements [Immediates] and returns the size of immediate
/// value.
///
/// Primarily needed to handle a special case of RJUMPV opcode.
pub fn immediate_size(op: OpCode, bytes_after: &[u8]) -> u8 {
match op.get() {
opcode::RJUMPV => {
if bytes_after.is_empty() {
return 0;
}
1 + (bytes_after[0] + 1) * 2
}
_ => op.info().immediate_size(),
pub fn immediate_size(bytecode: &impl Immediates) -> u8 {
let opcode = bytecode.read_u8();
if opcode == opcode::RJUMPV {
let vtable_size = bytecode.read_slice(2)[2];
return 1 + (vtable_size + 1) * 2;
}
let Some(opcode) = OpCode::new(opcode) else { return 0 };
opcode.info().immediate_size()
}

#[cfg(test)]
mod tests {
use super::*;
use revm::{
db::{CacheDB, EmptyDB},
interpreter::{opcode, Contract},
bytecode::Bytecode,
database_interface::EmptyDB,
interpreter::{interpreter::ExtBytecode, InputsImpl, SharedMemory},
primitives::Bytes,
specification::hardfork::SpecId,
Context,
};
use revm_database::CacheDB;
use std::{cell::RefCell, rc::Rc};

#[test]
fn test_opcode_counter_inspector() {
let mut opcode_counter = OpcodeGasInspector::new();
let contract = Contract::default();
let mut interpreter = Interpreter::new(contract, 10000, false);
let db = CacheDB::new(EmptyDB::default());

let opcodes = [
OpCode::new(opcode::ADD).unwrap(),
OpCode::new(opcode::ADD).unwrap(),
OpCode::new(opcode::ADD).unwrap(),
OpCode::new(opcode::BYTE).unwrap(),
];
let opcodes = [opcode::ADD, opcode::ADD, opcode::ADD, opcode::BYTE];

let bytecode = Bytecode::new_raw(Bytes::from(opcodes));
let mut interpreter = Interpreter::<EthInterpreter>::new(
Rc::new(RefCell::new(SharedMemory::new())),
ExtBytecode::new(bytecode),
InputsImpl::default(),
false,
false,
SpecId::LATEST,
u64::MAX,
);
let db = CacheDB::new(EmptyDB::default());

for &opcode in &opcodes {
interpreter.instruction_pointer = &opcode.get();
opcode_counter.step(&mut interpreter, &mut EvmContext::new(db.clone()));
let mut context = Context::default().with_db(db);
for _ in &opcodes {
opcode_counter.step(&mut interpreter, &mut context);
}
}

#[test]
fn test_with_variety_of_opcodes() {
let mut opcode_counter = OpcodeGasInspector::new();
let contract = Contract::default();
let mut interpreter = Interpreter::new(contract, 2024, false);
let db = CacheDB::new(EmptyDB::default());

let opcodes = [
opcode::PUSH1,
Expand All @@ -142,9 +146,21 @@ mod tests {
opcode::STOP,
];

for opcode in opcodes.iter() {
interpreter.instruction_pointer = opcode;
opcode_counter.step(&mut interpreter, &mut EvmContext::new(db.clone()));
let bytecode = Bytecode::new_raw(Bytes::from(opcodes));
let mut interpreter = Interpreter::<EthInterpreter>::new(
Rc::new(RefCell::new(SharedMemory::new())),
ExtBytecode::new(bytecode),
InputsImpl::default(),
false,
false,
SpecId::LATEST,
u64::MAX,
);
let db = CacheDB::new(EmptyDB::default());

let mut context = Context::default().with_db(db);
for _ in opcodes.iter() {
opcode_counter.step(&mut interpreter, &mut context);
}
}
}
1 change: 1 addition & 0 deletions src/tracing/arena.rs
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ impl CallTraceArena {
}

/// How to push a trace into the arena
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) enum PushTraceKind {
/// This will _only_ push the trace into the arena.
PushOnly,
Expand Down
Loading
Loading