Skip to content

Commit

Permalink
external calls detector
Browse files Browse the repository at this point in the history
  • Loading branch information
TilakMaddy committed Oct 6, 2024
1 parent a343b3b commit c5edfbc
Show file tree
Hide file tree
Showing 3 changed files with 193 additions and 0 deletions.
106 changes: 106 additions & 0 deletions aderyn_core/src/context/browser/external_calls.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
//! This module helps us detect whether a given AST Node has any external calls inside of it

use super::ExtractMemberAccesses;
use crate::{ast::*, context::workspace_context::ASTNode};

fn is_external_call(ast_node: ASTNode) -> bool {
// This is so we can skip the FunctionCallOptions layer which solidity compiler inserts
// when there are options passed to function calls
for member_access in ExtractMemberAccesses::from(&ast_node).extracted {
// address(..).call("...") pattern
let is_call = member_access.member_name == "call";
if is_call {
return true;
}

// payable(address(..)).transfer(100)
// payable(address(..)).send(100)
// address.sendValue(..) (from openzeppelin)
if member_access.member_name == "transfer"
|| member_access.member_name == "send"
|| member_access.member_name == "sendValue"
{
if let Some(type_description) = member_access.expression.type_descriptions() {
if type_description
.type_string
.as_ref()
.is_some_and(|type_string| type_string.starts_with("address"))
{
return true;
}
}
}

// Any external call
if member_access
.type_descriptions
.type_identifier
.is_some_and(|type_identifier| type_identifier.contains("function_external"))
{
return true;
}
}

false
}

impl FunctionCall {
pub fn is_external_call(&self) -> bool {
is_external_call(self.into())
}
}
impl FunctionCallOptions {
pub fn is_external_call(&self) -> bool {
is_external_call(self.into())
}
}

#[cfg(test)]
mod external_calls_detector {
use crate::{
context::browser::ExtractFunctionCalls, detect::test_utils::load_solidity_source_unit,
};

use super::FunctionDefinition;

impl FunctionDefinition {
pub fn makes_external_calls(&self) -> bool {
let func_calls = ExtractFunctionCalls::from(self).extracted;
func_calls.iter().any(|f| f.is_external_call())
}
}

#[test]
fn test_direct_call_on_address() {
let context =
load_solidity_source_unit("../tests/contract-playground/src/ExternalCalls.sol");

let childex = context.find_contract_by_name("ChildEx");

let ext1 = childex.find_function_by_name("ext1");
let ext2 = childex.find_function_by_name("ext2");
let ext3 = childex.find_function_by_name("ext3");
let ext4 = childex.find_function_by_name("ext4");
let ext5 = childex.find_function_by_name("ext5");
let ext6 = childex.find_function_by_name("ext6");
let ext7 = childex.find_function_by_name("ext7");
let ext8 = childex.find_function_by_name("ext8");
let ext9 = childex.find_function_by_name("ext9");

assert!(ext1.makes_external_calls());
assert!(ext2.makes_external_calls());
assert!(ext3.makes_external_calls());
assert!(ext4.makes_external_calls());
assert!(ext5.makes_external_calls());
assert!(ext6.makes_external_calls());
assert!(ext7.makes_external_calls());
assert!(ext8.makes_external_calls());
assert!(ext9.makes_external_calls());

let notext1 = childex.find_function_by_name("notExt1");
let notext2 = childex.find_function_by_name("notExt2");

assert!(!notext1.makes_external_calls());
assert!(!notext2.makes_external_calls());
}
}
2 changes: 2 additions & 0 deletions aderyn_core/src/context/browser/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
mod ancestral_line;
mod closest_ancestor;
mod external_calls;
mod extractor;
mod immediate_children;
mod location;
Expand All @@ -13,6 +14,7 @@ mod sort_nodes;
mod storage_vars;
pub use ancestral_line::*;
pub use closest_ancestor::*;
pub use external_calls::*;

Check failure on line 17 in aderyn_core/src/context/browser/mod.rs

View workflow job for this annotation

GitHub Actions / Lints

unused import: `external_calls::*`

Check failure on line 17 in aderyn_core/src/context/browser/mod.rs

View workflow job for this annotation

GitHub Actions / Lints

unused import: `external_calls::*`

Check warning on line 17 in aderyn_core/src/context/browser/mod.rs

View workflow job for this annotation

GitHub Actions / Check

unused import: `external_calls::*`

Check warning on line 17 in aderyn_core/src/context/browser/mod.rs

View workflow job for this annotation

GitHub Actions / Check

unused import: `external_calls::*`

Check warning on line 17 in aderyn_core/src/context/browser/mod.rs

View workflow job for this annotation

GitHub Actions / Check

unused import: `external_calls::*`

Check warning on line 17 in aderyn_core/src/context/browser/mod.rs

View workflow job for this annotation

GitHub Actions / Check

unused import: `external_calls::*`
pub use extractor::*;
pub use immediate_children::*;
pub use location::*;
Expand Down
85 changes: 85 additions & 0 deletions tests/contract-playground/src/ExternalCalls.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.19;

interface IMyTarget {
function extCall(uint256) external payable;
}

contract MyTarget is IMyTarget {
uint256 s_a;

function extCall(uint256 m_a) public payable {
s_a = m_a;
}
}

contract BaseEx {
MyTarget t2;

function baseThing(address x) public {
t2 = MyTarget(x);
}
}

contract ChildEx is BaseEx {
MyTarget t1;

constructor(MyTarget t) {
t1 = t;
}

// Functions that make external calls

function ext1() external payable {
t1.extCall(0);
}

function ext2(address target) external {
MyTarget(target).extCall(0);
}

function ext3() external {
this.ext1();
}

function ext4() external {
IMyTarget(address(t1)).extCall(0);
}

// Functions that make external calls with options

function ext5() external {
t1.extCall{gas: 100}(0);
}

function ext6(address target) external {
MyTarget(target).extCall{value: 100}(0);
}

function ext7() external {
this.ext1{gas: 100}();
}

function ext8() external {
IMyTarget(address(t1)).extCall{value: 100}(0);
}

function ext9() external {
(bool success, ) = payable(address(t1)).call{
value: address(this).balance
}("");
if (success) {
revert();
}
}

// Functions that don't make external calls

function notExt1() external {
super.baseThing(address(0));
}

function notExt2() external {
BaseEx.baseThing(address(0));
}
}

0 comments on commit c5edfbc

Please sign in to comment.