-
Notifications
You must be signed in to change notification settings - Fork 54
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
a343b3b
commit c5edfbc
Showing
3 changed files
with
193 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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()); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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)); | ||
} | ||
} |