Skip to content

Commit

Permalink
chore(decompile): separate extcall logic from solidity heuristic
Browse files Browse the repository at this point in the history
  • Loading branch information
Jon-Becker committed Oct 11, 2024
1 parent 06cfc61 commit 56ccb52
Show file tree
Hide file tree
Showing 28 changed files with 370 additions and 245 deletions.
17 changes: 7 additions & 10 deletions crates/cache/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -215,8 +215,7 @@ pub fn delete_cache(key: &str) -> Result<(), Error> {
#[allow(deprecated)]
pub fn read_cache<T>(key: &str) -> Result<Option<T>, Error>
where
T: 'static + DeserializeOwned,
{
T: 'static + DeserializeOwned, {
let home = home_dir().ok_or(Error::Generic(
"failed to get home directory. does your os support `std::env::home_dir()`?".to_string(),
))?;
Expand All @@ -239,8 +238,8 @@ where
.map_err(|e| Error::Generic(format!("failed to deserialize cache object: {:?}", e)))?;

// check if the cache has expired, if so, delete it and return None
if cache.expiry
< std::time::SystemTime::now()
if cache.expiry <
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map_err(|e| Error::Generic(format!("failed to get current time: {:?}", e)))?
.as_secs()
Expand All @@ -267,8 +266,7 @@ where
#[allow(deprecated)]
pub fn store_cache<T>(key: &str, value: T, expiry: Option<u64>) -> Result<(), Error>
where
T: Serialize,
{
T: Serialize, {
let home = home_dir().ok_or(Error::Generic(
"failed to get home directory. does your os support `std::env::home_dir()`?".to_string(),
))?;
Expand All @@ -280,8 +278,8 @@ where
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map_err(|e| Error::Generic(format!("failed to get current time: {:?}", e)))?
.as_secs()
+ 60 * 60 * 24 * 90,
.as_secs() +
60 * 60 * 24 * 90,
);

let cache = Cache { value, expiry };
Expand All @@ -306,8 +304,7 @@ pub async fn with_cache<T, F, Fut>(key: &str, func: F) -> eyre::Result<T>
where
T: 'static + Serialize + DeserializeOwned + Send + Sync,
F: FnOnce() -> Fut + Send,
Fut: std::future::Future<Output = Result<T, eyre::Report>> + Send,
{
Fut: std::future::Future<Output = Result<T, eyre::Report>> + Send, {
// Try to read from cache
match read_cache::<T>(key) {
Ok(Some(cached_value)) => {
Expand Down
4 changes: 2 additions & 2 deletions crates/cfg/src/core/graph.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,8 @@ pub fn build_cfg(
.first()
.ok_or_eyre("failed to get first operation")?
.last_instruction
.opcode
== JUMPDEST,
.opcode ==
JUMPDEST,
)?;
}

Expand Down
10 changes: 5 additions & 5 deletions crates/common/src/ether/signatures.rs
Original file line number Diff line number Diff line change
Expand Up @@ -286,18 +286,18 @@ pub fn score_signature(signature: &str, num_words: Option<usize>) -> u32 {

// prioritize signatures with less numbers
score -= (signature.split('(').next().unwrap_or("").matches(|c: char| c.is_numeric()).count()
as u32)
* 3;
as u32) *
3;

// prioritize signatures with parameters
let num_params = signature.matches(',').count() + 1;
score += num_params as u32 * 10;

// count the number of parameters in the signature, if enabled
if let Some(num_words) = num_words {
let num_dyn_params = signature.matches("bytes").count()
+ signature.matches("string").count()
+ signature.matches('[').count();
let num_dyn_params = signature.matches("bytes").count() +
signature.matches("string").count() +
signature.matches('[').count();
let num_static_params = num_params - num_dyn_params;

// reduce the score if the signature has less static parameters than there are words in the
Expand Down
28 changes: 14 additions & 14 deletions crates/common/src/ether/tokenize.rs
Original file line number Diff line number Diff line change
Expand Up @@ -130,17 +130,17 @@ pub fn tokenize(s: &str) -> Token {
let mut op = ch.to_string();
iter.next();
if let Some(&next_ch) = iter.peek() {
if (ch == '=' && (next_ch == '=' || next_ch == '>'))
|| (ch == '&' && next_ch == '&')
|| (ch == '|' && next_ch == '|')
|| (ch == '<' && next_ch == '=')
|| (ch == '>' && next_ch == '=')
|| (ch == '!' && next_ch == '=')
|| (ch == '+' && next_ch == '+')
|| (ch == '-' && next_ch == '-')
|| (ch == '*' && next_ch == '*')
|| (ch == '>' && next_ch == '>')
|| (ch == '<' && next_ch == '<')
if (ch == '=' && (next_ch == '=' || next_ch == '>')) ||
(ch == '&' && next_ch == '&') ||
(ch == '|' && next_ch == '|') ||
(ch == '<' && next_ch == '=') ||
(ch == '>' && next_ch == '=') ||
(ch == '!' && next_ch == '=') ||
(ch == '+' && next_ch == '+') ||
(ch == '-' && next_ch == '-') ||
(ch == '*' && next_ch == '*') ||
(ch == '>' && next_ch == '>') ||
(ch == '<' && next_ch == '<')
{
op.push(next_ch);
iter.next();
Expand Down Expand Up @@ -188,9 +188,9 @@ fn parse_literal(iter: &mut std::iter::Peekable<std::str::Chars>) -> String {
}

// literal validation
if literal.starts_with("0x")
&& literal.len() > 2
&& literal[2..].chars().all(|c| c.is_ascii_hexdigit())
if literal.starts_with("0x") &&
literal.len() > 2 &&
literal[2..].chars().all(|c| c.is_ascii_hexdigit())
{
return literal;
}
Expand Down
4 changes: 2 additions & 2 deletions crates/common/src/utils/strings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -330,8 +330,8 @@ pub fn tokenize(s: &str) -> Vec<String> {
// Check if current character and last character form a compound operator (like "==",
// ">=", "&&", "||")
if let Some(last) = last_char {
if compound_operator_first_chars.contains(&last)
&& (c == '=' || c == '&' || c == '|')
if compound_operator_first_chars.contains(&last) &&
(c == '=' || c == '&' || c == '|')
{
// Remove the last character as a single token
tokens.pop();
Expand Down
3 changes: 1 addition & 2 deletions crates/common/src/utils/sync.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
/// Take in a non-async function and await it. This functions should be blocking.
pub fn blocking_await<F, T>(f: F) -> T
where
F: FnOnce() -> T,
{
F: FnOnce() -> T, {
tokio::task::block_in_place(f)
}
40 changes: 20 additions & 20 deletions crates/common/src/utils/version.rs
Original file line number Diff line number Diff line change
Expand Up @@ -93,46 +93,46 @@ impl Display for Version {
impl Version {
/// greater than
pub fn gt(&self, other: &Version) -> bool {
self.major > other.major
|| (self.major == other.major && self.minor > other.minor)
|| (self.major == other.major && self.minor == other.minor && self.patch > other.patch)
self.major > other.major ||
(self.major == other.major && self.minor > other.minor) ||
(self.major == other.major && self.minor == other.minor && self.patch > other.patch)
}

/// greater than or equal to
pub fn gte(&self, other: &Version) -> bool {
self.major > other.major
|| (self.major == other.major && self.minor > other.minor)
|| (self.major == other.major && self.minor == other.minor && self.patch >= other.patch)
self.major > other.major ||
(self.major == other.major && self.minor > other.minor) ||
(self.major == other.major && self.minor == other.minor && self.patch >= other.patch)
}

/// less than
pub fn lt(&self, other: &Version) -> bool {
self.major < other.major
|| (self.major == other.major && self.minor < other.minor)
|| (self.major == other.major && self.minor == other.minor && self.patch < other.patch)
self.major < other.major ||
(self.major == other.major && self.minor < other.minor) ||
(self.major == other.major && self.minor == other.minor && self.patch < other.patch)
}

/// less than or equal to
pub fn lte(&self, other: &Version) -> bool {
self.major < other.major
|| (self.major == other.major && self.minor < other.minor)
|| (self.major == other.major && self.minor == other.minor && self.patch <= other.patch)
self.major < other.major ||
(self.major == other.major && self.minor < other.minor) ||
(self.major == other.major && self.minor == other.minor && self.patch <= other.patch)
}

#[allow(clippy::should_implement_trait)]
pub fn eq(&self, other: &Version) -> bool {
self.major == other.major
&& self.minor == other.minor
&& self.patch == other.patch
&& self.channel == other.channel
self.major == other.major &&
self.minor == other.minor &&
self.patch == other.patch &&
self.channel == other.channel
}

/// not equal to
pub fn ne(&self, other: &Version) -> bool {
self.major != other.major
|| self.minor != other.minor
|| self.patch != other.patch
|| self.channel != other.channel
self.major != other.major ||
self.minor != other.minor ||
self.patch != other.patch ||
self.channel != other.channel
}

/// if the version is a nightly version
Expand Down
20 changes: 10 additions & 10 deletions crates/decode/src/utils/abi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -185,8 +185,8 @@ fn try_decode_dynamic_parameter_bytes(
// (5) we've covered all words from `data_start_word_offset` to `data_end_word_offset`,
// so add them to `word_coverages`.
coverages.extend(
(word_offset.try_into().unwrap_or(usize::MAX)
..data_end_word_offset.try_into().unwrap_or(usize::MAX))
(word_offset.try_into().unwrap_or(usize::MAX)..
data_end_word_offset.try_into().unwrap_or(usize::MAX))
.collect::<Vec<usize>>(),
);

Expand All @@ -211,8 +211,8 @@ fn try_decode_dynamic_parameter_array(

// (1) join all words from `data_start_word_offset` to `data_end_word_offset`. This is where
// the encoded data may be stored.
let data_words = &calldata_words[data_start_word_offset.try_into().unwrap_or(usize::MAX)
..data_end_word_offset.try_into().unwrap_or(usize::MAX)];
let data_words = &calldata_words[data_start_word_offset.try_into().unwrap_or(usize::MAX)..
data_end_word_offset.try_into().unwrap_or(usize::MAX)];
trace!("potential array items: {:#?}", data_words);

// (2) first, check if this is a `string` type, since some string encodings may appear to be
Expand All @@ -234,8 +234,8 @@ fn try_decode_dynamic_parameter_array(
// `word_coverages` with the indices of all words from `data_start_word_offset` to
// `data_end_word_offset`, since we've now covered all words in the ABI-encoded type.
coverages.extend(
(word_offset.try_into().unwrap_or(usize::MAX)
..data_end_word_offset.try_into().unwrap_or(usize::MAX))
(word_offset.try_into().unwrap_or(usize::MAX)..
data_end_word_offset.try_into().unwrap_or(usize::MAX))
.collect::<Vec<usize>>(),
);

Expand Down Expand Up @@ -296,8 +296,8 @@ fn try_decode_dynamic_parameter_string(
trace!(
"with data: {:#?}",
encode_hex(
&calldata_words[data_start_word_offset.try_into().unwrap_or(usize::MAX)
..data_end_word_offset.try_into().unwrap_or(usize::MAX)]
&calldata_words[data_start_word_offset.try_into().unwrap_or(usize::MAX)..
data_end_word_offset.try_into().unwrap_or(usize::MAX)]
.concat()
)
);
Expand All @@ -321,8 +321,8 @@ fn try_decode_dynamic_parameter_string(
// (5) we've covered all words from `data_start_word_offset` to `data_end_word_offset`,
// so add them to `word_coverages`.
coverages.extend(
(word_offset.try_into().unwrap_or(usize::MAX)
..data_end_word_offset.try_into().unwrap_or(usize::MAX))
(word_offset.try_into().unwrap_or(usize::MAX)..
data_end_word_offset.try_into().unwrap_or(usize::MAX))
.collect::<Vec<usize>>(),
);

Expand Down
12 changes: 6 additions & 6 deletions crates/decode/src/utils/constructor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,16 +42,16 @@ pub fn parse_deployment_bytecode(input: Vec<u8>) -> Result<Constructor> {

let constructor_offset = 0;
let metadata_length = u32::from_str_radix(
&input[(contract_offset + contract_length - 4) as usize
..(contract_offset + contract_length) as usize],
&input[(contract_offset + contract_length - 4) as usize..
(contract_offset + contract_length) as usize],
16,
)? * 2
+ 4;
)? * 2 +
4;

let constructor = &input[constructor_offset as usize..contract_offset as usize];
let contract = &input[contract_offset as usize..(contract_offset + contract_length) as usize];
let metadata = &input[(contract_offset + contract_length - metadata_length) as usize
..(contract_offset + contract_length) as usize];
let metadata = &input[(contract_offset + contract_length - metadata_length) as usize..
(contract_offset + contract_length) as usize];
let arguments = &input[(contract_offset + contract_length) as usize..];

Ok(Constructor {
Expand Down
13 changes: 7 additions & 6 deletions crates/decompile/src/core/analyze.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ use tracing::debug;
use crate::{
interfaces::AnalyzedFunction,
utils::heuristics::{
argument_heuristic, event_heuristic, modifier_heuristic, solidity_heuristic, yul_heuristic,
Heuristic,
argument_heuristic, event_heuristic, extcall_heuristic, modifier_heuristic,
solidity_heuristic, yul_heuristic, Heuristic,
},
Error,
};
Expand Down Expand Up @@ -85,6 +85,7 @@ impl Analyzer {
self.heuristics.push(Heuristic::new(solidity_heuristic));
self.heuristics.push(Heuristic::new(argument_heuristic));
self.heuristics.push(Heuristic::new(modifier_heuristic));
self.heuristics.push(Heuristic::new(extcall_heuristic));
}
AnalyzerType::Yul => {
self.heuristics.push(Heuristic::new(event_heuristic));
Expand Down Expand Up @@ -156,8 +157,8 @@ impl Analyzer {
}

// check if the ending brackets are needed
if analyzer_state.jumped_conditional.is_some()
&& analyzer_state.conditional_stack.contains(
if analyzer_state.jumped_conditional.is_some() &&
analyzer_state.conditional_stack.contains(
analyzer_state
.jumped_conditional
.as_ref()
Expand All @@ -166,8 +167,8 @@ impl Analyzer {
{
// remove the conditional
for (i, conditional) in analyzer_state.conditional_stack.iter().enumerate() {
if conditional
== analyzer_state.jumped_conditional.as_ref().expect(
if conditional ==
analyzer_state.jumped_conditional.as_ref().expect(
"impossible case: should have short-circuited in previous conditional",
)
{
Expand Down
6 changes: 3 additions & 3 deletions crates/decompile/src/core/out/source.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,9 +67,9 @@ pub fn build_source(
functions
.iter()
.filter(|f| {
!f.fallback
&& (analyzer_type == AnalyzerType::Yul
|| (f.maybe_getter_for.is_none() && !f.is_constant()))
!f.fallback &&
(analyzer_type == AnalyzerType::Yul ||
(f.maybe_getter_for.is_none() && !f.is_constant()))
})
.for_each(|f| {
let mut function_source = Vec::new();
Expand Down
4 changes: 2 additions & 2 deletions crates/decompile/src/core/postprocess.rs
Original file line number Diff line number Diff line change
Expand Up @@ -130,8 +130,8 @@ impl PostprocessOrchestrator {
// Note: this can't be done with a postprocessor because it needs all lines
if !function.payable && (function.pure || function.view) && function.arguments.is_empty() {
// check for RLP encoding. very naive check, but it works for now
if function.logic.iter().any(|line| line.contains("0x0100 *"))
&& function.logic.iter().any(|line| line.contains("0x01) &"))
if function.logic.iter().any(|line| line.contains("0x0100 *")) &&
function.logic.iter().any(|line| line.contains("0x01) &"))
{
// find any storage accesses
let joined = function.logic.join(" ");
Expand Down
4 changes: 2 additions & 2 deletions crates/decompile/src/utils/heuristics/arguments.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@ pub fn argument_heuristic(
// calculate the argument index, with the 4byte signature padding removed
// for example, CALLDATALOAD(4) -> (4-4)/32 = 0
// CALLDATALOAD(36) -> (36-4)/32 = 1
let arg_index = (state.last_instruction.inputs[0].saturating_sub(U256::from(4))
/ U256::from(32))
let arg_index = (state.last_instruction.inputs[0].saturating_sub(U256::from(4)) /
U256::from(32))
.try_into()
.unwrap_or(usize::MAX);

Expand Down
Loading

0 comments on commit 56ccb52

Please sign in to comment.