From ca14d61375cc94b216742bb016d2199c0b006b17 Mon Sep 17 00:00:00 2001 From: Alexey Shekhirin Date: Sun, 18 Sep 2022 12:01:20 +0100 Subject: [PATCH] feat(revm, revme): gas inspector (#222) * feat(revm): gas inspector * use gas inspector in statetest * fix gas count during JUMPI --- bins/revme/src/statetest/trace.rs | 78 +++------ crates/revm/src/inspector.rs | 276 +++++++++++++++++++++++++++++- crates/revm/src/lib.rs | 2 +- 3 files changed, 300 insertions(+), 56 deletions(-) diff --git a/bins/revme/src/statetest/trace.rs b/bins/revme/src/statetest/trace.rs index 1e1918740d..fbbd76577a 100644 --- a/bins/revme/src/statetest/trace.rs +++ b/bins/revme/src/statetest/trace.rs @@ -3,26 +3,18 @@ use primitive_types::H160; pub use revm::Inspector; use revm::{ opcode::{self}, - spec_opcode_gas, CallInputs, CreateInputs, Database, EVMData, Gas, Return, + CallInputs, CreateInputs, Database, EVMData, Gas, GasInspector, Return, }; #[derive(Clone)] pub struct CustomPrintTracer { - /// We now batch continual gas_block in one go, that means we need to reduce it ifwe want to get - /// correct gas remaining. Check revm/interp/contract/analyze for more information - reduced_gas_block: u64, - full_gas_block: u64, - was_return: bool, - was_jumpi: Option, + gas_inspector: GasInspector, } impl CustomPrintTracer { pub fn new() -> Self { Self { - reduced_gas_block: 0, - full_gas_block: 0, - was_return: false, - was_jumpi: None, + gas_inspector: GasInspector::default(), } } } @@ -31,10 +23,11 @@ impl Inspector for CustomPrintTracer { fn initialize_interp( &mut self, interp: &mut revm::Interpreter, - _data: &mut EVMData<'_, DB>, - _is_static: bool, + data: &mut EVMData<'_, DB>, + is_static: bool, ) -> Return { - self.full_gas_block = interp.contract.first_gas_block(); + self.gas_inspector + .initialize_interp(interp, data, is_static); Return::Continue } @@ -44,16 +37,12 @@ impl Inspector for CustomPrintTracer { &mut self, interp: &mut revm::Interpreter, data: &mut EVMData<'_, DB>, - _is_static: bool, + is_static: bool, ) -> Return { let opcode = interp.current_opcode(); let opcode_str = opcode::OPCODE_JUMPMAP[opcode as usize]; - // calculate gas_block - let infos = spec_opcode_gas(data.env.cfg.spec_id); - let info = &infos[opcode as usize]; - - let gas_remaining = interp.gas.remaining() + self.full_gas_block - self.reduced_gas_block; + let gas_remaining = self.gas_inspector.gas_remaining(); println!( "depth:{}, PC:{}, gas:{:#x}({}), OPCODE: {:?}({:?}) refund:{:#x}({}) Stack:{:?}, Data size:{}", @@ -69,15 +58,7 @@ impl Inspector for CustomPrintTracer { interp.memory.data().len(), ); - let pc = interp.program_counter(); - if opcode == opcode::JUMPI { - self.was_jumpi = Some(pc); - } else if info.is_gas_block_end() { - self.reduced_gas_block = 0; - self.full_gas_block = interp.contract.gas_block(pc); - } else { - self.reduced_gas_block += info.get_gas() as u64; - } + self.gas_inspector.step(interp, data, is_static); Return::Continue } @@ -85,53 +66,42 @@ impl Inspector for CustomPrintTracer { fn step_end( &mut self, interp: &mut revm::Interpreter, - _data: &mut EVMData<'_, DB>, - _is_static: bool, - _eval: revm::Return, + data: &mut EVMData<'_, DB>, + is_static: bool, + eval: revm::Return, ) -> Return { - let pc = interp.program_counter(); - if let Some(was_pc) = self.was_jumpi { - if let Some(new_pc) = pc.checked_sub(1) { - if was_pc == new_pc { - self.reduced_gas_block = 0; - self.full_gas_block = interp.contract.gas_block(was_pc); - } - } - self.was_jumpi = None; - } else if self.was_return { - // we are okey to decrement PC by one as it is return of call - let previous_pc = pc - 1; - self.full_gas_block = interp.contract.gas_block(previous_pc); - self.was_return = false; - } + self.gas_inspector.step_end(interp, data, is_static, eval); Return::Continue } fn call_end( &mut self, - _data: &mut EVMData<'_, DB>, - _inputs: &CallInputs, + data: &mut EVMData<'_, DB>, + inputs: &CallInputs, remaining_gas: Gas, ret: Return, out: Bytes, - _is_static: bool, + is_static: bool, ) -> (Return, Gas, Bytes) { - self.was_return = true; + self.gas_inspector + .call_end(data, inputs, remaining_gas, ret, out.clone(), is_static); (ret, remaining_gas, out) } fn create_end( &mut self, - _data: &mut EVMData<'_, DB>, - _inputs: &CreateInputs, + data: &mut EVMData<'_, DB>, + inputs: &CreateInputs, ret: Return, address: Option, remaining_gas: Gas, out: Bytes, ) -> (Return, Option, Gas, Bytes) { - self.was_return = true; + self.gas_inspector + .create_end(data, inputs, ret, address, remaining_gas, out.clone()); (ret, address, remaining_gas, out) } + fn call( &mut self, _data: &mut EVMData<'_, DB>, diff --git a/crates/revm/src/inspector.rs b/crates/revm/src/inspector.rs index b4af769287..33ac2adf22 100644 --- a/crates/revm/src/inspector.rs +++ b/crates/revm/src/inspector.rs @@ -1,7 +1,10 @@ use bytes::Bytes; use primitive_types::{H160, H256}; -use crate::{evm_impl::EVMData, CallInputs, CreateInputs, Database, Gas, Interpreter, Return}; +use crate::{ + evm_impl::EVMData, opcode, spec_opcode_gas, CallInputs, CreateInputs, Database, Gas, + Interpreter, Return, +}; use auto_impl::auto_impl; #[auto_impl(&mut, Box)] @@ -122,3 +125,274 @@ pub trait Inspector { pub struct NoOpInspector(); impl Inspector for NoOpInspector {} + +#[derive(Clone, Copy, Debug, Default)] +pub struct GasInspector { + /// We now batch continual gas_block in one go, that means we need to reduce it if we want + /// to get correct gas remaining. Check revm/interp/contract/analyze for more information + reduced_gas_block: u64, + full_gas_block: u64, + was_return: bool, + was_jumpi: Option, + + gas_remaining: u64, +} + +impl GasInspector { + pub fn gas_remaining(&self) -> u64 { + self.gas_remaining + } +} + +impl Inspector for GasInspector { + fn initialize_interp( + &mut self, + interp: &mut Interpreter, + _data: &mut EVMData<'_, DB>, + _is_static: bool, + ) -> Return { + self.full_gas_block = interp.contract.first_gas_block(); + self.gas_remaining = interp.gas.limit(); + Return::Continue + } + + // get opcode by calling `interp.contract.opcode(interp.program_counter())`. + // all other information can be obtained from interp. + fn step( + &mut self, + interp: &mut Interpreter, + data: &mut EVMData<'_, DB>, + _is_static: bool, + ) -> Return { + let op = interp.current_opcode(); + + // calculate gas_block + let infos = spec_opcode_gas(data.env.cfg.spec_id); + let info = &infos[op as usize]; + + let pc = interp.program_counter(); + if op == opcode::JUMPI { + self.reduced_gas_block += info.get_gas() as u64; + self.was_jumpi = Some(pc); + } else if info.is_gas_block_end() { + self.reduced_gas_block = 0; + self.full_gas_block = interp.contract.gas_block(pc); + } else { + self.reduced_gas_block += info.get_gas() as u64; + } + + Return::Continue + } + + fn step_end( + &mut self, + interp: &mut Interpreter, + _data: &mut EVMData<'_, DB>, + _is_static: bool, + _eval: Return, + ) -> Return { + let pc = interp.program_counter(); + if let Some(was_pc) = self.was_jumpi { + if let Some(new_pc) = pc.checked_sub(1) { + if was_pc == new_pc { + self.reduced_gas_block = 0; + self.full_gas_block = interp.contract.gas_block(was_pc); + } + } + self.was_jumpi = None; + } else if self.was_return { + // we are ok to decrement PC by one as it is return of call + let previous_pc = pc - 1; + self.full_gas_block = interp.contract.gas_block(previous_pc); + self.was_return = false; + } + + self.gas_remaining = interp.gas.remaining() + self.full_gas_block - self.reduced_gas_block; + + Return::Continue + } + + fn call_end( + &mut self, + _data: &mut EVMData<'_, DB>, + _inputs: &CallInputs, + remaining_gas: Gas, + ret: Return, + out: Bytes, + _is_static: bool, + ) -> (Return, Gas, Bytes) { + self.was_return = true; + (ret, remaining_gas, out) + } + + fn create_end( + &mut self, + _data: &mut EVMData<'_, DB>, + _inputs: &CreateInputs, + ret: Return, + address: Option, + remaining_gas: Gas, + out: Bytes, + ) -> (Return, Option, Gas, Bytes) { + self.was_return = true; + (ret, address, remaining_gas, out) + } +} + +#[cfg(test)] +mod tests { + use crate::db::BenchmarkDB; + use crate::{ + opcode, Bytecode, CallInputs, CreateInputs, Database, EVMData, Gas, GasInspector, + Inspector, Interpreter, OpCode, Return, TransactTo, + }; + use bytes::Bytes; + use core::str::FromStr; + use primitive_types::{H160, H256}; + + #[derive(Default, Debug)] + struct StackInspector { + pc: usize, + gas_inspector: GasInspector, + gas_remaining_steps: Vec<(usize, u64)>, + } + + impl Inspector for StackInspector { + fn initialize_interp( + &mut self, + interp: &mut Interpreter, + data: &mut EVMData<'_, DB>, + is_static: bool, + ) -> Return { + self.gas_inspector + .initialize_interp(interp, data, is_static); + Return::Continue + } + + fn step( + &mut self, + interp: &mut Interpreter, + data: &mut EVMData<'_, DB>, + is_static: bool, + ) -> Return { + self.pc = interp.program_counter(); + self.gas_inspector.step(interp, data, is_static); + Return::Continue + } + + fn log( + &mut self, + evm_data: &mut EVMData<'_, DB>, + address: &H160, + topics: &[H256], + data: &Bytes, + ) { + self.gas_inspector.log(evm_data, address, topics, data); + } + + fn step_end( + &mut self, + interp: &mut Interpreter, + data: &mut EVMData<'_, DB>, + is_static: bool, + eval: Return, + ) -> Return { + self.gas_inspector.step_end(interp, data, is_static, eval); + self.gas_remaining_steps + .push((self.pc, self.gas_inspector.gas_remaining())); + eval + } + + fn call( + &mut self, + data: &mut EVMData<'_, DB>, + call: &mut CallInputs, + is_static: bool, + ) -> (Return, Gas, Bytes) { + self.gas_inspector.call(data, call, is_static); + + (Return::Continue, Gas::new(call.gas_limit), Bytes::new()) + } + + fn call_end( + &mut self, + data: &mut EVMData<'_, DB>, + inputs: &CallInputs, + remaining_gas: Gas, + ret: Return, + out: Bytes, + is_static: bool, + ) -> (Return, Gas, Bytes) { + self.gas_inspector + .call_end(data, inputs, remaining_gas, ret, out.clone(), is_static); + (ret, remaining_gas, out) + } + + fn create( + &mut self, + data: &mut EVMData<'_, DB>, + call: &mut CreateInputs, + ) -> (Return, Option, Gas, Bytes) { + self.gas_inspector.create(data, call); + + ( + Return::Continue, + None, + Gas::new(call.gas_limit), + Bytes::new(), + ) + } + + fn create_end( + &mut self, + data: &mut EVMData<'_, DB>, + inputs: &CreateInputs, + status: Return, + address: Option, + gas: Gas, + retdata: Bytes, + ) -> (Return, Option, Gas, Bytes) { + self.gas_inspector + .create_end(data, inputs, status, address, gas, retdata.clone()); + (status, address, gas, retdata) + } + } + + #[test] + fn test_gas_inspector() { + let contract_data: Bytes = Bytes::from(vec![ + opcode::PUSH1, + 0x1, + opcode::PUSH1, + 0xb, + opcode::JUMPI, + opcode::PUSH1, + 0x1, + opcode::PUSH1, + 0x1, + opcode::PUSH1, + 0x1, + opcode::JUMPDEST, + opcode::STOP, + ]); + let bytecode = Bytecode::new_raw(contract_data); + + let mut evm = crate::new(); + evm.database(BenchmarkDB::new_bytecode(bytecode.clone())); + evm.env.tx.caller = H160::from_str("0x1000000000000000000000000000000000000000").unwrap(); + evm.env.tx.transact_to = + TransactTo::Call(H160::from_str("0x0000000000000000000000000000000000000000").unwrap()); + evm.env.tx.gas_limit = 21100; + + let mut inspector = StackInspector::default(); + let (result, state) = evm.inspect(&mut inspector); + println!("{result:?} {state:?} {inspector:?}"); + + for (pc, gas) in inspector.gas_remaining_steps { + println!( + "{pc} {} {gas:?}", + OpCode::try_from_u8(bytecode.bytes()[pc]).unwrap().as_str(), + ); + } + } +} diff --git a/crates/revm/src/lib.rs b/crates/revm/src/lib.rs index ed14e7c995..15651122e7 100644 --- a/crates/revm/src/lib.rs +++ b/crates/revm/src/lib.rs @@ -19,7 +19,7 @@ pub type DummyStateDB = InMemoryDB; pub use db::{Database, DatabaseCommit, InMemoryDB}; pub use evm::{evm_inner, new, EVM}; pub use gas::Gas; -pub use inspector::{Inspector, NoOpInspector}; +pub use inspector::{GasInspector, Inspector, NoOpInspector}; pub use instructions::{ opcode::{self, spec_opcode_gas, OpCode, OPCODE_JUMPMAP}, Return,