Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: function call blocks work #96

Open
wants to merge 3 commits into
base: brock/no_recurse
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions crates/graph/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ tracing-subscriber.workspace = true

itertools = "0.10.5"
lazy_static = "1.4.0"
keccak-hash = "0.10.0"


[dev-dependencies]
pretty_assertions = "1.4.0"
4 changes: 4 additions & 0 deletions crates/graph/src/graph_elements.rs
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,8 @@ pub enum Node {
Concrete(Concrete),
/// The `msg` global in solidity
Msg(Box<Msg>),
/// Local function call environmental data
EnvCtx(EnvCtx),
/// The `block` global in solidity
Block(Box<Block>),
/// A yul-based function
Expand Down Expand Up @@ -395,6 +397,8 @@ pub enum ContextEdge {
// Range analysis
/// Unused
Range,

Env,
}

#[derive(Default)]
Expand Down
1 change: 1 addition & 0 deletions crates/graph/src/nodes/block.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,5 +70,6 @@ pub struct Block {
pub timestamp: Option<U256>,
/// The block's blobhash
pub blobhash: Vec<H256>,
/// Blob base fee
pub blobbasefee: Option<U256>,
}
27 changes: 0 additions & 27 deletions crates/graph/src/nodes/context/typing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,31 +70,4 @@ impl ContextNode {
pub fn is_ext_fn(&self, analyzer: &impl GraphBackend) -> Result<bool, GraphError> {
Ok(self.underlying(analyzer)?.is_ext_fn_call())
}

/// Checks whether a function is external to the current context
pub fn is_fn_ext(
&self,
fn_node: FunctionNode,
analyzer: &mut impl AnalyzerBackend,
) -> Result<bool, GraphError> {
match fn_node.maybe_associated_contract(analyzer) {
None => Ok(false),
Some(fn_ctrt) => {
if let Some(self_ctrt) = self
.associated_fn(analyzer)?
.maybe_associated_contract(analyzer)
{
Ok(Some(self_ctrt) != Some(fn_ctrt)
&& !self_ctrt
.underlying(analyzer)?
.inherits
.iter()
.filter_map(|i| i.as_ref())
.any(|inherited| *inherited == fn_ctrt))
} else {
Ok(false)
}
}
}
}
}
27 changes: 26 additions & 1 deletion crates/graph/src/nodes/context/variables.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
use crate::{
nodes::{ContextNode, ContextVarNode, ExprRet, VarNode},
nodes::{ContextNode, ContextVarNode, EnvCtxNode, ExprRet, VarNode},
AnalyzerBackend, ContextEdge, Edge, GraphBackend, Node, TypeNode,
};
use petgraph::Direction;
use shared::GraphError;

use petgraph::visit::EdgeRef;
Expand Down Expand Up @@ -95,6 +96,30 @@ impl ContextNode {
.copied()
}

pub fn env_or_recurse(
&self,
analyzer: &mut impl AnalyzerBackend,
) -> Result<Option<EnvCtxNode>, GraphError> {
if let Some(env) = analyzer
.graph()
.edges_directed(self.0.into(), Direction::Incoming)
.find(|e| matches!(e.weight(), Edge::Context(ContextEdge::Env)))
.map(|e| e.source())
{
return Ok(Some(env.into()));
}

if let Some(parent) = self.ancestor_in_call(analyzer)? {
if let Some(in_parent) = parent.env_or_recurse(analyzer)? {
return Ok(Some(in_parent));
} else {
Ok(None)
}
} else {
Ok(None)
}
}

/// Gets a variable by name or recurses up the relevant scopes/contexts until it is found
pub fn var_by_name_or_recurse(
&self,
Expand Down
18 changes: 18 additions & 0 deletions crates/graph/src/nodes/context/versioning.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,24 @@ impl ContextNode {
}
}

/// Get the first ancestor context that is in the same function
pub fn ancestor_in_call(
&self,
analyzer: &mut impl AnalyzerBackend,
) -> Result<Option<ContextNode>, GraphError> {
let mut curr_ancestor = self.underlying(analyzer)?.parent_ctx();
while let Some(parent) = curr_ancestor {
let is_ext = parent.is_ext_fn(analyzer)?;
if is_ext {
curr_ancestor = parent.underlying(analyzer)?.parent_ctx();
} else {
return Ok(curr_ancestor);
}
}

Ok(None)
}

/// Returns all forks associated with the context
pub fn calls(&self, analyzer: &impl GraphBackend) -> Result<Vec<Self>, GraphError> {
let descendents = self.descendents(analyzer)?;
Expand Down
189 changes: 189 additions & 0 deletions crates/graph/src/nodes/env_ctx.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
use crate::nodes::Msg;
use crate::{
nodes::{Concrete, ContextNode, ContextVarNode},
range::elem::Elem,
AnalyzerBackend, AsDotStr, ContextEdge, Edge, GraphBackend, Node,
};
use solang_parser::pt::Loc;

use shared::{GraphError, NodeIdx, RangeArena};

#[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash)]
pub struct EnvCtxNode(pub usize);

impl EnvCtxNode {
pub fn underlying<'a>(
&self,
analyzer: &'a impl GraphBackend,
) -> Result<&'a EnvCtx, GraphError> {
match analyzer.node(*self) {
Node::EnvCtx(st) => Ok(st),
Node::Unresolved(ident) => Err(GraphError::UnknownVariable(format!(
"Could not find environment context: {}",
ident.name
))),
e => Err(GraphError::NodeConfusion(format!(
"Node type confusion: expected node to be EnvCtx but it was: {e:?}"
))),
}
}

pub fn underlying_mut<'a>(
&self,
analyzer: &'a mut impl GraphBackend,
) -> Result<&'a mut EnvCtx, GraphError> {
match analyzer.node_mut(*self) {
Node::EnvCtx(st) => Ok(st),
Node::Unresolved(ident) => Err(GraphError::UnknownVariable(format!(
"Could not find environment context: {}",
ident.name
))),
e => Err(GraphError::NodeConfusion(format!(
"Node type confusion: expected node to be EnvCtx but it was: {e:?}"
))),
}
}

pub fn member_access(
&self,
analyzer: &impl GraphBackend,
name: &str,
) -> Result<Option<ContextVarNode>, GraphError> {
tracing::trace!("env access: {name}");
Ok(self.underlying(analyzer)?.get(name))
}

pub fn members(&self, analyzer: &impl GraphBackend) -> Result<Vec<ContextVarNode>, GraphError> {
let underlying = self.underlying(analyzer)?;
Ok(vec![
underlying.this,
underlying.data,
underlying.sender,
underlying.sig,
underlying.value,
underlying.origin,
underlying.gasprice,
underlying.gaslimit,
]
.into_iter()
.flatten()
.collect())
}
}

impl AsDotStr for EnvCtxNode {
fn as_dot_str(
&self,
analyzer: &impl GraphBackend,
_arena: &mut RangeArena<Elem<Concrete>>,
) -> String {
format!("env_ctx {{ {:?} }}", self.underlying(analyzer).unwrap())
}
}

impl From<EnvCtxNode> for NodeIdx {
fn from(val: EnvCtxNode) -> Self {
val.0.into()
}
}

impl From<NodeIdx> for EnvCtxNode {
fn from(idx: NodeIdx) -> Self {
EnvCtxNode(idx.index())
}
}

#[derive(Debug, Clone, Default, Eq, PartialEq)]
pub struct EnvCtx {
pub this: Option<ContextVarNode>,
pub data: Option<ContextVarNode>,
pub sender: Option<ContextVarNode>,
pub sig: Option<ContextVarNode>,
pub value: Option<ContextVarNode>,
pub origin: Option<ContextVarNode>,
pub gasprice: Option<ContextVarNode>,
pub gaslimit: Option<ContextVarNode>,
}

impl From<EnvCtx> for Node {
fn from(e: EnvCtx) -> Node {
Node::EnvCtx(e)
}
}

impl EnvCtx {
pub fn from_msg(
analyzer: &mut impl AnalyzerBackend,
msg: &Msg,
loc: Loc,
ctx: ContextNode,
) -> Result<Self, GraphError> {
let data_var = msg.context_var_from_str("data", loc, ctx, analyzer)?;
let sender_var = msg.context_var_from_str("sender", loc, ctx, analyzer)?;
let sig_var = msg.context_var_from_str("sig", loc, ctx, analyzer)?;
let value_var = msg.context_var_from_str("value", loc, ctx, analyzer)?;
let origin_var = msg.context_var_from_str("origin", loc, ctx, analyzer)?;
let gasprice_var = msg.context_var_from_str("gasprice", loc, ctx, analyzer)?;
let gaslimit_var = msg.context_var_from_str("gaslimit", loc, ctx, analyzer)?;
let data_node = analyzer.add_node(data_var);
let sender_node = analyzer.add_node(sender_var);
let sig_node = analyzer.add_node(sig_var);
let value_node = analyzer.add_node(value_var);
let origin_node = analyzer.add_node(origin_var);
let gasprice_node = analyzer.add_node(gasprice_var);
let gaslimit_node = analyzer.add_node(gaslimit_var);

Ok(EnvCtx {
this: None,
data: Some(data_node.into()),
sender: Some(sender_node.into()),
sig: Some(sig_node.into()),
value: Some(value_node.into()),
origin: Some(origin_node.into()),
gasprice: Some(gasprice_node.into()),
gaslimit: Some(gaslimit_node.into()),
})
}

pub fn set(&mut self, name: &str, val: impl Into<ContextVarNode>) {
match name {
"this" => self.this = Some(val.into()),
"data" => self.data = Some(val.into()),
"sender" => self.sender = Some(val.into()),
"sig" => self.sig = Some(val.into()),
"value" => self.value = Some(val.into()),
"origin" => self.origin = Some(val.into()),
"gasprice" => self.gasprice = Some(val.into()),
"gaslimit" | "gas" => self.gaslimit = Some(val.into()),
_ => unreachable!(),
}
}

pub fn get(&self, name: &str) -> Option<ContextVarNode> {
match name {
"this" => self.this,
"data" => self.data,
"sender" => self.sender,
"sig" => self.sig,
"value" => self.value,
"origin" => self.origin,
"gasprice" => self.gasprice,
"gaslimit" | "gas" => self.gaslimit,
_ => None,
}
}

pub fn get_name(name: &str) -> Option<String> {
match name {
"this" => Some("this".to_string()),
"data" => Some("msg.data".to_string()),
"sender" => Some("msg.sender".to_string()),
"sig" => Some("msg.sig".to_string()),
"value" => Some("msg.value".to_string()),
"origin" => Some("tx.origin".to_string()),
"gasprice" => Some("tx.gasprice".to_string()),
"gaslimit" | "gas" => Some("gasleft()".to_string()),
_ => None,
}
}
}
14 changes: 14 additions & 0 deletions crates/graph/src/nodes/func_ty.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use crate::{
range::elem::Elem,
AnalyzerBackend, AsDotStr, ContextEdge, Edge, GraphBackend, Node, SolcRange, VarType,
};
use ethers_core::types::H256;

use shared::{GraphError, NodeIdx, RangeArena, Search, StorageLocation};

Expand Down Expand Up @@ -44,6 +45,19 @@ impl FunctionNode {
}
}

pub fn sig(&self, analyzer: &impl GraphBackend) -> Result<Option<Concrete>, GraphError> {
if !self.is_public_or_ext(analyzer)? {
Ok(None)
} else {
let name = self.name(analyzer)?;
let mut out = [0; 32];
keccak_hash::keccak_256(name.as_bytes(), &mut out);
let mut sig = [0; 32];
(0..4).for_each(|j| sig[j] = out[j]);
Ok(Some(Concrete::Bytes(4, H256(sig))))
}
}

pub fn add_gas_cost(
&mut self,
analyzer: &mut impl GraphBackend,
Expand Down
3 changes: 3 additions & 0 deletions crates/graph/src/nodes/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,3 +45,6 @@ pub use debug_reconstruction::*;

mod yul_func;
pub use yul_func::*;

mod env_ctx;
pub use env_ctx::*;
1 change: 1 addition & 0 deletions crates/graph/src/var_type.rs
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,7 @@ impl VarType {
| Node::Entry
| Node::Context(..)
| Node::Msg(_)
| Node::EnvCtx(_)
| Node::Block(_)
| Node::YulFunction(..) => None,
}
Expand Down
Loading
Loading