Skip to content

Commit 23addf5

Browse files
refactor(snapshot): code split (#198)
* refactor: extract logger functions * refactor: extract shortned address to function * chore: merge with origin branch * chore: remove duplicated function declaration * chore: move get_shortnet_target and set_logger_env to common * refactor: extract function selector resolvers to resolve.rs * refactor: move get_contract_bytecode to a new bytecode module * refactor: move get_selectors to selectors module * refactor: make bytecode disambled external from get_resolved_selectors * refactor: remove logger from params of resolve_signature * refactor: rework snapshot mod variable and args declaration order * refactor: remove logger from params list and initialize internally * fix: remove extrar logger param from get_contract_bytecode call * chore: change rpc from llama to ankr * fix(tests): remove unnecessary `0x` prefix constraint * refactor: rename resolve_custom_event_signatures to resolve_event_signatures * refactor: rename get_contract_bytecode to get_bytecode_from_target * fix: update comments from get_bytecode_from_target to be module agnostic * refactor: switch from logger::debug_max to debug_max * fix: remove out of context log * refactor: remove get_logger_and_trace function * style: code format * refactor: change target param from string reference to string slice --------- Co-authored-by: Jon-Becker <jonathan@jbecker.dev>
1 parent cfbf6b1 commit 23addf5

9 files changed

Lines changed: 572 additions & 336 deletions

File tree

common/src/ether/bytecode.rs

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
use super::rpc::get_code;
2+
use crate::{
3+
constants::{ADDRESS_REGEX, BYTECODE_REGEX},
4+
debug_max,
5+
utils::io::logging::Logger,
6+
};
7+
use std::fs;
8+
9+
pub async fn get_bytecode_from_target(
10+
target: &str,
11+
rpc_url: &str,
12+
) -> Result<String, Box<dyn std::error::Error>> {
13+
let (logger, _) = Logger::new("");
14+
15+
if ADDRESS_REGEX.is_match(target)? {
16+
// Target is a contract address, so we need to fetch the bytecode from the RPC provider.
17+
get_code(target, rpc_url).await
18+
} else if BYTECODE_REGEX.is_match(target)? {
19+
debug_max!("using provided bytecode for snapshotting.");
20+
21+
// Target is already a bytecode, so we just need to remove 0x from the begining
22+
Ok(target.replacen("0x", "", 1))
23+
} else {
24+
debug_max!("using provided file for snapshotting.");
25+
26+
// Target is a file path, so we need to read the bytecode from the file.
27+
match fs::read_to_string(target) {
28+
Ok(contents) => {
29+
let _contents = contents.replace('\n', "");
30+
if BYTECODE_REGEX.is_match(&_contents)? && _contents.len() % 2 == 0 {
31+
Ok(_contents.replacen("0x", "", 1))
32+
} else {
33+
logger.error(&format!("file '{}' doesn't contain valid bytecode.", &target));
34+
std::process::exit(1)
35+
}
36+
}
37+
Err(_) => {
38+
logger.error(&format!("failed to open file '{}' .", &target));
39+
std::process::exit(1)
40+
}
41+
}
42+
}
43+
}
44+
#[cfg(test)]
45+
mod tests {
46+
use super::*;
47+
use std::fs;
48+
49+
#[tokio::test]
50+
async fn test_get_bytecode_when_target_is_address() {
51+
let bytecode = get_bytecode_from_target(
52+
"0x9f00c43700bc0000Ff91bE00841F8e04c0495000",
53+
"https://rpc.ankr.com/eth",
54+
)
55+
.await
56+
.unwrap();
57+
58+
assert!(BYTECODE_REGEX.is_match(&bytecode).unwrap());
59+
}
60+
61+
#[tokio::test]
62+
async fn test_get_bytecode_when_target_is_bytecode() {
63+
let bytecode = get_bytecode_from_target(
64+
"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001",
65+
"https://rpc.ankr.com/eth",
66+
)
67+
.await
68+
.unwrap();
69+
70+
assert!(BYTECODE_REGEX.is_match(&bytecode).unwrap());
71+
}
72+
73+
#[tokio::test]
74+
async fn test_get_bytecode_when_target_is_file_path() {
75+
let file_path = "./mock-file.txt";
76+
let mock_bytecode = "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001";
77+
78+
fs::write(file_path, mock_bytecode).unwrap();
79+
80+
let bytecode =
81+
get_bytecode_from_target(file_path, "https://rpc.ankr.com/eth").await.unwrap();
82+
83+
assert!(BYTECODE_REGEX.is_match(&bytecode).unwrap());
84+
85+
fs::remove_file(file_path).unwrap();
86+
}
87+
}

common/src/ether/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
pub mod bytecode;
12
pub mod compiler;
23
pub mod evm;
34
pub mod lexers;

common/src/ether/rpc.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,7 @@ pub async fn get_code(
147147
)
148148
.map_err(|_| logger.error(&format!("failed to cache bytecode for contract: {:?}", &contract_address)));
149149

150-
Ok(bytecode_as_bytes.to_string())
150+
Ok(bytecode_as_bytes.to_string().replacen("0x", "", 1))
151151
})
152152
.await
153153
.map_err(|_| Box::from("failed to fetch bytecode"))

common/src/ether/selectors.rs

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,41 @@ use tokio::task;
99

1010
use crate::utils::{io::logging::Logger, strings::decode_hex};
1111

12-
use super::{evm::core::vm::VM, signatures::ResolveSelector};
12+
use super::{
13+
evm::core::vm::VM,
14+
signatures::{ResolveSelector, ResolvedFunction},
15+
};
1316
use crate::debug_max;
1417

18+
// Find all function selectors and all the data associated to this function, represented by
19+
// [`ResolvedFunction`]
20+
pub async fn get_resolved_selectors(
21+
disassembled_bytecode: &str,
22+
skip_resolving: &bool,
23+
evm: &VM,
24+
) -> Result<
25+
(HashMap<String, u128>, HashMap<String, Vec<ResolvedFunction>>),
26+
Box<dyn std::error::Error>,
27+
> {
28+
let selectors = find_function_selectors(evm, disassembled_bytecode);
29+
30+
let mut resolved_selectors = HashMap::new();
31+
if !skip_resolving {
32+
resolved_selectors =
33+
resolve_selectors::<ResolvedFunction>(selectors.keys().cloned().collect()).await;
34+
35+
debug_max!(&format!(
36+
"resolved {} possible functions from {} detected selectors.",
37+
resolved_selectors.len(),
38+
selectors.len()
39+
));
40+
} else {
41+
debug_max!(&format!("found {} possible function selectors.", selectors.len()));
42+
}
43+
44+
Ok((selectors, resolved_selectors))
45+
}
46+
1547
/// find all function selectors in the given EVM assembly.
1648
pub fn find_function_selectors(evm: &VM, assembly: &str) -> HashMap<String, u128> {
1749
let mut function_selectors = HashMap::new();

common/src/utils/io/logging.rs

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -673,11 +673,33 @@ impl Logger {
673673
}
674674
}
675675

676+
/// Set `RUST_LOG` variable to env if does not exist
677+
///
678+
/// ```
679+
/// use heimdall_common::utils::io::logging::set_logger_env;
680+
///
681+
/// let verbosity = clap_verbosity_flag::Verbosity::new(-1, 0);
682+
/// set_logger_env(&verbosity);
683+
/// ```
684+
pub fn set_logger_env(verbosity: &clap_verbosity_flag::Verbosity) {
685+
let env_not_set = std::env::var("RUST_LOG").is_err();
686+
687+
if env_not_set {
688+
let log_level = match verbosity.log_level() {
689+
Some(level) => level.as_str(),
690+
None => "SILENT",
691+
};
692+
693+
std::env::set_var("RUST_LOG", log_level);
694+
}
695+
}
696+
676697
#[cfg(test)]
677698
mod tests {
678699
use std::time::Instant;
679700

680701
use super::*;
702+
use std::env;
681703

682704
#[test]
683705
fn test_raw_trace() {
@@ -956,4 +978,15 @@ mod tests {
956978
let (_logger, _) = Logger::new("MAX");
957979
debug_max!("log");
958980
}
981+
982+
#[test]
983+
fn test_set_logger_env_default() {
984+
env::remove_var("RUST_LOG");
985+
986+
let verbosity = clap_verbosity_flag::Verbosity::new(-1, 0);
987+
988+
set_logger_env(&verbosity);
989+
990+
assert_eq!(env::var("RUST_LOG").unwrap(), "SILENT");
991+
}
959992
}

common/src/utils/strings.rs

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -352,6 +352,26 @@ pub fn classify_token(token: &str) -> TokenType {
352352
TokenType::Function
353353
}
354354

355+
/// Returns a collapsed version of a string if this string is greater than 66 characters in length.
356+
/// The collapsed string consists of the first 66 characters, followed by an ellipsis ("..."), and
357+
/// then the last 16 characters of the original string. ```
358+
/// use heimdall_common::utils::strings::get_shortned_target;
359+
///
360+
/// let long_target = "0".repeat(80);
361+
/// let shortened_target = get_shortned_target(&long_target);
362+
/// ```
363+
pub fn get_shortned_target(target: &str) -> String {
364+
let mut shortened_target = target.to_string();
365+
366+
if shortened_target.len() > 66 {
367+
shortened_target = shortened_target.chars().take(66).collect::<String>() +
368+
"..." +
369+
&shortened_target.chars().skip(shortened_target.len() - 16).collect::<String>();
370+
}
371+
372+
shortened_target
373+
}
374+
355375
#[cfg(test)]
356376
mod tests {
357377
use ethers::types::{I256, U256};
@@ -691,4 +711,20 @@ mod tests {
691711
assert_eq!(classification, TokenType::Function);
692712
}
693713
}
714+
715+
#[test]
716+
fn test_shorten_long_target() {
717+
let long_target = "0".repeat(80);
718+
let shortened_target = get_shortned_target(&long_target);
719+
720+
assert_eq!(shortened_target.len(), 85);
721+
}
722+
723+
#[test]
724+
fn test_shorten_short_target() {
725+
let short_target = "0".repeat(66);
726+
let shortened_target = get_shortned_target(&short_target);
727+
728+
assert_eq!(shortened_target.len(), 66);
729+
}
694730
}

0 commit comments

Comments
 (0)