diff --git a/crates/cfg/src/core/graph.rs b/crates/cfg/src/core/graph.rs index 5bec52fb..702d31a4 100644 --- a/crates/cfg/src/core/graph.rs +++ b/crates/cfg/src/core/graph.rs @@ -2,7 +2,7 @@ use alloy::primitives::U256; use eyre::{OptionExt, Result}; use heimdall_common::utils::strings::encode_hex_reduced; use heimdall_vm::{ - core::opcodes::{OpCodeInfo, JUMPDEST}, + core::opcodes::{opcode_name, JUMPDEST}, ext::exec::VMTrace, }; use petgraph::{matrix_graph::NodeIndex, Graph}; @@ -21,7 +21,7 @@ pub fn build_cfg( // add the current operations to the cfg for operation in &vm_trace.operations { - let opcode_name = OpCodeInfo::from(operation.last_instruction.opcode).name(); + let opcode_name = opcode_name(operation.last_instruction.opcode); let assembly = format!( "{} {} {}", diff --git a/crates/decompile/src/utils/heuristics/arguments.rs b/crates/decompile/src/utils/heuristics/arguments.rs index 63a905cd..4f14fe25 100644 --- a/crates/decompile/src/utils/heuristics/arguments.rs +++ b/crates/decompile/src/utils/heuristics/arguments.rs @@ -4,7 +4,7 @@ use alloy::primitives::U256; use eyre::eyre; use heimdall_common::utils::strings::find_balanced_encapsulator; use heimdall_vm::core::{ - opcodes::{OpCodeInfo, CALLDATALOAD, ISZERO}, + opcodes::{opcode_name, CALLDATALOAD, ISZERO}, types::{byte_size_to_type, convert_bitmask}, vm::State, }; @@ -72,7 +72,7 @@ pub fn argument_heuristic( debug!( "instruction {} ({}) indicates argument {} is masked to {} bytes", state.last_instruction.instruction, - OpCodeInfo::from(state.last_instruction.opcode).name(), + opcode_name(state.last_instruction.opcode), arg_index, mask_size_bytes ); @@ -216,7 +216,7 @@ pub fn argument_heuristic( debug!( "instruction {} ({}) indicates argument {} may be a numeric type", state.last_instruction.instruction, - OpCodeInfo::from(state.last_instruction.opcode).name(), + opcode_name(state.last_instruction.opcode), arg_index ); @@ -239,7 +239,7 @@ pub fn argument_heuristic( debug!( "instruction {} ({}) indicates argument {} may be a bytes type", state.last_instruction.instruction, - OpCodeInfo::from(state.last_instruction.opcode).name(), + opcode_name(state.last_instruction.opcode), arg_index ); @@ -262,7 +262,7 @@ pub fn argument_heuristic( debug!( "instruction {} ({}) indicates argument {} may be a boolean", state.last_instruction.instruction, - OpCodeInfo::from(state.last_instruction.opcode).name(), + opcode_name(state.last_instruction.opcode), arg_index ); diff --git a/crates/decompile/src/utils/heuristics/modifiers.rs b/crates/decompile/src/utils/heuristics/modifiers.rs index 7addd82d..074ff11f 100644 --- a/crates/decompile/src/utils/heuristics/modifiers.rs +++ b/crates/decompile/src/utils/heuristics/modifiers.rs @@ -9,42 +9,29 @@ use tracing::debug; use crate::{core::analyze::AnalyzerState, interfaces::AnalyzedFunction, Error}; -use lazy_static::lazy_static; - -lazy_static! { - /// A list of opcodes that are considered non-pure (state accessing) - pub static ref NON_PURE_OPCODES: Vec = vec![ - 0x31, 0x32, 0x33, 0x3a, 0x3b, 0x3c, 0x40, 0x41, 0x42, - 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x54, 0x55, 0xf0, - 0xf1, 0xf2, 0xf4, 0xf5, 0xfa, 0xff - ]; - /// A list of opcodes that are considered non-view (state modifying) - pub static ref NON_VIEW_OPCODES: Vec = vec![ - 0x55, 0xf0, 0xf1, 0xf2, 0xf4, 0xf5, 0xfa, 0xff - ]; -} - pub fn modifier_heuristic( function: &mut AnalyzedFunction, state: &State, _: &mut AnalyzerState, ) -> Result<(), Error> { - let opcode_name = OpCodeInfo::from(state.last_instruction.opcode).name(); + let opcode_info = OpCodeInfo::from(state.last_instruction.opcode); // if any instruction is non-pure, the function is non-pure - if function.pure && NON_PURE_OPCODES.contains(&state.last_instruction.opcode) { + if function.pure && !opcode_info.is_pure() { debug!( "instruction {} ({}) indicates a non-pure function", - state.last_instruction.instruction, opcode_name + state.last_instruction.instruction, + opcode_info.name() ); function.pure = false; } // if any instruction is non-view, the function is non-view - if function.view && NON_VIEW_OPCODES.contains(&state.last_instruction.opcode) { + if function.view && !opcode_info.is_view() { debug!( "instruction {} ({}) indicates a non-view function", - state.last_instruction.instruction, opcode_name + state.last_instruction.instruction, + opcode_info.name() ); function.view = false; } diff --git a/crates/decompile/src/utils/heuristics/solidity.rs b/crates/decompile/src/utils/heuristics/solidity.rs index 2eb4199f..3641d78a 100644 --- a/crates/decompile/src/utils/heuristics/solidity.rs +++ b/crates/decompile/src/utils/heuristics/solidity.rs @@ -1,7 +1,7 @@ use alloy::primitives::U256; use alloy_dyn_abi::{DynSolType, DynSolValue}; use heimdall_common::utils::strings::encode_hex_reduced; -use heimdall_vm::core::{opcodes::OpCodeInfo, vm::State}; +use heimdall_vm::core::{opcodes::opcode_name, vm::State}; use crate::{ core::analyze::AnalyzerState, @@ -188,7 +188,7 @@ pub fn solidity_heuristic( function.logic.push(format!( "(bool success, bytes memory ret0) = address({}).{}{}(abi.encode({}));", address, - OpCodeInfo::from(instruction.opcode).name().to_lowercase(), + opcode_name(instruction.opcode).to_lowercase(), modifier, calldata .iter() diff --git a/crates/decompile/src/utils/heuristics/yul.rs b/crates/decompile/src/utils/heuristics/yul.rs index c1f54abe..cdbbeeb8 100644 --- a/crates/decompile/src/utils/heuristics/yul.rs +++ b/crates/decompile/src/utils/heuristics/yul.rs @@ -1,5 +1,5 @@ use heimdall_common::utils::strings::encode_hex_reduced; -use heimdall_vm::core::{opcodes::OpCodeInfo, vm::State}; +use heimdall_vm::core::{opcodes::opcode_name, vm::State}; use crate::{ core::analyze::AnalyzerState, @@ -24,7 +24,7 @@ pub fn yul_heuristic( function.memory.insert(key, StorageFrame { operation }); function.logic.push(format!( "{}({}, {})", - OpCodeInfo::from(instruction.opcode).name().to_lowercase(), + opcode_name(instruction.opcode).to_lowercase(), encode_hex_reduced(key), instruction.input_operations[1].yulify() )); @@ -80,7 +80,7 @@ pub fn yul_heuristic( 0xff | 0xA0 | 0xA1 | 0xA2 | 0xA3 | 0xA4 => { function.logic.push(format!( "{}({})", - OpCodeInfo::from(instruction.opcode).name().to_lowercase(), + opcode_name(instruction.opcode).to_lowercase(), instruction .input_operations .iter() diff --git a/crates/disassemble/src/core/mod.rs b/crates/disassemble/src/core/mod.rs index 940313a2..e0ec39c4 100644 --- a/crates/disassemble/src/core/mod.rs +++ b/crates/disassemble/src/core/mod.rs @@ -3,7 +3,7 @@ use std::time::Instant; use crate::{error::Error, interfaces::DisassemblerArgs}; use eyre::eyre; use heimdall_common::utils::strings::encode_hex; -use heimdall_vm::core::opcodes::OpCodeInfo; +use heimdall_vm::core::opcodes::opcode_name; use tracing::{debug, info}; pub async fn disassemble(args: DisassemblerArgs) -> Result { @@ -45,7 +45,7 @@ pub async fn disassemble(args: DisassemblerArgs) -> Result { } else { format!("{:06x}", program_counter) }, - OpCodeInfo::from(opcode).name(), + opcode_name(opcode), pushed_bytes ) .as_str(), diff --git a/crates/vm/benches/bench_ten_thousand_hashes.rs b/crates/vm/benches/bench_ten_thousand_hashes.rs index 91ccc1cc..c14ffac1 100644 --- a/crates/vm/benches/bench_ten_thousand_hashes.rs +++ b/crates/vm/benches/bench_ten_thousand_hashes.rs @@ -4,10 +4,10 @@ use heimdall_common::utils::strings::decode_hex; use heimdall_vm::core::vm::VM; use tokio::runtime::Runtime; -fn test_fib(c: &mut Criterion) { +fn test_ten_thousand_hashes(c: &mut Criterion) { let mut group = c.benchmark_group("heimdall_vm"); - group.sample_size(500); + group.sample_size(100); group.bench_function(BenchmarkId::from_parameter("ten_thousand_hashes"), |b| { b.to_async::(Runtime::new().unwrap()).iter(|| async { // build the evm @@ -33,5 +33,5 @@ fn test_fib(c: &mut Criterion) { group.finish(); } -criterion_group!(benches, test_fib); +criterion_group!(benches, test_ten_thousand_hashes); criterion_main!(benches); diff --git a/crates/vm/src/core/opcodes/mod.rs b/crates/vm/src/core/opcodes/mod.rs index c71c4db9..c314bbdf 100644 --- a/crates/vm/src/core/opcodes/mod.rs +++ b/crates/vm/src/core/opcodes/mod.rs @@ -75,7 +75,7 @@ impl OpCodeInfo { impl From for OpCodeInfo { #[inline] fn from(opcode: u8) -> Self { - OPCODE_INFO_JUMPTABLE[opcode as usize].unwrap_or(OpCodeInfo { + OPCODE_INFO_TABLE[opcode as usize].unwrap_or(OpCodeInfo { name: "unknown", inputs: 0, outputs: 0, @@ -269,7 +269,7 @@ macro_rules! opcodes { )* /// Maps each opcode to its info. - pub const OPCODE_INFO_JUMPTABLE: [Option; 256] = { + pub const OPCODE_INFO_TABLE: [Option; 256] = { let mut map = [None; 256]; let mut prev: u8 = 0; $( @@ -287,9 +287,24 @@ macro_rules! opcodes { let _ = prev; map }; + + /// Maps each opcode to its name. (So we dont need to load [`OpCodeInfo`] to get the name) + pub const OPCODE_NAME_TABLE: [&'static str; 256] = { + let mut map = ["unknown"; 256]; + $( + map[$val] = stringify!($name); + )* + map + }; } } +/// Get the name of an opcode. +#[inline] +pub fn opcode_name(opcode: u8) -> &'static str { + OPCODE_NAME_TABLE[opcode as usize] +} + opcodes! { 0x00 => STOP => terminating; diff --git a/crates/vm/src/core/opcodes/wrapped.rs b/crates/vm/src/core/opcodes/wrapped.rs index 4ca55600..2ab149a8 100644 --- a/crates/vm/src/core/opcodes/wrapped.rs +++ b/crates/vm/src/core/opcodes/wrapped.rs @@ -1,6 +1,6 @@ use alloy::primitives::U256; -use crate::core::opcodes::OpCodeInfo; +use crate::core::opcodes::opcode_name; /// A WrappedInput can contain either a raw U256 value or a WrappedOpcode #[derive(Clone, Debug, PartialEq, Eq, Hash)] @@ -30,7 +30,7 @@ impl std::fmt::Display for WrappedOpcode { write!( f, "{}({})", - OpCodeInfo::from(self.opcode).name(), + opcode_name(self.opcode), self.inputs.iter().map(|x| x.to_string()).collect::>().join(", ") ) } diff --git a/crates/vm/src/core/vm.rs b/crates/vm/src/core/vm.rs index cd288e79..6a2f5770 100644 --- a/crates/vm/src/core/vm.rs +++ b/crates/vm/src/core/vm.rs @@ -712,7 +712,7 @@ impl VM { let b = self.stack.pop()?; // convert a to usize - let usize_a: usize = a.value.try_into().unwrap_or(usize::MAX); + let usize_a: usize = a.value.try_into()?; let mut result = I256::ZERO; if !b.value.is_zero() { @@ -737,8 +737,8 @@ impl VM { let size = self.stack.pop()?.value; // Safely convert U256 to usize - let offset: usize = offset.try_into().unwrap_or(32 * 32); - let size: usize = size.try_into().unwrap_or(32 * 32); + let offset: usize = offset.try_into()?; + let size: usize = size.try_into()?; let data = self.memory.read(offset, size); let result = keccak256(data); @@ -802,7 +802,7 @@ impl VM { let i = self.stack.pop()?.value; // Safely convert U256 to usize - let i: usize = i.try_into().unwrap_or(usize::MAX); + let i: usize = i.try_into()?; let result = if i + 32 > self.calldata.len() { let mut value = [0u8; 32]; @@ -834,9 +834,9 @@ impl VM { // Safely convert U256 to usize // Note: clamping to 8 words here, since we dont actually use the return data - let dest_offset: usize = dest_offset.try_into().unwrap_or(8 * 32); - let offset: usize = offset.try_into().unwrap_or(8 * 32); - let size: usize = size.try_into().unwrap_or(8 * 32); + let dest_offset: usize = dest_offset.try_into()?; + let offset: usize = offset.try_into()?; + let size: usize = size.try_into()?; // clamp values to calldata length let end_offset_clamped = (offset + size).min(self.calldata.len()); @@ -879,9 +879,9 @@ impl VM { // Safely convert U256 to usize // Note: clamping to 8 words here, since we dont actually use the return data - let dest_offset: usize = dest_offset.try_into().unwrap_or(8 * 32); - let offset: usize = offset.try_into().unwrap_or(8 * 32); - let size: usize = size.try_into().unwrap_or(8 * 32); + let dest_offset: usize = dest_offset.try_into()?; + let offset: usize = offset.try_into()?; + let size: usize = size.try_into()?; let value_offset_safe = (offset + size).min(self.bytecode.len()); let mut value = @@ -935,8 +935,8 @@ impl VM { // Safely convert U256 to usize // Note: clamping to 8 words here, since we dont actually use the return data - let dest_offset: usize = dest_offset.try_into().unwrap_or(32 * 8); - let size: usize = size.try_into().unwrap_or(32 * 8); + let dest_offset: usize = dest_offset.try_into()?; + let size: usize = size.try_into()?; let mut value = Vec::with_capacity(size); value.fill(0xff); @@ -975,8 +975,8 @@ impl VM { // Safely convert U256 to usize // Note: clamping to 8 words here, since we dont actually use the return data - let dest_offset: usize = dest_offset.try_into().unwrap_or(32 * 8); - let size: usize = size.try_into().unwrap_or(32 * 8); + let dest_offset: usize = dest_offset.try_into()?; + let size: usize = size.try_into()?; let mut value = Vec::with_capacity(size); value.fill(0xff); @@ -1037,7 +1037,7 @@ impl VM { // MLOAD 0x51 => { let i = self.stack.pop()?.value; - let i: usize = i.try_into().unwrap_or(32 * 32); + let i: usize = i.try_into()?; let result = U256::from_be_slice(self.memory.read(i, 32).as_slice()); @@ -1054,7 +1054,7 @@ impl VM { let value = self.stack.pop()?.value; // Safely convert U256 to usize - let offset: usize = offset.try_into().unwrap_or(32 * 32); + let offset: usize = offset.try_into()?; // consume dynamic gas let gas_cost = self.memory.expansion_cost(offset, 32); @@ -1075,7 +1075,7 @@ impl VM { let value = self.stack.pop()?.value; // Safely convert U256 to usize - let offset: usize = offset.try_into().unwrap_or(64 * 32); + let offset: usize = offset.try_into()?; // consume dynamic gas let gas_cost = self.memory.expansion_cost(offset, 1); @@ -1118,7 +1118,7 @@ impl VM { let pc = self.stack.pop()?.value; // Safely convert U256 to u128 - let pc: u128 = pc.try_into().unwrap_or(u128::MAX); + let pc: u128 = pc.try_into()?; // Check if JUMPDEST is valid and throw with 790 if not (invalid jump destination) if (pc <= @@ -1148,7 +1148,7 @@ impl VM { let condition = self.stack.pop()?.value; // Safely convert U256 to u128 - let pc: u128 = pc.try_into().unwrap_or(u128::MAX); + let pc: u128 = pc.try_into()?; if !condition.eq(&U256::from(0u8)) { // Check if JUMPDEST is valid and throw with 790 if not (invalid jump @@ -1199,9 +1199,9 @@ impl VM { // Safely convert U256 to usize // Note: clamping to 8 words here, since we dont actually use the return data - let dest_offset: usize = dest_offset.try_into().unwrap_or(32 * 32); - let offset: usize = offset.try_into().unwrap_or(32 * 32); - let size: usize = size.try_into().unwrap_or(32 * 32); + let dest_offset: usize = dest_offset.try_into()?; + let offset: usize = offset.try_into()?; + let size: usize = size.try_into()?; let value_offset_safe = (offset + size) .min(self.memory.size().try_into().expect("failed to convert u128 to usize")); @@ -1293,8 +1293,8 @@ impl VM { self.stack.pop_n(topic_count as usize).iter().map(|x| x.value).collect(); // Safely convert U256 to usize - let offset: usize = offset.try_into().unwrap_or(32 * 32); - let size: usize = size.try_into().unwrap_or(32 * 32); + let offset: usize = offset.try_into()?; + let size: usize = size.try_into()?; let data = self.memory.read(offset, size); @@ -1345,8 +1345,8 @@ impl VM { let size = self.stack.pop()?.value; // Safely convert U256 to usize - let offset: usize = offset.try_into().unwrap_or(32 * 32); - let size: usize = size.try_into().unwrap_or(32 * 32); + let offset: usize = offset.try_into()?; + let size: usize = size.try_into()?; // consume dynamic gas let gas_cost = self.memory.expansion_cost(offset, size); @@ -1384,8 +1384,8 @@ impl VM { let size = self.stack.pop()?.value; // Safely convert U256 to usize - let offset: usize = offset.try_into().unwrap_or(32 * 32); - let size: usize = size.try_into().unwrap_or(32 * 32); + let offset: usize = offset.try_into()?; + let size: usize = size.try_into()?; self.exit(1, self.memory.read(offset, size)); } diff --git a/crates/vm/src/ext/lexers/yul.rs b/crates/vm/src/ext/lexers/yul.rs index 69430bf2..4c18f8cf 100644 --- a/crates/vm/src/ext/lexers/yul.rs +++ b/crates/vm/src/ext/lexers/yul.rs @@ -1,6 +1,6 @@ use heimdall_common::utils::strings::encode_hex_reduced; -use crate::core::opcodes::{OpCodeInfo, WrappedInput, WrappedOpcode, PUSH0}; +use crate::core::opcodes::{opcode_name, WrappedInput, WrappedOpcode, PUSH0}; impl WrappedOpcode { /// Returns a WrappedOpcode's yul representation. @@ -12,7 +12,7 @@ impl WrappedOpcode { } else { format!( "{}({})", - OpCodeInfo::from(self.opcode).name().to_lowercase(), + opcode_name(self.opcode).to_lowercase(), self.inputs.iter().map(|input| input._yulify()).collect::>().join(", ") ) }