From b4243e9a5f81c3a69e34d0d9d7695460c5d37bd3 Mon Sep 17 00:00:00 2001
From: brock elmore <brock.elmore@gmail.com>
Date: Mon, 11 Dec 2023 10:56:48 -0800
Subject: [PATCH] mostly documentation and some reorg

---
 crates/solc-expressions/src/array.rs          |    1 +
 crates/solc-expressions/src/bin_op.rs         |    1 +
 crates/solc-expressions/src/cmp.rs            |    1 +
 crates/solc-expressions/src/cond_op.rs        |    3 +
 .../src/context_builder/mod.rs                |   26 +-
 crates/solc-expressions/src/env.rs            |    3 +-
 .../src/func_call/func_caller.rs              |  380 ++++++
 .../solc-expressions/src/func_call/helper.rs  |  585 ++++++++
 .../src/func_call/internal_call.rs            |  175 +--
 .../src/func_call/intrinsic_call/abi.rs       |    5 +-
 .../src/func_call/intrinsic_call/address.rs   |    3 +
 .../src/func_call/intrinsic_call/array.rs     |    3 +
 .../src/func_call/intrinsic_call/block.rs     |    3 +
 .../func_call/intrinsic_call/constructors.rs  |   11 +-
 .../func_call/intrinsic_call/dyn_builtin.rs   |    6 +
 .../intrinsic_call/intrinsic_caller.rs        |    7 +-
 .../src/func_call/intrinsic_call/mod.rs       |    1 +
 .../src/func_call/intrinsic_call/msg.rs       |    3 +
 .../func_call/intrinsic_call/precompile.rs    |    9 +-
 .../src/func_call/intrinsic_call/solidity.rs  |    9 +-
 .../src/func_call/intrinsic_call/types.rs     |    6 +-
 crates/solc-expressions/src/func_call/mod.rs  | 1195 +----------------
 .../src/func_call/modifier.rs                 |  285 +++-
 .../src/func_call/namespaced_call.rs          |   16 +-
 crates/solc-expressions/src/lib.rs            |   11 +-
 crates/solc-expressions/src/list.rs           |    2 +-
 crates/solc-expressions/src/literal.rs        |    1 +
 crates/solc-expressions/src/loops.rs          |    5 +
 .../src/member_access/builtin_access.rs       |    3 +
 .../src/member_access/contract_access.rs      |    3 +
 .../src/member_access/enum_access.rs          |    3 +
 .../src/member_access/library_access.rs       |    4 +
 .../src/member_access/list_access.rs          |    6 +
 .../src/member_access/member_trait.rs         |  161 +--
 .../solc-expressions/src/member_access/mod.rs |    3 +
 .../src/member_access/struct_access.rs        |    2 +
 crates/solc-expressions/src/require.rs        |    3 +
 crates/solc-expressions/src/variable.rs       |    3 +-
 crates/solc-expressions/src/yul/mod.rs        |  382 +-----
 .../solc-expressions/src/yul/yul_builder.rs   |  388 ++++++
 .../solc-expressions/src/yul/yul_cond_op.rs   |   11 +-
 crates/solc-expressions/src/yul/yul_funcs.rs  |    2 +-
 42 files changed, 1960 insertions(+), 1770 deletions(-)
 create mode 100644 crates/solc-expressions/src/func_call/func_caller.rs
 create mode 100644 crates/solc-expressions/src/func_call/helper.rs
 create mode 100644 crates/solc-expressions/src/yul/yul_builder.rs

diff --git a/crates/solc-expressions/src/array.rs b/crates/solc-expressions/src/array.rs
index b603e39b..2045d818 100644
--- a/crates/solc-expressions/src/array.rs
+++ b/crates/solc-expressions/src/array.rs
@@ -12,6 +12,7 @@ use solang_parser::{
 };
 
 impl<T> Array for T where T: AnalyzerBackend<Expr = Expression, ExprErr = ExprErr> + Sized {}
+/// Handles arrays
 pub trait Array: AnalyzerBackend<Expr = Expression, ExprErr = ExprErr> + Sized {
     /// Gets the array type
     #[tracing::instrument(level = "trace", skip_all)]
diff --git a/crates/solc-expressions/src/bin_op.rs b/crates/solc-expressions/src/bin_op.rs
index 712cfe7b..5f855b1b 100644
--- a/crates/solc-expressions/src/bin_op.rs
+++ b/crates/solc-expressions/src/bin_op.rs
@@ -13,6 +13,7 @@ use ethers_core::types::{I256, U256};
 use solang_parser::pt::{Expression, Loc};
 
 impl<T> BinOp for T where T: AnalyzerBackend<Expr = Expression, ExprErr = ExprErr> + Sized {}
+/// Handles binary operations (`+`, `-`, `/`, etc.)
 pub trait BinOp: AnalyzerBackend<Expr = Expression, ExprErr = ExprErr> + Sized {
     /// Evaluate and execute a binary operation expression
     #[tracing::instrument(level = "trace", skip_all)]
diff --git a/crates/solc-expressions/src/cmp.rs b/crates/solc-expressions/src/cmp.rs
index 771e54e4..948e34a2 100644
--- a/crates/solc-expressions/src/cmp.rs
+++ b/crates/solc-expressions/src/cmp.rs
@@ -13,6 +13,7 @@ use solang_parser::pt::{Expression, Loc};
 use std::cmp::Ordering;
 
 impl<T> Cmp for T where T: AnalyzerBackend<Expr = Expression, ExprErr = ExprErr> + Sized {}
+/// Handles comparator operations, i.e: `!`
 pub trait Cmp: AnalyzerBackend<Expr = Expression, ExprErr = ExprErr> + Sized {
     #[tracing::instrument(level = "trace", skip_all)]
     fn not(&mut self, loc: Loc, lhs_expr: &Expression, ctx: ContextNode) -> Result<(), ExprErr> {
diff --git a/crates/solc-expressions/src/cond_op.rs b/crates/solc-expressions/src/cond_op.rs
index 00ac50e9..f96e3307 100644
--- a/crates/solc-expressions/src/cond_op.rs
+++ b/crates/solc-expressions/src/cond_op.rs
@@ -11,8 +11,10 @@ use solang_parser::pt::{Expression, Loc, Statement};
 
 impl<T> CondOp for T where T: AnalyzerBackend<Expr = Expression, ExprErr = ExprErr> + Require + Sized
 {}
+/// Handles conditional operations, like `if .. else ..` and ternary operations
 pub trait CondOp: AnalyzerBackend<Expr = Expression, ExprErr = ExprErr> + Require + Sized {
     #[tracing::instrument(level = "trace", skip_all)]
+    /// Handles a conditional operation like `if .. else ..`
     fn cond_op_stmt(
         &mut self,
         loc: Loc,
@@ -126,6 +128,7 @@ pub trait CondOp: AnalyzerBackend<Expr = Expression, ExprErr = ExprErr> + Requir
         })
     }
 
+    /// Handles a conditional expression like `if .. else ..`
     /// When we have a conditional operator, we create a fork in the context. One side of the fork is
     /// if the expression is true, the other is if it is false.
     #[tracing::instrument(level = "trace", skip_all)]
diff --git a/crates/solc-expressions/src/context_builder/mod.rs b/crates/solc-expressions/src/context_builder/mod.rs
index 0f97cd16..c959f033 100644
--- a/crates/solc-expressions/src/context_builder/mod.rs
+++ b/crates/solc-expressions/src/context_builder/mod.rs
@@ -1,5 +1,6 @@
+//! Trait and blanket implementation for the core parsing loop
 use crate::{
-    func_call::FuncCaller, loops::Looper, yul::YulBuilder, ExprErr, ExprParser, IntoExprErr,
+    func_call::{func_caller::FuncCaller, modifier::ModifierCaller}, loops::Looper, yul::YulBuilder, ExprErr, ExprParser, IntoExprErr,
 };
 
 use graph::{
@@ -24,9 +25,11 @@ impl<T> ContextBuilder for T where
 {
 }
 
+/// Dispatcher for building up a context of a function
 pub trait ContextBuilder:
     AnalyzerBackend<Expr = Expression, ExprErr = ExprErr> + Sized + ExprParser
 {
+    /// Performs setup for parsing a solidity statement
     fn parse_ctx_statement(
         &mut self,
         stmt: &Statement,
@@ -61,6 +64,7 @@ pub trait ContextBuilder:
     }
 
     #[tracing::instrument(level = "trace", skip_all)]
+    /// Performs parsing of a solidity statement
     fn parse_ctx_stmt_inner(
         &mut self,
         stmt: &Statement,
@@ -525,6 +529,7 @@ pub trait ContextBuilder:
         }
     }
 
+    /// TODO: rename this. Sometimes we dont want to kill a context if we hit an error
     fn widen_if_limit_hit(&mut self, ctx: ContextNode, maybe_err: Result<(), ExprErr>) -> bool {
         match maybe_err {
             Err(ExprErr::FunctionCallBlockTodo(_, _s)) => {
@@ -549,6 +554,7 @@ pub trait ContextBuilder:
         }
     }
 
+    /// Match on the [`ExprRet`]s of a return statement and performs the return
     fn return_match(&mut self, ctx: ContextNode, loc: &Loc, paths: &ExprRet) {
         match paths {
             ExprRet::CtxKilled(kind) => {
@@ -582,6 +588,7 @@ pub trait ContextBuilder:
         }
     }
 
+    /// Match on the [`ExprRet`]s of a variable definition
     fn match_var_def(
         &mut self,
         ctx: ContextNode,
@@ -702,6 +709,7 @@ pub trait ContextBuilder:
         }
     }
 
+    /// Perform setup for parsing an expression
     fn parse_ctx_expr(&mut self, expr: &Expression, ctx: ContextNode) -> Result<(), ExprErr> {
         if !ctx.killed_or_ret(self).unwrap() {
             let edges = ctx.live_edges(self).into_expr_err(expr.loc())?;
@@ -719,6 +727,7 @@ pub trait ContextBuilder:
     }
 
     #[tracing::instrument(level = "trace", skip_all, fields(ctx = %ctx.path(self).replace('.', "\n\t.")))]
+    /// Perform parsing of an expression
     fn parse_ctx_expr_inner(&mut self, expr: &Expression, ctx: ContextNode) -> Result<(), ExprErr> {
         use Expression::*;
         // println!(
@@ -1128,6 +1137,7 @@ pub trait ContextBuilder:
         }
     }
 
+    /// Match on the [`ExprRet`]s of a pre-or-post in/decrement and performs it
     fn match_in_de_crement(
         &mut self,
         ctx: ContextNode,
@@ -1217,6 +1227,7 @@ pub trait ContextBuilder:
     }
 
     #[tracing::instrument(level = "trace", skip_all)]
+    /// Parse an assignment expression
     fn assign_exprs(
         &mut self,
         loc: Loc,
@@ -1255,6 +1266,7 @@ pub trait ContextBuilder:
         })
     }
 
+    /// Match on the [`ExprRet`]s of an assignment expression
     fn match_assign_sides(
         &mut self,
         ctx: ContextNode,
@@ -1312,6 +1324,7 @@ pub trait ContextBuilder:
         }
     }
 
+    /// Perform an assignment
     fn assign(
         &mut self,
         loc: Loc,
@@ -1442,6 +1455,8 @@ pub trait ContextBuilder:
     }
 
     #[tracing::instrument(level = "trace", skip_all, fields(ctx = %ctx.path(self)))]
+    /// Creates a newer version of a variable in the context. It may or may not actually
+    /// create this new variable depending on if there are two successively identical version.
     fn advance_var_in_ctx(
         &mut self,
         cvar_node: ContextVarNode,
@@ -1452,6 +1467,8 @@ pub trait ContextBuilder:
     }
 
     #[tracing::instrument(level = "trace", skip_all, fields(ctx = %ctx.path(self)))]
+    /// Creates a new version of a variable in a context. Takes an additional parameter
+    /// denoting whether or not to force the creation, skipping an optimization.
     fn advance_var_in_ctx_forcible(
         &mut self,
         cvar_node: ContextVarNode,
@@ -1526,6 +1543,7 @@ pub trait ContextBuilder:
         Ok(ContextVarNode::from(new_cvarnode))
     }
 
+    /// Creates a new version of a variable in it's current context
     fn advance_var_in_curr_ctx(
         &mut self,
         cvar_node: ContextVarNode,
@@ -1554,6 +1572,7 @@ pub trait ContextBuilder:
         Ok(ContextVarNode::from(new_cvarnode))
     }
 
+    /// 
     fn advance_var_underlying(&mut self, cvar_node: ContextVarNode, loc: Loc) -> &mut ContextVar {
         assert_eq!(None, cvar_node.next_version(self));
         let mut new_cvar = cvar_node
@@ -1569,6 +1588,9 @@ pub trait ContextBuilder:
             .unwrap()
     }
 
+    /// Apply an expression or statement to all *live* edges of a context. This is used everywhere
+    /// to ensure we only ever update *live* contexts. If a context has a subcontext, we *never*
+    /// want to update the original context. We only ever want to operate on the latest edges.
     fn apply_to_edges(
         &mut self,
         ctx: ContextNode,
@@ -1602,6 +1624,8 @@ pub trait ContextBuilder:
         }
     }
 
+    /// The inverse of [`apply_to_edges`], used only for modifiers because modifiers have extremely weird
+    /// dynamics.
     fn take_from_edge<T>(
         &mut self,
         ctx: ContextNode,
diff --git a/crates/solc-expressions/src/env.rs b/crates/solc-expressions/src/env.rs
index e203455e..54060832 100644
--- a/crates/solc-expressions/src/env.rs
+++ b/crates/solc-expressions/src/env.rs
@@ -1,4 +1,4 @@
-use crate::{func_call::FuncCaller, ExprErr, IntoExprErr};
+use crate::{func_call::helper::CallerHelper, func_call::modifier::ModifierCaller, ExprErr, IntoExprErr};
 
 use graph::{
     nodes::{Builtin, Concrete, ContextNode, ContextVar, ExprRet},
@@ -9,6 +9,7 @@ use shared::StorageLocation;
 use solang_parser::pt::{Expression, Identifier, Loc};
 
 impl<T> Env for T where T: AnalyzerBackend<Expr = Expression, ExprErr = ExprErr> + Sized {}
+/// Handles environment based things like `msg`, `block`, etc.
 pub trait Env: AnalyzerBackend<Expr = Expression, ExprErr = ExprErr> + Sized {
     fn env_variable(
         &mut self,
diff --git a/crates/solc-expressions/src/func_call/func_caller.rs b/crates/solc-expressions/src/func_call/func_caller.rs
new file mode 100644
index 00000000..8b06d943
--- /dev/null
+++ b/crates/solc-expressions/src/func_call/func_caller.rs
@@ -0,0 +1,380 @@
+//! Traits & blanket implementations that facilitate performing various forms of function calls.
+
+use crate::{
+    func_call::modifier::ModifierCaller,
+    internal_call::InternalFuncCaller, intrinsic_call::IntrinsicFuncCaller,
+    namespaced_call::NameSpaceFuncCaller, ContextBuilder, ExprErr, IntoExprErr,
+    helper::CallerHelper,
+};
+
+use graph::{
+    nodes::{
+        Context, ContextNode, ContextVar, ContextVarNode, ExprRet, FunctionNode,
+        FunctionParamNode, ModifierState,
+    },
+    AnalyzerBackend, ContextEdge, Edge, GraphBackend, Node,
+};
+use shared::{NodeIdx};
+
+use solang_parser::pt::{Expression, Loc, NamedArgument};
+
+use std::collections::BTreeMap;
+
+impl<T> FuncCaller for T where
+    T: AnalyzerBackend<Expr = Expression, ExprErr = ExprErr> + Sized + GraphBackend + CallerHelper
+{
+}
+/// A trait for calling a function
+pub trait FuncCaller:
+    GraphBackend + AnalyzerBackend<Expr = Expression, ExprErr = ExprErr> + Sized
+{
+    #[tracing::instrument(level = "trace", skip_all)]
+    /// Perform a function call with named inputs
+    fn named_fn_call_expr(
+        &mut self,
+        ctx: ContextNode,
+        loc: &Loc,
+        func_expr: &Expression,
+        input_exprs: &[NamedArgument],
+    ) -> Result<(), ExprErr> {
+        use solang_parser::pt::Expression::*;
+        match func_expr {
+            MemberAccess(loc, member_expr, ident) => {
+                self.call_name_spaced_named_func(ctx, loc, member_expr, ident, input_exprs)
+            }
+            Variable(ident) => self.call_internal_named_func(ctx, loc, ident, input_exprs),
+            e => Err(ExprErr::IntrinsicNamedArgs(
+                *loc,
+                format!("Cannot call intrinsic functions with named arguments. Call: {e:?}"),
+            )),
+        }
+    }
+    #[tracing::instrument(level = "trace", skip_all)]
+    /// Perform a function call
+    fn fn_call_expr(
+        &mut self,
+        ctx: ContextNode,
+        loc: &Loc,
+        func_expr: &Expression,
+        input_exprs: &[Expression],
+    ) -> Result<(), ExprErr> {
+        use solang_parser::pt::Expression::*;
+        match func_expr {
+            MemberAccess(loc, member_expr, ident) => {
+                self.call_name_spaced_func(ctx, loc, member_expr, ident, input_exprs)
+            }
+            Variable(ident) => self.call_internal_func(ctx, loc, ident, func_expr, input_exprs),
+            _ => {
+                self.parse_ctx_expr(func_expr, ctx)?;
+                self.apply_to_edges(ctx, *loc, &|analyzer, ctx, loc| {
+                    let Some(ret) = ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? else {
+                        return Err(ExprErr::NoLhs(
+                            loc,
+                            "Function call to nonexistent function".to_string(),
+                        ));
+                    };
+                    if matches!(ret, ExprRet::CtxKilled(_)) {
+                        ctx.push_expr(ret, analyzer).into_expr_err(loc)?;
+                        return Ok(());
+                    }
+                    analyzer.match_intrinsic_fallback(ctx, &loc, input_exprs, ret)
+                })
+            }
+        }
+    }
+
+    /// Perform an intrinsic function call
+    fn match_intrinsic_fallback(
+        &mut self,
+        ctx: ContextNode,
+        loc: &Loc,
+        input_exprs: &[Expression],
+        ret: ExprRet,
+    ) -> Result<(), ExprErr> {
+        match ret {
+            ExprRet::Single(func_idx) | ExprRet::SingleLiteral(func_idx) => {
+                self.intrinsic_func_call(loc, input_exprs, func_idx, ctx)
+            }
+            ExprRet::Multi(inner) => inner
+                .into_iter()
+                .try_for_each(|ret| self.match_intrinsic_fallback(ctx, loc, input_exprs, ret)),
+            ExprRet::CtxKilled(kind) => ctx.kill(self, *loc, kind).into_expr_err(*loc),
+            ExprRet::Null => Ok(()),
+        }
+    }
+
+    /// Setups up storage variables for a function call and calls it
+    fn setup_fn_call(
+        &mut self,
+        loc: &Loc,
+        inputs: &ExprRet,
+        func_idx: NodeIdx,
+        ctx: ContextNode,
+        func_call_str: Option<&str>,
+    ) -> Result<(), ExprErr> {
+        // if we have a single match thats our function
+        let var = match ContextVar::maybe_from_user_ty(self, *loc, func_idx) {
+            Some(v) => v,
+            None => panic!(
+                "Could not create context variable from user type: {:?}",
+                self.node(func_idx)
+            ),
+        };
+
+        let new_cvarnode = self.add_node(Node::ContextVar(var));
+        ctx.add_var(new_cvarnode.into(), self).into_expr_err(*loc)?;
+        self.add_edge(new_cvarnode, ctx, Edge::Context(ContextEdge::Variable));
+        if let Some(func_node) = ContextVarNode::from(new_cvarnode)
+            .ty(self)
+            .into_expr_err(*loc)?
+            .func_node(self)
+        {
+            self.func_call(ctx, *loc, inputs, func_node, func_call_str, None)
+        } else {
+            unreachable!()
+        }
+    }
+
+    /// Matches the input kinds and performs the call
+    fn func_call(
+        &mut self,
+        ctx: ContextNode,
+        loc: Loc,
+        input_paths: &ExprRet,
+        func: FunctionNode,
+        func_call_str: Option<&str>,
+        modifier_state: Option<ModifierState>,
+    ) -> Result<(), ExprErr> {
+        let params = func.params(self);
+        let input_paths = input_paths.clone().flatten();
+        if input_paths.has_killed() {
+            return ctx
+                .kill(self, loc, input_paths.killed_kind().unwrap())
+                .into_expr_err(loc);
+        }
+        match input_paths {
+            ExprRet::Single(input_var) | ExprRet::SingleLiteral(input_var) => {
+                // if we get a single var, we expect the func to only take a single
+                // variable
+                self.func_call_inner(
+                    false,
+                    ctx,
+                    func,
+                    loc,
+                    vec![ContextVarNode::from(input_var).latest_version(self)],
+                    params,
+                    func_call_str,
+                    modifier_state,
+                )
+            }
+            ExprRet::Multi(ref inputs) => {
+                if ExprRet::Multi(inputs.to_vec()).flatten().has_killed() {
+                    return ctx
+                        .kill(
+                            self,
+                            loc,
+                            ExprRet::Multi(inputs.to_vec()).killed_kind().unwrap(),
+                        )
+                        .into_expr_err(loc);
+                }
+                // check if the inputs length matchs func params length
+                // if they do, check that none are forks
+                if inputs.len() == params.len() {
+                    let input_vars = inputs
+                        .iter()
+                        .map(|expr_ret| {
+                            let var = expr_ret.expect_single().into_expr_err(loc)?;
+                            Ok(ContextVarNode::from(var).latest_version(self))
+                        })
+                        .collect::<Result<Vec<_>, ExprErr>>()?;
+                    self.func_call_inner(
+                        false,
+                        ctx,
+                        func,
+                        loc,
+                        input_vars,
+                        params,
+                        func_call_str,
+                        modifier_state,
+                    )
+                } else {
+                    Err(ExprErr::InvalidFunctionInput(
+                        loc,
+                        format!(
+                            "Length mismatch: {inputs:?} {params:?}, inputs as vars: {}, ctx: {}",
+                            ExprRet::Multi(inputs.to_vec()).debug_str(self),
+                            ctx.path(self)
+                        ),
+                    ))
+                }
+            }
+            e => todo!("here: {:?}", e),
+        }
+    }
+
+    /// Checks if there are any modifiers and executes them prior to executing the function
+    #[tracing::instrument(level = "trace", skip_all)]
+    fn func_call_inner(
+        &mut self,
+        entry_call: bool,
+        ctx: ContextNode,
+        func_node: FunctionNode,
+        loc: Loc,
+        inputs: Vec<ContextVarNode>,
+        params: Vec<FunctionParamNode>,
+        func_call_str: Option<&str>,
+        modifier_state: Option<ModifierState>,
+    ) -> Result<(), ExprErr> {
+        // pseudocode:
+        //  1. Create context for the call
+        //  2. Check for modifiers
+        //  3. Call modifier 0, then 1, then 2, ... then N.
+        //  4. Call this function
+        //  5. Finish modifier N.. then 2, then 1, then 0
+        let callee_ctx = if entry_call {
+            ctx
+        } else {
+            self.create_call_ctx(ctx, loc, func_node, modifier_state)?
+        };
+
+        // TODO: implement joining
+        // if !entry_call {
+        //     let mapping = params
+        //         .iter()
+        //         .zip(inputs.iter())
+        //         .map(|(param, input)| (*input, *param))
+        //         .collect::<BTreeMap<_, _>>();
+        //     ctx.join(func_node, &mapping, self);
+        // }
+
+        // handle remapping of variable names and bringing variables into the new context
+        let renamed_inputs =
+            self.map_inputs_to_params(loc, entry_call, params, inputs, callee_ctx)?;
+
+        // begin modifier handling by making sure modifiers were set
+        if !func_node.modifiers_set(self).into_expr_err(loc)? {
+            self.set_modifiers(func_node, ctx)?;
+        }
+
+        // get modifiers
+        let mods = func_node.modifiers(self);
+        self.apply_to_edges(callee_ctx, loc, &|analyzer, callee_ctx, loc| {
+            if let Some(mod_state) = &ctx.underlying(analyzer).into_expr_err(loc)?.modifier_state {
+                // we are iterating through modifiers
+                if mod_state.num + 1 < mods.len() {
+                    // use the next modifier
+                    let mut mstate = mod_state.clone();
+                    mstate.num += 1;
+                    analyzer.call_modifier_for_fn(loc, callee_ctx, func_node, mstate)
+                } else {
+                    // out of modifiers, execute the actual function call
+                    analyzer.execute_call_inner(
+                        loc,
+                        ctx,
+                        callee_ctx,
+                        func_node,
+                        &renamed_inputs,
+                        func_call_str,
+                    )
+                }
+            } else if !mods.is_empty() {
+                // we have modifiers and havent executed them, start the process of executing them
+                let state =
+                    ModifierState::new(0, loc, func_node, callee_ctx, ctx, renamed_inputs.clone());
+                analyzer.call_modifier_for_fn(loc, callee_ctx, func_node, state)
+            } else {
+                // no modifiers, just execute the function
+                analyzer.execute_call_inner(
+                    loc,
+                    ctx,
+                    callee_ctx,
+                    func_node,
+                    &renamed_inputs,
+                    func_call_str,
+                )
+            }
+        })
+    }
+
+
+    /// Actually executes the function
+    #[tracing::instrument(level = "trace", skip_all)]
+    fn execute_call_inner(
+        &mut self,
+        loc: Loc,
+        caller_ctx: ContextNode,
+        callee_ctx: ContextNode,
+        func_node: FunctionNode,
+        _renamed_inputs: &BTreeMap<ContextVarNode, ContextVarNode>,
+        func_call_str: Option<&str>,
+    ) -> Result<(), ExprErr> {
+        if let Some(body) = func_node.underlying(self).into_expr_err(loc)?.body.clone() {
+            // add return nodes into the subctx
+            func_node
+                .returns(self)
+                .collect::<Vec<_>>()
+                .into_iter()
+                .for_each(|ret| {
+                    if let Some(var) = ContextVar::maybe_new_from_func_ret(
+                        self,
+                        ret.underlying(self).unwrap().clone(),
+                    ) {
+                        let cvar = self.add_node(Node::ContextVar(var));
+                        callee_ctx.add_var(cvar.into(), self).unwrap();
+                        self.add_edge(cvar, callee_ctx, Edge::Context(ContextEdge::Variable));
+                    }
+                });
+
+            self.parse_ctx_statement(&body, false, Some(callee_ctx));
+            self.ctx_rets(loc, caller_ctx, callee_ctx)
+        } else {
+            let ret_ctx = Context::new_subctx(
+                callee_ctx,
+                Some(caller_ctx),
+                loc,
+                None,
+                None,
+                false,
+                self,
+                caller_ctx
+                    .underlying(self)
+                    .into_expr_err(loc)?
+                    .modifier_state
+                    .clone(),
+            )
+            .unwrap();
+            let ret_subctx = ContextNode::from(self.add_node(Node::Context(ret_ctx)));
+            self.add_edge(ret_subctx, caller_ctx, Edge::Context(ContextEdge::Continue));
+
+            let res = callee_ctx
+                .set_child_call(ret_subctx, self)
+                .into_expr_err(loc);
+            let _ = self.add_if_err(res);
+            self.apply_to_edges(callee_ctx, loc, &|analyzer, ctx, loc| {
+                func_node
+                    .returns(analyzer)
+                    .collect::<Vec<_>>()
+                    .into_iter()
+                    .try_for_each(|ret| {
+                        let underlying = ret.underlying(analyzer).unwrap();
+                        let mut var =
+                            ContextVar::new_from_func_ret(ctx, analyzer, underlying.clone())
+                                .unwrap()
+                                .expect("No type for return variable?");
+                        if let Some(func_call) = &func_call_str {
+                            var.name =
+                                format!("{}_{}", func_call, callee_ctx.new_tmp(analyzer).unwrap());
+                            var.display_name = func_call.to_string();
+                        }
+                        let node = analyzer.add_node(Node::ContextVar(var));
+                        ctx.add_var(node.into(), analyzer).into_expr_err(loc)?;
+                        analyzer.add_edge(node, ctx, Edge::Context(ContextEdge::Variable));
+                        analyzer.add_edge(node, ctx, Edge::Context(ContextEdge::Return));
+                        ctx.push_expr(ExprRet::Single(node), analyzer)
+                            .into_expr_err(loc)?;
+                        Ok(())
+                    })
+            })
+        }
+    }
+}
diff --git a/crates/solc-expressions/src/func_call/helper.rs b/crates/solc-expressions/src/func_call/helper.rs
new file mode 100644
index 00000000..fe1e0dcd
--- /dev/null
+++ b/crates/solc-expressions/src/func_call/helper.rs
@@ -0,0 +1,585 @@
+//! Helper traits & blanket implementations that help facilitate performing function calls.
+use crate::{
+    ContextBuilder, ExprErr, IntoExprErr,
+};
+
+use graph::{
+    nodes::{
+        CallFork, Context, ContextNode, ContextVar, ContextVarNode, ExprRet, FunctionNode,
+        FunctionParamNode, FunctionReturnNode, ModifierState,
+    },
+    AnalyzerBackend, ContextEdge, Edge, Node, Range, VarType,
+};
+use shared::{NodeIdx, StorageLocation};
+
+use solang_parser::pt::{Expression, Loc};
+
+use std::{
+	cell::RefCell,
+	collections::BTreeMap,
+	rc::Rc
+};
+
+impl<T> CallerHelper for T where T: AnalyzerBackend<Expr = Expression, ExprErr = ExprErr> + Sized {}
+/// Helper trait for performing function calls
+pub trait CallerHelper: AnalyzerBackend<Expr = Expression, ExprErr = ExprErr> + Sized {
+    /// Maps inputs to function parameters such that if there is a renaming i.e. `a(uint256 x)` is called via `a(y)`,
+    /// we map `y -> x` for future lookups
+    fn map_inputs_to_params(
+        &mut self,
+        loc: Loc,
+        entry_call: bool,
+        params: Vec<FunctionParamNode>,
+        inputs: Vec<ContextVarNode>,
+        callee_ctx: ContextNode,
+    ) -> Result<BTreeMap<ContextVarNode, ContextVarNode>, ExprErr> {
+        Ok(params
+            .iter()
+            .zip(inputs.iter())
+            .filter_map(|(param, input)| {
+                if !entry_call {
+                    if let Some(name) =
+                        self.add_if_err(param.maybe_name(self).into_expr_err(loc))?
+                    {
+                        let res = input
+                            .latest_version(self)
+                            .underlying(self)
+                            .into_expr_err(loc)
+                            .cloned();
+                        let mut new_cvar = self.add_if_err(res)?;
+                        new_cvar.loc = Some(param.loc(self).unwrap());
+                        new_cvar.name = name.clone();
+                        new_cvar.display_name = name;
+                        new_cvar.is_tmp = false;
+                        new_cvar.storage = if let Some(StorageLocation::Storage(_)) =
+                            param.underlying(self).unwrap().storage
+                        {
+                            new_cvar.storage
+                        } else {
+                            None
+                        };
+
+                        if let Some(param_ty) = VarType::try_from_idx(self, param.ty(self).unwrap())
+                        {
+                            let ty = new_cvar.ty.clone();
+                            if !ty.ty_eq(&param_ty, self).unwrap() {
+                                if let Some(new_ty) = ty.try_cast(&param_ty, self).unwrap() {
+                                    new_cvar.ty = new_ty;
+                                }
+                            }
+                        }
+
+                        let node = ContextVarNode::from(self.add_node(Node::ContextVar(new_cvar)));
+                        self.add_edge(
+                            node,
+                            input.latest_version(self),
+                            Edge::Context(ContextEdge::InputVariable),
+                        );
+
+                        if let (Some(r), Some(r2)) =
+                            (node.range(self).unwrap(), param.range(self).unwrap())
+                        {
+                            let new_min =
+                                r.range_min().into_owned().cast(r2.range_min().into_owned());
+                            let new_max =
+                                r.range_max().into_owned().cast(r2.range_max().into_owned());
+                            let res = node.try_set_range_min(self, new_min).into_expr_err(loc);
+                            self.add_if_err(res);
+                            let res = node.try_set_range_max(self, new_max).into_expr_err(loc);
+                            self.add_if_err(res);
+                            let res = node
+                                .try_set_range_exclusions(self, r.exclusions)
+                                .into_expr_err(loc);
+                            self.add_if_err(res);
+                        }
+                        callee_ctx.add_var(node, self).unwrap();
+                        self.add_edge(node, callee_ctx, Edge::Context(ContextEdge::Variable));
+                        Some((*input, node))
+                    } else {
+                        None
+                    }
+                } else {
+                    None
+                }
+            })
+            .collect::<BTreeMap<_, ContextVarNode>>())
+    }
+
+    #[tracing::instrument(level = "trace", skip_all)]
+    /// Parses input expressions into [`ExprRet`]s and adds them to the expr ret stack
+    fn parse_inputs(
+        &mut self,
+        ctx: ContextNode,
+        loc: Loc,
+        inputs: &[Expression],
+    ) -> Result<(), ExprErr> {
+        let append = if ctx.underlying(self).into_expr_err(loc)?.tmp_expr.is_empty() {
+            Rc::new(RefCell::new(true))
+        } else {
+            Rc::new(RefCell::new(false))
+        };
+
+        inputs.iter().try_for_each(|input| {
+            self.parse_ctx_expr(input, ctx)?;
+            self.apply_to_edges(ctx, loc, &|analyzer, ctx, loc| {
+                let Some(ret) = ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? else {
+                    return Err(ExprErr::NoLhs(
+                        loc,
+                        "Inputs did not have left hand sides".to_string(),
+                    ));
+                };
+                if matches!(ret, ExprRet::CtxKilled(_)) {
+                    ctx.push_expr(ret, analyzer).into_expr_err(loc)?;
+                    return Ok(());
+                }
+                if *append.borrow() {
+                    ctx.append_tmp_expr(ret, analyzer).into_expr_err(loc)
+                } else {
+                    *append.borrow_mut() = true;
+                    ctx.push_tmp_expr(ret, analyzer).into_expr_err(loc)
+                }
+            })
+        })?;
+        if !inputs.is_empty() {
+            self.apply_to_edges(ctx, loc, &|analyzer, ctx, loc| {
+                let Some(ret) = ctx.pop_tmp_expr(loc, analyzer).into_expr_err(loc)? else {
+                    return Err(ExprErr::NoLhs(
+                        loc,
+                        "Inputs did not have left hand sides".to_string(),
+                    ));
+                };
+                ctx.push_expr(ret, analyzer).into_expr_err(loc)
+            })
+        } else {
+            Ok(())
+        }
+    }
+
+    /// Creates a new context for a call
+    fn create_call_ctx(
+        &mut self,
+        curr_ctx: ContextNode,
+        loc: Loc,
+        func_node: FunctionNode,
+        modifier_state: Option<ModifierState>,
+    ) -> Result<ContextNode, ExprErr> {
+        let fn_ext = curr_ctx.is_fn_ext(func_node, self).into_expr_err(loc)?;
+        let ctx = Context::new_subctx(
+            curr_ctx,
+            None,
+            loc,
+            None,
+            Some(func_node),
+            fn_ext,
+            self,
+            modifier_state,
+        )
+        .into_expr_err(loc)?;
+        let callee_ctx = ContextNode::from(self.add_node(Node::Context(ctx)));
+        curr_ctx
+            .set_child_call(callee_ctx, self)
+            .into_expr_err(loc)?;
+        let ctx_fork = self.add_node(Node::FunctionCall);
+        self.add_edge(ctx_fork, curr_ctx, Edge::Context(ContextEdge::Subcontext));
+        self.add_edge(ctx_fork, func_node, Edge::Context(ContextEdge::Call));
+        self.add_edge(
+            NodeIdx::from(callee_ctx.0),
+            ctx_fork,
+            Edge::Context(ContextEdge::Subcontext),
+        );
+        Ok(callee_ctx)
+    }
+
+    /// Disambiguates a function call by their inputs (length & type)
+    fn disambiguate_fn_call(
+        &mut self,
+        fn_name: &str,
+        literals: Vec<bool>,
+        input_paths: &ExprRet,
+        funcs: &[FunctionNode],
+    ) -> Option<FunctionNode> {
+        let input_paths = input_paths.clone().flatten();
+        // try to find the function based on naive signature
+        // This doesnt do type inference on NumberLiterals (i.e. 100 could be uintX or intX, and there could
+        // be a function that takes an int256 but we evaled as uint256)
+        let fn_sig = format!("{}{}", fn_name, input_paths.try_as_func_input_str(self));
+        if let Some(func) = funcs.iter().find(|func| func.name(self).unwrap() == fn_sig) {
+            return Some(*func);
+        }
+
+        // filter by input len
+        let inputs = input_paths.as_flat_vec();
+        let funcs: Vec<&FunctionNode> = funcs
+            .iter()
+            .filter(|func| func.params(self).len() == inputs.len())
+            .collect();
+
+        if funcs.len() == 1 {
+            return Some(*funcs[0]);
+        }
+
+        if !literals.iter().any(|i| *i) {
+            None
+        } else {
+            let funcs = funcs
+                .iter()
+                .filter(|func| {
+                    let params = func.params(self);
+                    params
+                        .iter()
+                        .zip(&inputs)
+                        .enumerate()
+                        .all(|(i, (param, input))| {
+                            let param_ty = VarType::try_from_idx(self, (*param).into()).unwrap();
+                            let input_ty = ContextVarNode::from(*input).ty(self).unwrap();
+                            if param_ty.ty_eq(input_ty, self).unwrap() {
+                                true
+                            } else if literals[i] {
+                                let possibilities = ContextVarNode::from(*input)
+                                    .ty(self)
+                                    .unwrap()
+                                    .possible_builtins_from_ty_inf(self);
+                                let param_ty = param.ty(self).unwrap();
+                                match self.node(param_ty) {
+                                    Node::Builtin(b) => possibilities.contains(b),
+                                    _ => false,
+                                }
+                            } else {
+                                false
+                            }
+                        })
+                })
+                .collect::<Vec<_>>();
+            if funcs.len() == 1 {
+                Some(**funcs[0])
+            } else {
+                // this would be invalid solidity, likely the user needs to perform a cast
+                None
+            }
+        }
+    }
+
+    /// Handle returns for a function call
+    fn ctx_rets(
+        &mut self,
+        loc: Loc,
+        caller_ctx: ContextNode,
+        callee_ctx: ContextNode,
+    ) -> Result<(), ExprErr> {
+        tracing::trace!(
+            "Handling function call return for: {}, {}, depth: {:?}, {:?}",
+            caller_ctx.path(self),
+            callee_ctx.path(self),
+            caller_ctx.depth(self),
+            callee_ctx.depth(self),
+        );
+        match callee_ctx.underlying(self).into_expr_err(loc)?.child {
+            Some(CallFork::Fork(w1, w2)) => {
+                self.ctx_rets(loc, caller_ctx, w1)?;
+                self.ctx_rets(loc, caller_ctx, w2)?;
+                Ok(())
+            }
+            Some(CallFork::Call(c))
+                if c.underlying(self).into_expr_err(loc)?.depth
+                    >= caller_ctx.underlying(self).into_expr_err(loc)?.depth =>
+            {
+                // follow rabbit hole
+                self.ctx_rets(loc, caller_ctx, c)?;
+                Ok(())
+            }
+            _ => {
+                if callee_ctx.is_killed(self).into_expr_err(loc)? {
+                    return Ok(());
+                }
+                let callee_depth = callee_ctx.underlying(self).into_expr_err(loc)?.depth;
+                let caller_depth = caller_ctx.underlying(self).into_expr_err(loc)?.depth;
+                if callee_depth != caller_depth {
+                    let ctx = Context::new_subctx(
+                        callee_ctx,
+                        Some(caller_ctx),
+                        loc,
+                        None,
+                        None,
+                        false,
+                        self,
+                        caller_ctx
+                            .underlying(self)
+                            .into_expr_err(loc)?
+                            .modifier_state
+                            .clone(),
+                    )
+                    .unwrap();
+                    let ret_subctx = ContextNode::from(self.add_node(Node::Context(ctx)));
+                    self.add_edge(ret_subctx, caller_ctx, Edge::Context(ContextEdge::Continue));
+
+                    let res = callee_ctx
+                        .set_child_call(ret_subctx, self)
+                        .into_expr_err(loc);
+                    let _ = self.add_if_err(res);
+
+                    let mut rets = callee_ctx.underlying(self).unwrap().ret.clone();
+
+                    if rets.is_empty() {
+                        let func_rets: Vec<FunctionReturnNode> = callee_ctx
+                            .associated_fn(self)
+                            .into_expr_err(loc)?
+                            .returns(self)
+                            .collect();
+                        func_rets
+                            .iter()
+                            .filter_map(|ret| {
+                                let n: String = ret.maybe_name(self).ok()??;
+                                let ret_loc: Loc = ret.loc(self).ok()?;
+                                Some((n, ret_loc))
+                            })
+                            .collect::<Vec<_>>()
+                            .into_iter()
+                            .try_for_each(|(name, ret_loc)| {
+                                if let Some(cvar) = callee_ctx
+                                    .var_by_name_or_recurse(self, &name)
+                                    .into_expr_err(loc)?
+                                {
+                                    let cvar = cvar.latest_version(self);
+                                    // let ret_loc = ret.loc(self).into_expr_err(loc)?;
+                                    callee_ctx
+                                        .add_return_node(ret_loc, cvar, self)
+                                        .into_expr_err(loc)?;
+                                    self.add_edge(
+                                        cvar,
+                                        callee_ctx,
+                                        Edge::Context(ContextEdge::Return),
+                                    );
+                                }
+                                Ok(())
+                            })?;
+
+                        // add unnamed rets
+                        func_rets
+                            .into_iter()
+                            .filter(|ret| ret.maybe_name(self).unwrap().is_none())
+                            .collect::<Vec<_>>()
+                            .iter()
+                            .try_for_each(|ret| {
+                                let ret_loc = ret.loc(self).into_expr_err(loc)?;
+                                let cvar = ContextVar::new_from_func_ret(
+                                    callee_ctx,
+                                    self,
+                                    ret.underlying(self).into_expr_err(loc)?.clone(),
+                                )
+                                .into_expr_err(loc)?
+                                .unwrap();
+                                let cvar =
+                                    ContextVarNode::from(self.add_node(Node::ContextVar(cvar)));
+                                callee_ctx.add_var(cvar, self).into_expr_err(loc)?;
+                                self.add_edge(
+                                    cvar,
+                                    callee_ctx,
+                                    Edge::Context(ContextEdge::Variable),
+                                );
+                                callee_ctx
+                                    .add_return_node(ret_loc, cvar, self)
+                                    .into_expr_err(loc)?;
+                                self.add_edge(cvar, callee_ctx, Edge::Context(ContextEdge::Return));
+                                Ok(())
+                            })?;
+                        rets = callee_ctx.underlying(self).unwrap().ret.clone();
+                    }
+
+                    let handle_rets = rets.iter().all(|(_, node)| node.is_some());
+                    if handle_rets {
+                        let ret = rets
+                            .into_iter()
+                            .enumerate()
+                            .map(|(i, (_, node))| {
+                                let tmp_ret = node
+                                    .unwrap()
+                                    .as_tmp(
+                                        callee_ctx.underlying(self).unwrap().loc,
+                                        ret_subctx,
+                                        self,
+                                    )
+                                    .unwrap();
+                                tmp_ret.underlying_mut(self).into_expr_err(loc)?.is_return = true;
+                                tmp_ret
+                                    .underlying_mut(self)
+                                    .into_expr_err(loc)?
+                                    .display_name = format!(
+                                    "{}.{}",
+                                    callee_ctx.associated_fn_name(self).unwrap(),
+                                    i
+                                );
+                                ret_subctx.add_var(tmp_ret, self).into_expr_err(loc)?;
+                                self.add_edge(
+                                    tmp_ret,
+                                    ret_subctx,
+                                    Edge::Context(ContextEdge::Variable),
+                                );
+                                Ok(ExprRet::Single(tmp_ret.into()))
+                            })
+                            .collect::<Result<_, ExprErr>>()?;
+                        ret_subctx
+                            .push_expr(ExprRet::Multi(ret), self)
+                            .into_expr_err(loc)?;
+                    }
+                    Ok(())
+                } else {
+                    let mut rets = callee_ctx.underlying(self).unwrap().ret.clone();
+
+                    if rets.is_empty() {
+                        callee_ctx
+                            .associated_fn(self)
+                            .into_expr_err(loc)?
+                            .returns(self)
+                            .filter_map(|ret| {
+                                let n: String = ret.maybe_name(self).ok()??;
+                                let ret_loc: Loc = ret.loc(self).ok()?;
+                                Some((n, ret_loc))
+                            })
+                            .collect::<Vec<_>>()
+                            .into_iter()
+                            .try_for_each(|(name, ret_loc)| {
+                                if let Some(cvar) = callee_ctx
+                                    .var_by_name_or_recurse(self, &name)
+                                    .into_expr_err(loc)?
+                                {
+                                    let cvar = cvar.latest_version(self);
+                                    // let ret_loc = ret.loc(self).into_expr_err(loc)?;
+                                    callee_ctx
+                                        .add_return_node(ret_loc, cvar, self)
+                                        .into_expr_err(loc)?;
+                                    self.add_edge(
+                                        cvar,
+                                        callee_ctx,
+                                        Edge::Context(ContextEdge::Return),
+                                    );
+                                }
+                                Ok(())
+                            })?;
+                        rets = callee_ctx.underlying(self).unwrap().ret.clone();
+                    }
+                    if rets.iter().all(|(_, node)| node.is_some()) {
+                        callee_ctx
+                            .push_expr(
+                                ExprRet::Multi(
+                                    rets.iter()
+                                        .map(|(_, node)| ExprRet::Single((node.unwrap()).into()))
+                                        .collect(),
+                                ),
+                                self,
+                            )
+                            .into_expr_err(loc)
+                    } else {
+                        Ok(())
+                    }
+                }
+            }
+        }
+    }
+
+    /// Inherit the input changes from a function call
+    fn inherit_input_changes(
+        &mut self,
+        loc: Loc,
+        to_ctx: ContextNode,
+        from_ctx: ContextNode,
+        renamed_inputs: &BTreeMap<ContextVarNode, ContextVarNode>,
+    ) -> Result<(), ExprErr> {
+        if to_ctx != from_ctx {
+            self.apply_to_edges(to_ctx, loc, &|analyzer, to_ctx, loc| {
+                renamed_inputs
+                    .iter()
+                    .try_for_each(|(input_var, updated_var)| {
+                        let new_input = analyzer.advance_var_in_ctx(
+                            input_var.latest_version(analyzer),
+                            loc,
+                            to_ctx,
+                        )?;
+                        let latest_updated = updated_var.latest_version(analyzer);
+                        if let Some(updated_var_range) =
+                            latest_updated.range(analyzer).into_expr_err(loc)?
+                        {
+                            let res = new_input
+                                .set_range_min(analyzer, updated_var_range.range_min().into_owned())
+                                .into_expr_err(loc);
+                            let _ = analyzer.add_if_err(res);
+                            let res = new_input
+                                .set_range_max(analyzer, updated_var_range.range_max().into_owned())
+                                .into_expr_err(loc);
+                            let _ = analyzer.add_if_err(res);
+                            let res = new_input
+                                .set_range_exclusions(
+                                    analyzer,
+                                    updated_var_range.range_exclusions(),
+                                )
+                                .into_expr_err(loc);
+                            let _ = analyzer.add_if_err(res);
+                        }
+                        Ok(())
+                    })
+            })?;
+        }
+        Ok(())
+    }
+
+    /// Inherit the input changes from a function call
+    fn modifier_inherit_return(&mut self, mod_ctx: ContextNode, fn_ctx: ContextNode) {
+        let ret = fn_ctx.underlying(self).unwrap().ret.clone();
+        mod_ctx.underlying_mut(self).unwrap().ret = ret;
+    }
+
+    /// Inherit the storage changes from a function call
+    fn inherit_storage_changes(
+        &mut self,
+        loc: Loc,
+        inheritor_ctx: ContextNode,
+        grantor_ctx: ContextNode,
+    ) -> Result<(), ExprErr> {
+        if inheritor_ctx != grantor_ctx {
+            return self.apply_to_edges(inheritor_ctx, loc, &|analyzer, inheritor_ctx, loc| {
+                let vars = grantor_ctx.local_vars(analyzer).clone();
+                vars.iter().try_for_each(|(name, old_var)| {
+                    let var = old_var.latest_version(analyzer);
+                    let underlying = var.underlying(analyzer).into_expr_err(loc)?;
+                    if var.is_storage(analyzer).into_expr_err(loc)? {
+                        if let Some(inheritor_var) = inheritor_ctx.var_by_name(analyzer, name) {
+                            let inheritor_var = inheritor_var.latest_version(analyzer);
+                            if let Some(r) = underlying.ty.range(analyzer).into_expr_err(loc)? {
+                                let new_inheritor_var = analyzer
+                                    .advance_var_in_ctx(
+                                        inheritor_var,
+                                        underlying.loc.expect("No loc for val change"),
+                                        inheritor_ctx,
+                                    )
+                                    .unwrap();
+                                let _ = new_inheritor_var
+                                    .set_range_min(analyzer, r.range_min().into_owned());
+                                let _ = new_inheritor_var
+                                    .set_range_max(analyzer, r.range_max().into_owned());
+                                let _ = new_inheritor_var
+                                    .set_range_exclusions(analyzer, r.range_exclusions());
+                            }
+                        } else {
+                            let new_in_inheritor =
+                                analyzer.add_node(Node::ContextVar(underlying.clone()));
+                            inheritor_ctx
+                                .add_var(new_in_inheritor.into(), analyzer)
+                                .into_expr_err(loc)?;
+                            analyzer.add_edge(
+                                new_in_inheritor,
+                                inheritor_ctx,
+                                Edge::Context(ContextEdge::Variable),
+                            );
+                            analyzer.add_edge(
+                                new_in_inheritor,
+                                var,
+                                Edge::Context(ContextEdge::InheritedVariable),
+                            );
+                        }
+                    }
+                    Ok(())
+                })
+            });
+        }
+        Ok(())
+    }
+}
\ No newline at end of file
diff --git a/crates/solc-expressions/src/func_call/internal_call.rs b/crates/solc-expressions/src/func_call/internal_call.rs
index e9a417a1..d7e615f0 100644
--- a/crates/solc-expressions/src/func_call/internal_call.rs
+++ b/crates/solc-expressions/src/func_call/internal_call.rs
@@ -1,4 +1,6 @@
-use crate::{ContextBuilder, ExprErr, FuncCaller, IntoExprErr};
+//! Traits & blanket implementations that facilitate performing locally scoped function calls.
+
+use crate::{func_call::func_caller::FuncCaller, helper::CallerHelper, ContextBuilder, ExprErr, IntoExprErr};
 
 use graph::{
     nodes::{Builtin, Concrete, ContextNode, ContextVar, ContextVarNode, ExprRet},
@@ -8,13 +10,15 @@ use graph::{
 use solang_parser::pt::{Expression, Identifier, Loc, NamedArgument};
 
 impl<T> InternalFuncCaller for T where
-    T: AnalyzerBackend<Expr = Expression, ExprErr = ExprErr> + Sized + GraphBackend
+    T: AnalyzerBackend<Expr = Expression, ExprErr = ExprErr> + Sized + GraphBackend + CallerHelper
 {
 }
+/// A trait for performing internally scoped function calls (i.e. *NOT* `MyContract.func(...)`)
 pub trait InternalFuncCaller:
-    AnalyzerBackend<Expr = Expression, ExprErr = ExprErr> + Sized + GraphBackend
+    AnalyzerBackend<Expr = Expression, ExprErr = ExprErr> + Sized + GraphBackend + CallerHelper
 {
     #[tracing::instrument(level = "trace", skip_all)]
+    /// Perform a named function call
     fn call_internal_named_func(
         &mut self,
         ctx: ContextNode,
@@ -186,7 +190,7 @@ pub trait InternalFuncCaller:
         tracing::trace!("function call: {}(..)", ident.name);
         // It is a function call, check if we have the ident in scope
         let funcs = ctx.visible_funcs(self).into_expr_err(*loc)?;
-        // println!("visible funcs: {:#?}", funcs.iter().map(|f| f.name(self).unwrap()).collect::<Vec<_>>());
+
         // filter down all funcs to those that match
         let possible_funcs = funcs
             .iter()
@@ -198,89 +202,88 @@ pub trait InternalFuncCaller:
             .copied()
             .collect::<Vec<_>>();
 
-        if possible_funcs.is_empty() {
-            // this is a builtin, cast, or unknown function?
-            self.parse_ctx_expr(func_expr, ctx)?;
-            self.apply_to_edges(ctx, *loc, &|analyzer, ctx, loc| {
-                let ret = ctx
-                    .pop_expr_latest(loc, analyzer)
-                    .into_expr_err(loc)?
-                    .unwrap_or_else(|| ExprRet::Multi(vec![]));
-                let ret = ret.flatten();
-                if matches!(ret, ExprRet::CtxKilled(_)) {
-                    ctx.push_expr(ret, analyzer).into_expr_err(loc)?;
-                    return Ok(());
-                }
-                analyzer.match_intrinsic_fallback(ctx, &loc, input_exprs, ret)
-            })
-        } else if possible_funcs.len() == 1 {
-            self.parse_inputs(ctx, *loc, input_exprs)?;
-            self.apply_to_edges(ctx, *loc, &|analyzer, ctx, loc| {
-                let inputs = ctx
-                    .pop_expr_latest(loc, analyzer)
-                    .into_expr_err(loc)?
-                    .unwrap_or_else(|| ExprRet::Multi(vec![]));
-                let inputs = inputs.flatten();
-                if matches!(inputs, ExprRet::CtxKilled(_)) {
-                    ctx.push_expr(inputs, analyzer).into_expr_err(loc)?;
-                    return Ok(());
-                }
-                analyzer.setup_fn_call(&ident.loc, &inputs, (possible_funcs[0]).into(), ctx, None)
-            })
-        } else {
-            // this is the annoying case due to function overloading & type inference on number literals
-            self.parse_inputs(ctx, *loc, input_exprs)?;
-            self.apply_to_edges(ctx, *loc, &|analyzer, ctx, loc| {
-                let inputs = ctx
-                    .pop_expr_latest(loc, analyzer)
-                    .into_expr_err(loc)?
-                    .unwrap_or_else(|| ExprRet::Multi(vec![]));
-                let inputs = inputs.flatten();
-                if matches!(inputs, ExprRet::CtxKilled(_)) {
-                    ctx.push_expr(inputs, analyzer).into_expr_err(loc)?;
-                    return Ok(());
-                }
-                let resizeables: Vec<_> = inputs.as_flat_vec()
-                    .iter()
-                    .map(|idx| {
-                        match VarType::try_from_idx(analyzer, *idx) {
-                            Some(VarType::BuiltIn(bn, _)) => {
-                                matches!(analyzer.node(bn), Node::Builtin(Builtin::Uint(_)) | Node::Builtin(Builtin::Int(_)) | Node::Builtin(Builtin::Bytes(_)))
-                                // match analyzer.node(bn) {
-                                //     Node::Builtin(Builtin::Uint(s)) if s < &256 => true,
-                                //     Node::Builtin(Builtin::Int(s)) if s < &256 => true,
-                                //     Node::Builtin(Builtin::Bytes(s)) if s < &32 => true,
-                                //     _ => false
-                                // }
-                            }
-                            Some(VarType::Concrete(c)) => {
-                                matches!(analyzer.node(c), Node::Concrete(Concrete::Uint(_, _)) | Node::Concrete(Concrete::Int(_, _)) | Node::Concrete(Concrete::Bytes(_, _)))
+        match possible_funcs.len() {
+            0 => {
+                // this is a builtin, cast, or unknown function
+                self.parse_ctx_expr(func_expr, ctx)?;
+                self.apply_to_edges(ctx, *loc, &|analyzer, ctx, loc| {
+                    let ret = ctx
+                        .pop_expr_latest(loc, analyzer)
+                        .into_expr_err(loc)?
+                        .unwrap_or_else(|| ExprRet::Multi(vec![]));
+                    let ret = ret.flatten();
+                    if matches!(ret, ExprRet::CtxKilled(_)) {
+                        ctx.push_expr(ret, analyzer).into_expr_err(loc)?;
+                        return Ok(());
+                    }
+                    analyzer.match_intrinsic_fallback(ctx, &loc, input_exprs, ret)
+                })
+            }
+            1 => {
+                // there is only a single possible function
+                self.parse_inputs(ctx, *loc, input_exprs)?;
+                self.apply_to_edges(ctx, *loc, &|analyzer, ctx, loc| {
+                    let inputs = ctx
+                        .pop_expr_latest(loc, analyzer)
+                        .into_expr_err(loc)?
+                        .unwrap_or_else(|| ExprRet::Multi(vec![]));
+                    let inputs = inputs.flatten();
+                    if matches!(inputs, ExprRet::CtxKilled(_)) {
+                        ctx.push_expr(inputs, analyzer).into_expr_err(loc)?;
+                        return Ok(());
+                    }
+                    analyzer.setup_fn_call(&ident.loc, &inputs, (possible_funcs[0]).into(), ctx, None)
+                })
+            }
+            _ => {
+                // this is the annoying case due to function overloading & type inference on number literals
+                self.parse_inputs(ctx, *loc, input_exprs)?;
+                self.apply_to_edges(ctx, *loc, &|analyzer, ctx, loc| {
+                    let inputs = ctx
+                        .pop_expr_latest(loc, analyzer)
+                        .into_expr_err(loc)?
+                        .unwrap_or_else(|| ExprRet::Multi(vec![]));
+                    let inputs = inputs.flatten();
+                    if matches!(inputs, ExprRet::CtxKilled(_)) {
+                        ctx.push_expr(inputs, analyzer).into_expr_err(loc)?;
+                        return Ok(());
+                    }
+                    let resizeables: Vec<_> = inputs.as_flat_vec()
+                        .iter()
+                        .map(|idx| {
+                            match VarType::try_from_idx(analyzer, *idx) {
+                                Some(VarType::BuiltIn(bn, _)) => {
+                                    matches!(analyzer.node(bn), Node::Builtin(Builtin::Uint(_)) | Node::Builtin(Builtin::Int(_)) | Node::Builtin(Builtin::Bytes(_)))
+                                }
+                                Some(VarType::Concrete(c)) => {
+                                    matches!(analyzer.node(c), Node::Concrete(Concrete::Uint(_, _)) | Node::Concrete(Concrete::Int(_, _)) | Node::Concrete(Concrete::Bytes(_, _)))
+                                }
+                                _ => false
                             }
-                            _ => false
-                        }
-                    })
-                    .collect();
-                if let Some(func) = analyzer.disambiguate_fn_call(
-                    &ident.name,
-                    resizeables,
-                    &inputs,
-                    &possible_funcs,
-                ) {
-                    analyzer.setup_fn_call(&loc, &inputs, func.into(), ctx, None)
-                } else {
-                    Err(ExprErr::FunctionNotFound(
-                        loc,
-                        format!(
-                            "Could not disambiguate function, default input types: {}, possible functions: {:#?}",
-                            inputs.try_as_func_input_str(analyzer),
-                            possible_funcs
-                                .iter()
-                                .map(|i| i.name(analyzer).unwrap())
-                                .collect::<Vec<_>>()
-                        ),
-                    ))
-                }
-            })
+                        })
+                        .collect();
+                    if let Some(func) = analyzer.disambiguate_fn_call(
+                        &ident.name,
+                        resizeables,
+                        &inputs,
+                        &possible_funcs,
+                    ) {
+                        analyzer.setup_fn_call(&loc, &inputs, func.into(), ctx, None)
+                    } else {
+                        Err(ExprErr::FunctionNotFound(
+                            loc,
+                            format!(
+                                "Could not disambiguate function, default input types: {}, possible functions: {:#?}",
+                                inputs.try_as_func_input_str(analyzer),
+                                possible_funcs
+                                    .iter()
+                                    .map(|i| i.name(analyzer).unwrap())
+                                    .collect::<Vec<_>>()
+                            ),
+                        ))
+                    }
+                })
+            }
         }
     }
 }
diff --git a/crates/solc-expressions/src/func_call/intrinsic_call/abi.rs b/crates/solc-expressions/src/func_call/intrinsic_call/abi.rs
index 3307fb04..d2507e2e 100644
--- a/crates/solc-expressions/src/func_call/intrinsic_call/abi.rs
+++ b/crates/solc-expressions/src/func_call/intrinsic_call/abi.rs
@@ -8,7 +8,10 @@ use graph::{
 use solang_parser::pt::{Expression, Loc};
 
 impl<T> AbiCaller for T where T: AnalyzerBackend<Expr = Expression, ExprErr = ExprErr> + Sized {}
+
+/// Trait for calling abi-namespaced intrinsic functions
 pub trait AbiCaller: AnalyzerBackend<Expr = Expression, ExprErr = ExprErr> + Sized {
+    /// Perform an `abi.<..>` function call
     fn abi_call(
         &mut self,
         func_name: String,
@@ -91,7 +94,7 @@ pub trait AbiCaller: AnalyzerBackend<Expr = Expression, ExprErr = ExprErr> + Siz
             | "abi.encodeCall"
             | "abi.encodeWithSignature"
             | "abi.encodeWithSelector" => {
-                // currently we dont support concrete abi encoding, TODO
+                // TODO: Support concrete abi encoding
                 let bn = self.builtin_or_add(Builtin::DynamicBytes);
                 let cvar = ContextVar::new_from_builtin(loc, bn.into(), self).into_expr_err(loc)?;
                 let node = self.add_node(Node::ContextVar(cvar));
diff --git a/crates/solc-expressions/src/func_call/intrinsic_call/address.rs b/crates/solc-expressions/src/func_call/intrinsic_call/address.rs
index 67fbf8d1..8303d01d 100644
--- a/crates/solc-expressions/src/func_call/intrinsic_call/address.rs
+++ b/crates/solc-expressions/src/func_call/intrinsic_call/address.rs
@@ -8,7 +8,10 @@ use graph::{
 use solang_parser::pt::{Expression, Loc};
 
 impl<T> AddressCaller for T where T: AnalyzerBackend<Expr = Expression, ExprErr = ExprErr> + Sized {}
+
+/// Trait for calling address-based intrinsic functions
 pub trait AddressCaller: AnalyzerBackend<Expr = Expression, ExprErr = ExprErr> + Sized {
+    /// Perform an `address.<..>` function call
     fn address_call(
         &mut self,
         func_name: String,
diff --git a/crates/solc-expressions/src/func_call/intrinsic_call/array.rs b/crates/solc-expressions/src/func_call/intrinsic_call/array.rs
index 58a93628..8a898252 100644
--- a/crates/solc-expressions/src/func_call/intrinsic_call/array.rs
+++ b/crates/solc-expressions/src/func_call/intrinsic_call/array.rs
@@ -10,7 +10,10 @@ use ethers_core::types::U256;
 use solang_parser::pt::{Expression, Loc};
 
 impl<T> ArrayCaller for T where T: AnalyzerBackend<Expr = Expression, ExprErr = ExprErr> + Sized {}
+
+/// Trait for calling array-based intrinsic functions
 pub trait ArrayCaller: AnalyzerBackend<Expr = Expression, ExprErr = ExprErr> + Sized {
+    /// Perform an `array.<..>` function call
     fn array_call(
         &mut self,
         func_name: String,
diff --git a/crates/solc-expressions/src/func_call/intrinsic_call/block.rs b/crates/solc-expressions/src/func_call/intrinsic_call/block.rs
index b29f08c1..67e9ea18 100644
--- a/crates/solc-expressions/src/func_call/intrinsic_call/block.rs
+++ b/crates/solc-expressions/src/func_call/intrinsic_call/block.rs
@@ -8,7 +8,10 @@ use graph::{
 use solang_parser::pt::{Expression, Loc};
 
 impl<T> BlockCaller for T where T: AnalyzerBackend<Expr = Expression, ExprErr = ExprErr> + Sized {}
+
+/// Trait for calling block-based intrinsic functions
 pub trait BlockCaller: AnalyzerBackend<Expr = Expression, ExprErr = ExprErr> + Sized {
+    /// Perform a `block` function call
     fn block_call(
         &mut self,
         func_name: String,
diff --git a/crates/solc-expressions/src/func_call/intrinsic_call/constructors.rs b/crates/solc-expressions/src/func_call/intrinsic_call/constructors.rs
index 86bed531..71735a84 100644
--- a/crates/solc-expressions/src/func_call/intrinsic_call/constructors.rs
+++ b/crates/solc-expressions/src/func_call/intrinsic_call/constructors.rs
@@ -1,4 +1,4 @@
-use crate::{ContextBuilder, ExprErr, FuncCaller, IntoExprErr};
+use crate::{ContextBuilder, ExprErr, IntoExprErr, func_call::helper::CallerHelper};
 
 use graph::{
     elem::*,
@@ -10,10 +10,13 @@ use shared::NodeIdx;
 use solang_parser::pt::{Expression, Loc};
 
 impl<T> ConstructorCaller for T where
-    T: AnalyzerBackend<Expr = Expression, ExprErr = ExprErr> + Sized
+    T: AnalyzerBackend<Expr = Expression, ExprErr = ExprErr> + Sized + CallerHelper
 {
 }
-pub trait ConstructorCaller: AnalyzerBackend<Expr = Expression, ExprErr = ExprErr> + Sized {
+
+/// Trait for constructing compound types like contracts, structs and arrays
+pub trait ConstructorCaller: AnalyzerBackend<Expr = Expression, ExprErr = ExprErr> + Sized + CallerHelper {
+    /// Construct an array
     fn construct_array(
         &mut self,
         func_idx: NodeIdx,
@@ -97,6 +100,7 @@ pub trait ConstructorCaller: AnalyzerBackend<Expr = Expression, ExprErr = ExprEr
         })
     }
 
+    /// Construct a contract
     fn construct_contract(
         &mut self,
         func_idx: NodeIdx,
@@ -144,6 +148,7 @@ pub trait ConstructorCaller: AnalyzerBackend<Expr = Expression, ExprErr = ExprEr
         })
     }
 
+    /// Construct a struct
     fn construct_struct(
         &mut self,
         func_idx: NodeIdx,
diff --git a/crates/solc-expressions/src/func_call/intrinsic_call/dyn_builtin.rs b/crates/solc-expressions/src/func_call/intrinsic_call/dyn_builtin.rs
index b0143eb2..1afc8477 100644
--- a/crates/solc-expressions/src/func_call/intrinsic_call/dyn_builtin.rs
+++ b/crates/solc-expressions/src/func_call/intrinsic_call/dyn_builtin.rs
@@ -9,7 +9,10 @@ use solang_parser::pt::{Expression, Loc};
 
 impl<T> DynBuiltinCaller for T where T: AnalyzerBackend<Expr = Expression, ExprErr = ExprErr> + Sized
 {}
+
+/// Trait for calling dynamic builtin-based intrinsic functions, like `concat`
 pub trait DynBuiltinCaller: AnalyzerBackend<Expr = Expression, ExprErr = ExprErr> + Sized {
+    /// Perform a dynamic builtin type's builtin function call
     fn dyn_builtin_call(
         &mut self,
         func_name: String,
@@ -30,6 +33,7 @@ pub trait DynBuiltinCaller: AnalyzerBackend<Expr = Expression, ExprErr = ExprErr
     }
 
     #[tracing::instrument(level = "trace", skip_all)]
+    /// Concatenate two dynamic builtins
     fn concat(
         &mut self,
         loc: &Loc,
@@ -71,6 +75,7 @@ pub trait DynBuiltinCaller: AnalyzerBackend<Expr = Expression, ExprErr = ExprErr
         })
     }
 
+    /// Match on the expression returns
     fn match_concat(
         &mut self,
         ctx: ContextNode,
@@ -123,6 +128,7 @@ pub trait DynBuiltinCaller: AnalyzerBackend<Expr = Expression, ExprErr = ExprErr
         }
     }
 
+    /// Perform the concatenation
     fn concat_inner(
         &mut self,
         loc: Loc,
diff --git a/crates/solc-expressions/src/func_call/intrinsic_call/intrinsic_caller.rs b/crates/solc-expressions/src/func_call/intrinsic_call/intrinsic_caller.rs
index 2b57ba7c..21b83e57 100644
--- a/crates/solc-expressions/src/func_call/intrinsic_call/intrinsic_caller.rs
+++ b/crates/solc-expressions/src/func_call/intrinsic_call/intrinsic_caller.rs
@@ -3,7 +3,7 @@ use crate::{
         AbiCaller, AddressCaller, ArrayCaller, BlockCaller, ConstructorCaller, DynBuiltinCaller,
         MsgCaller, PrecompileCaller, SolidityCaller, TypesCaller,
     },
-    ContextBuilder, ExprErr, FuncCaller, IntoExprErr,
+    ContextBuilder, ExprErr, IntoExprErr, func_call::helper::CallerHelper
 };
 
 use graph::{
@@ -14,6 +14,7 @@ use shared::NodeIdx;
 
 use solang_parser::pt::{Expression, Loc};
 
+/// Supertrait of individual types of calls like abi, address, etc.
 pub trait CallerParts:
     AbiCaller
     + AddressCaller
@@ -25,6 +26,7 @@ pub trait CallerParts:
     + TypesCaller
     + ConstructorCaller
     + MsgCaller
+    + CallerHelper
 {
 }
 
@@ -39,6 +41,7 @@ impl<T> CallerParts for T where
         + TypesCaller
         + ConstructorCaller
         + MsgCaller
+        + CallerHelper
 {
 }
 
@@ -46,6 +49,8 @@ impl<T> IntrinsicFuncCaller for T where
     T: AnalyzerBackend<Expr = Expression, ExprErr = ExprErr> + Sized + CallerParts
 {
 }
+
+/// Perform calls to intrinsic functions like `abi.encode`, `array.push`, `require`, and constructors etc.
 pub trait IntrinsicFuncCaller:
     AnalyzerBackend<Expr = Expression, ExprErr = ExprErr> + Sized + CallerParts
 {
diff --git a/crates/solc-expressions/src/func_call/intrinsic_call/mod.rs b/crates/solc-expressions/src/func_call/intrinsic_call/mod.rs
index 0345a08a..9a33e6da 100644
--- a/crates/solc-expressions/src/func_call/intrinsic_call/mod.rs
+++ b/crates/solc-expressions/src/func_call/intrinsic_call/mod.rs
@@ -1,3 +1,4 @@
+//! Traits & blanket implementations that facilitate performing intrinsic function calls.
 mod abi;
 mod address;
 mod array;
diff --git a/crates/solc-expressions/src/func_call/intrinsic_call/msg.rs b/crates/solc-expressions/src/func_call/intrinsic_call/msg.rs
index cf3f371f..d2b3f1d5 100644
--- a/crates/solc-expressions/src/func_call/intrinsic_call/msg.rs
+++ b/crates/solc-expressions/src/func_call/intrinsic_call/msg.rs
@@ -8,7 +8,10 @@ use graph::{
 use solang_parser::pt::{Expression, Loc};
 
 impl<T> MsgCaller for T where T: AnalyzerBackend<Expr = Expression, ExprErr = ExprErr> + Sized {}
+
+/// Trait for calling msg-based intrinsic functions, like `gasleft`
 pub trait MsgCaller: AnalyzerBackend<Expr = Expression, ExprErr = ExprErr> + Sized {
+    /// Perform a msg's builtin function call, like `gasleft()`
     fn msg_call(
         &mut self,
         func_name: String,
diff --git a/crates/solc-expressions/src/func_call/intrinsic_call/precompile.rs b/crates/solc-expressions/src/func_call/intrinsic_call/precompile.rs
index 64c7f09f..bfe38af9 100644
--- a/crates/solc-expressions/src/func_call/intrinsic_call/precompile.rs
+++ b/crates/solc-expressions/src/func_call/intrinsic_call/precompile.rs
@@ -1,4 +1,4 @@
-use crate::{ContextBuilder, ExprErr, FuncCaller, IntoExprErr};
+use crate::{ContextBuilder, ExprErr, IntoExprErr, func_call::helper::CallerHelper};
 
 use graph::{
     nodes::{Builtin, Context, ContextNode, ContextVar, ContextVarNode, ExprRet},
@@ -8,9 +8,12 @@ use shared::NodeIdx;
 
 use solang_parser::pt::{Expression, Loc};
 
-impl<T> PrecompileCaller for T where T: AnalyzerBackend<Expr = Expression, ExprErr = ExprErr> + Sized
+impl<T> PrecompileCaller for T where T: AnalyzerBackend<Expr = Expression, ExprErr = ExprErr> + Sized + CallerHelper
 {}
-pub trait PrecompileCaller: AnalyzerBackend<Expr = Expression, ExprErr = ExprErr> + Sized {
+
+/// Trait for calling precompile intrinsic functions, like `ecrecover`
+pub trait PrecompileCaller: AnalyzerBackend<Expr = Expression, ExprErr = ExprErr> + Sized + CallerHelper {
+    /// Perform a precompile's function call, like `ecrecover`
     fn precompile_call(
         &mut self,
         func_name: String,
diff --git a/crates/solc-expressions/src/func_call/intrinsic_call/solidity.rs b/crates/solc-expressions/src/func_call/intrinsic_call/solidity.rs
index e8fd3f11..acbf38ce 100644
--- a/crates/solc-expressions/src/func_call/intrinsic_call/solidity.rs
+++ b/crates/solc-expressions/src/func_call/intrinsic_call/solidity.rs
@@ -1,4 +1,4 @@
-use crate::{require::Require, ContextBuilder, ExprErr, FuncCaller, IntoExprErr};
+use crate::{require::Require, ContextBuilder, ExprErr, func_call::helper::CallerHelper, IntoExprErr};
 
 use graph::{
     nodes::{Builtin, ContextNode, ContextVar, ExprRet},
@@ -7,8 +7,11 @@ use graph::{
 
 use solang_parser::pt::{Expression, Loc};
 
-impl<T> SolidityCaller for T where T: AnalyzerBackend<Expr = Expression, ExprErr = ExprErr> + Sized {}
-pub trait SolidityCaller: AnalyzerBackend<Expr = Expression, ExprErr = ExprErr> + Sized {
+impl<T> SolidityCaller for T where T: AnalyzerBackend<Expr = Expression, ExprErr = ExprErr> + Sized + CallerHelper {}
+
+/// Trait for calling solidity's intrinsic functions, like `keccak256`
+pub trait SolidityCaller: AnalyzerBackend<Expr = Expression, ExprErr = ExprErr> + Sized + CallerHelper {
+    /// Perform a solidity intrinsic function call, like `keccak256`
     fn solidity_call(
         &mut self,
         func_name: String,
diff --git a/crates/solc-expressions/src/func_call/intrinsic_call/types.rs b/crates/solc-expressions/src/func_call/intrinsic_call/types.rs
index ee5b421c..82875d94 100644
--- a/crates/solc-expressions/src/func_call/intrinsic_call/types.rs
+++ b/crates/solc-expressions/src/func_call/intrinsic_call/types.rs
@@ -1,4 +1,4 @@
-use crate::{ContextBuilder, ExprErr, FuncCaller, IntoExprErr};
+use crate::{ContextBuilder, ExprErr, IntoExprErr, func_call::helper::CallerHelper};
 
 use graph::{
     elem::*,
@@ -10,7 +10,10 @@ use shared::NodeIdx;
 use solang_parser::pt::{Expression, Loc};
 
 impl<T> TypesCaller for T where T: AnalyzerBackend<Expr = Expression, ExprErr = ExprErr> + Sized {}
+
+/// Trait for calling type-based intrinsic functions, like `wrap`
 pub trait TypesCaller: AnalyzerBackend<Expr = Expression, ExprErr = ExprErr> + Sized {
+    /// Perform a type-based intrinsic function call, like `wrap`
     fn types_call(
         &mut self,
         func_name: String,
@@ -116,6 +119,7 @@ pub trait TypesCaller: AnalyzerBackend<Expr = Expression, ExprErr = ExprErr> + S
         }
     }
 
+    /// Perform a cast of a type
     fn cast(
         &mut self,
         ty: Builtin,
diff --git a/crates/solc-expressions/src/func_call/mod.rs b/crates/solc-expressions/src/func_call/mod.rs
index 33c82738..a4ea9278 100644
--- a/crates/solc-expressions/src/func_call/mod.rs
+++ b/crates/solc-expressions/src/func_call/mod.rs
@@ -1,1197 +1,6 @@
-use crate::{
-    internal_call::InternalFuncCaller, intrinsic_call::IntrinsicFuncCaller,
-    namespaced_call::NameSpaceFuncCaller, ContextBuilder, ExprErr, IntoExprErr,
-};
-
-use graph::{
-    nodes::{
-        CallFork, Context, ContextNode, ContextVar, ContextVarNode, ExprRet, FunctionNode,
-        FunctionParamNode, FunctionReturnNode, ModifierState,
-    },
-    AnalyzerBackend, ContextEdge, Edge, GraphBackend, Node, Range, VarType,
-};
-use shared::{NodeIdx, StorageLocation};
-
-use solang_parser::helpers::CodeLocation;
-use solang_parser::pt::{Expression, Loc, NamedArgument};
-use std::cell::RefCell;
-use std::collections::BTreeMap;
-use std::rc::Rc;
-
+pub mod helper;
 pub mod internal_call;
 pub mod intrinsic_call;
 pub mod modifier;
 pub mod namespaced_call;
-
-impl<T> FuncCaller for T where
-    T: AnalyzerBackend<Expr = Expression, ExprErr = ExprErr> + Sized + GraphBackend
-{
-}
-pub trait FuncCaller:
-    GraphBackend + AnalyzerBackend<Expr = Expression, ExprErr = ExprErr> + Sized
-{
-    #[tracing::instrument(level = "trace", skip_all)]
-    fn named_fn_call_expr(
-        &mut self,
-        ctx: ContextNode,
-        loc: &Loc,
-        func_expr: &Expression,
-        input_exprs: &[NamedArgument],
-    ) -> Result<(), ExprErr> {
-        use solang_parser::pt::Expression::*;
-        match func_expr {
-            MemberAccess(loc, member_expr, ident) => {
-                self.call_name_spaced_named_func(ctx, loc, member_expr, ident, input_exprs)
-            }
-            Variable(ident) => self.call_internal_named_func(ctx, loc, ident, input_exprs),
-            e => Err(ExprErr::IntrinsicNamedArgs(
-                *loc,
-                format!("Cannot call intrinsic functions with named arguments. Call: {e:?}"),
-            )),
-        }
-    }
-    #[tracing::instrument(level = "trace", skip_all)]
-    fn fn_call_expr(
-        &mut self,
-        ctx: ContextNode,
-        loc: &Loc,
-        func_expr: &Expression,
-        input_exprs: &[Expression],
-    ) -> Result<(), ExprErr> {
-        use solang_parser::pt::Expression::*;
-        match func_expr {
-            MemberAccess(loc, member_expr, ident) => {
-                self.call_name_spaced_func(ctx, loc, member_expr, ident, input_exprs)
-            }
-            Variable(ident) => self.call_internal_func(ctx, loc, ident, func_expr, input_exprs),
-            _ => {
-                self.parse_ctx_expr(func_expr, ctx)?;
-                self.apply_to_edges(ctx, *loc, &|analyzer, ctx, loc| {
-                    let Some(ret) = ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? else {
-                        return Err(ExprErr::NoLhs(
-                            loc,
-                            "Function call to nonexistent function".to_string(),
-                        ));
-                    };
-                    if matches!(ret, ExprRet::CtxKilled(_)) {
-                        ctx.push_expr(ret, analyzer).into_expr_err(loc)?;
-                        return Ok(());
-                    }
-                    analyzer.match_intrinsic_fallback(ctx, &loc, input_exprs, ret)
-                })
-            }
-        }
-    }
-
-    fn match_intrinsic_fallback(
-        &mut self,
-        ctx: ContextNode,
-        loc: &Loc,
-        input_exprs: &[Expression],
-        ret: ExprRet,
-    ) -> Result<(), ExprErr> {
-        match ret {
-            ExprRet::Single(func_idx) | ExprRet::SingleLiteral(func_idx) => {
-                self.intrinsic_func_call(loc, input_exprs, func_idx, ctx)
-            }
-            ExprRet::Multi(inner) => inner
-                .into_iter()
-                .try_for_each(|ret| self.match_intrinsic_fallback(ctx, loc, input_exprs, ret)),
-            ExprRet::CtxKilled(kind) => ctx.kill(self, *loc, kind).into_expr_err(*loc),
-            ExprRet::Null => Ok(()),
-        }
-    }
-
-    /// Disambiguates a function call by their inputs (length & type)
-    fn disambiguate_fn_call(
-        &mut self,
-        fn_name: &str,
-        literals: Vec<bool>,
-        input_paths: &ExprRet,
-        funcs: &[FunctionNode],
-    ) -> Option<FunctionNode> {
-        let input_paths = input_paths.clone().flatten();
-        // try to find the function based on naive signature
-        // This doesnt do type inference on NumberLiterals (i.e. 100 could be uintX or intX, and there could
-        // be a function that takes an int256 but we evaled as uint256)
-        let fn_sig = format!("{}{}", fn_name, input_paths.try_as_func_input_str(self));
-        if let Some(func) = funcs.iter().find(|func| func.name(self).unwrap() == fn_sig) {
-            return Some(*func);
-        }
-
-        // filter by input len
-        let inputs = input_paths.as_flat_vec();
-        let funcs: Vec<&FunctionNode> = funcs
-            .iter()
-            .filter(|func| func.params(self).len() == inputs.len())
-            .collect();
-
-        if funcs.len() == 1 {
-            return Some(*funcs[0]);
-        }
-
-        if !literals.iter().any(|i| *i) {
-            None
-        } else {
-            let funcs = funcs
-                .iter()
-                .filter(|func| {
-                    let params = func.params(self);
-                    params
-                        .iter()
-                        .zip(&inputs)
-                        .enumerate()
-                        .all(|(i, (param, input))| {
-                            let param_ty = VarType::try_from_idx(self, (*param).into()).unwrap();
-                            let input_ty = ContextVarNode::from(*input).ty(self).unwrap();
-                            if param_ty.ty_eq(input_ty, self).unwrap() {
-                                true
-                            } else if literals[i] {
-                                let possibilities = ContextVarNode::from(*input)
-                                    .ty(self)
-                                    .unwrap()
-                                    .possible_builtins_from_ty_inf(self);
-                                let param_ty = param.ty(self).unwrap();
-                                match self.node(param_ty) {
-                                    Node::Builtin(b) => possibilities.contains(b),
-                                    _ => false,
-                                }
-                            } else {
-                                false
-                            }
-                        })
-                })
-                .collect::<Vec<_>>();
-            if funcs.len() == 1 {
-                Some(**funcs[0])
-            } else {
-                // this would be invalid solidity, likely the user needs to perform a cast
-                None
-            }
-        }
-    }
-
-    #[tracing::instrument(level = "trace", skip_all)]
-    fn parse_inputs(
-        &mut self,
-        ctx: ContextNode,
-        loc: Loc,
-        inputs: &[Expression],
-    ) -> Result<(), ExprErr> {
-        let append = if ctx.underlying(self).into_expr_err(loc)?.tmp_expr.is_empty() {
-            Rc::new(RefCell::new(true))
-        } else {
-            Rc::new(RefCell::new(false))
-        };
-
-        inputs.iter().try_for_each(|input| {
-            self.parse_ctx_expr(input, ctx)?;
-            self.apply_to_edges(ctx, loc, &|analyzer, ctx, loc| {
-                let Some(ret) = ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? else {
-                    return Err(ExprErr::NoLhs(
-                        loc,
-                        "Inputs did not have left hand sides".to_string(),
-                    ));
-                };
-                if matches!(ret, ExprRet::CtxKilled(_)) {
-                    ctx.push_expr(ret, analyzer).into_expr_err(loc)?;
-                    return Ok(());
-                }
-                if *append.borrow() {
-                    ctx.append_tmp_expr(ret, analyzer).into_expr_err(loc)
-                } else {
-                    *append.borrow_mut() = true;
-                    ctx.push_tmp_expr(ret, analyzer).into_expr_err(loc)
-                }
-            })
-        })?;
-        if !inputs.is_empty() {
-            self.apply_to_edges(ctx, loc, &|analyzer, ctx, loc| {
-                let Some(ret) = ctx.pop_tmp_expr(loc, analyzer).into_expr_err(loc)? else {
-                    return Err(ExprErr::NoLhs(
-                        loc,
-                        "Inputs did not have left hand sides".to_string(),
-                    ));
-                };
-                ctx.push_expr(ret, analyzer).into_expr_err(loc)
-            })
-        } else {
-            Ok(())
-        }
-    }
-
-    /// Setups up storage variables for a function call and calls it
-    fn setup_fn_call(
-        &mut self,
-        loc: &Loc,
-        inputs: &ExprRet,
-        func_idx: NodeIdx,
-        ctx: ContextNode,
-        func_call_str: Option<&str>,
-    ) -> Result<(), ExprErr> {
-        // if we have a single match thats our function
-        let var = match ContextVar::maybe_from_user_ty(self, *loc, func_idx) {
-            Some(v) => v,
-            None => panic!(
-                "Could not create context variable from user type: {:?}",
-                self.node(func_idx)
-            ),
-        };
-
-        let new_cvarnode = self.add_node(Node::ContextVar(var));
-        ctx.add_var(new_cvarnode.into(), self).into_expr_err(*loc)?;
-        self.add_edge(new_cvarnode, ctx, Edge::Context(ContextEdge::Variable));
-        if let Some(func_node) = ContextVarNode::from(new_cvarnode)
-            .ty(self)
-            .into_expr_err(*loc)?
-            .func_node(self)
-        {
-            self.func_call(ctx, *loc, inputs, func_node, func_call_str, None)
-        } else {
-            unreachable!()
-        }
-    }
-
-    /// Matches the input kinds and performs the call
-    fn func_call(
-        &mut self,
-        ctx: ContextNode,
-        loc: Loc,
-        input_paths: &ExprRet,
-        func: FunctionNode,
-        func_call_str: Option<&str>,
-        modifier_state: Option<ModifierState>,
-    ) -> Result<(), ExprErr> {
-        let params = func.params(self);
-        let input_paths = input_paths.clone().flatten();
-        if input_paths.has_killed() {
-            return ctx
-                .kill(self, loc, input_paths.killed_kind().unwrap())
-                .into_expr_err(loc);
-        }
-        match input_paths {
-            ExprRet::Single(input_var) | ExprRet::SingleLiteral(input_var) => {
-                // if we get a single var, we expect the func to only take a single
-                // variable
-                self.func_call_inner(
-                    false,
-                    ctx,
-                    func,
-                    loc,
-                    vec![ContextVarNode::from(input_var).latest_version(self)],
-                    params,
-                    func_call_str,
-                    modifier_state,
-                )
-            }
-            ExprRet::Multi(ref inputs) => {
-                if ExprRet::Multi(inputs.to_vec()).flatten().has_killed() {
-                    return ctx
-                        .kill(
-                            self,
-                            loc,
-                            ExprRet::Multi(inputs.to_vec()).killed_kind().unwrap(),
-                        )
-                        .into_expr_err(loc);
-                }
-                // check if the inputs length matchs func params length
-                // if they do, check that none are forks
-                if inputs.len() == params.len() {
-                    let input_vars = inputs
-                        .iter()
-                        .map(|expr_ret| {
-                            let var = expr_ret.expect_single().into_expr_err(loc)?;
-                            Ok(ContextVarNode::from(var).latest_version(self))
-                        })
-                        .collect::<Result<Vec<_>, ExprErr>>()?;
-                    self.func_call_inner(
-                        false,
-                        ctx,
-                        func,
-                        loc,
-                        input_vars,
-                        params,
-                        func_call_str,
-                        modifier_state,
-                    )
-                } else {
-                    Err(ExprErr::InvalidFunctionInput(
-                        loc,
-                        format!(
-                            "Length mismatch: {inputs:?} {params:?}, inputs as vars: {}, ctx: {}",
-                            ExprRet::Multi(inputs.to_vec()).debug_str(self),
-                            ctx.path(self)
-                        ),
-                    ))
-                }
-            }
-            e => todo!("here: {:?}", e),
-        }
-    }
-
-    fn create_call_ctx(
-        &mut self,
-        curr_ctx: ContextNode,
-        loc: Loc,
-        func_node: FunctionNode,
-        modifier_state: Option<ModifierState>,
-    ) -> Result<ContextNode, ExprErr> {
-        let fn_ext = curr_ctx.is_fn_ext(func_node, self).into_expr_err(loc)?;
-        let ctx = Context::new_subctx(
-            curr_ctx,
-            None,
-            loc,
-            None,
-            Some(func_node),
-            fn_ext,
-            self,
-            modifier_state,
-        )
-        .into_expr_err(loc)?;
-        let callee_ctx = ContextNode::from(self.add_node(Node::Context(ctx)));
-        curr_ctx
-            .set_child_call(callee_ctx, self)
-            .into_expr_err(loc)?;
-        let ctx_fork = self.add_node(Node::FunctionCall);
-        self.add_edge(ctx_fork, curr_ctx, Edge::Context(ContextEdge::Subcontext));
-        self.add_edge(ctx_fork, func_node, Edge::Context(ContextEdge::Call));
-        self.add_edge(
-            NodeIdx::from(callee_ctx.0),
-            ctx_fork,
-            Edge::Context(ContextEdge::Subcontext),
-        );
-        Ok(callee_ctx)
-    }
-
-    /// Maps inputs to function parameters such that if there is a renaming i.e. `a(uint256 x)` is called via `a(y)`,
-    /// we map `y -> x` for future lookups
-    fn map_inputs_to_params(
-        &mut self,
-        loc: Loc,
-        entry_call: bool,
-        params: Vec<FunctionParamNode>,
-        inputs: Vec<ContextVarNode>,
-        callee_ctx: ContextNode,
-    ) -> Result<BTreeMap<ContextVarNode, ContextVarNode>, ExprErr> {
-        Ok(params
-            .iter()
-            .zip(inputs.iter())
-            .filter_map(|(param, input)| {
-                if !entry_call {
-                    if let Some(name) =
-                        self.add_if_err(param.maybe_name(self).into_expr_err(loc))?
-                    {
-                        let res = input
-                            .latest_version(self)
-                            .underlying(self)
-                            .into_expr_err(loc)
-                            .cloned();
-                        let mut new_cvar = self.add_if_err(res)?;
-                        new_cvar.loc = Some(param.loc(self).unwrap());
-                        new_cvar.name = name.clone();
-                        new_cvar.display_name = name;
-                        new_cvar.is_tmp = false;
-                        new_cvar.storage = if let Some(StorageLocation::Storage(_)) =
-                            param.underlying(self).unwrap().storage
-                        {
-                            new_cvar.storage
-                        } else {
-                            None
-                        };
-
-                        if let Some(param_ty) = VarType::try_from_idx(self, param.ty(self).unwrap())
-                        {
-                            let ty = new_cvar.ty.clone();
-                            if !ty.ty_eq(&param_ty, self).unwrap() {
-                                if let Some(new_ty) = ty.try_cast(&param_ty, self).unwrap() {
-                                    new_cvar.ty = new_ty;
-                                }
-                            }
-                        }
-
-                        let node = ContextVarNode::from(self.add_node(Node::ContextVar(new_cvar)));
-                        self.add_edge(
-                            node,
-                            input.latest_version(self),
-                            Edge::Context(ContextEdge::InputVariable),
-                        );
-
-                        if let (Some(r), Some(r2)) =
-                            (node.range(self).unwrap(), param.range(self).unwrap())
-                        {
-                            let new_min =
-                                r.range_min().into_owned().cast(r2.range_min().into_owned());
-                            let new_max =
-                                r.range_max().into_owned().cast(r2.range_max().into_owned());
-                            let res = node.try_set_range_min(self, new_min).into_expr_err(loc);
-                            self.add_if_err(res);
-                            let res = node.try_set_range_max(self, new_max).into_expr_err(loc);
-                            self.add_if_err(res);
-                            let res = node
-                                .try_set_range_exclusions(self, r.exclusions)
-                                .into_expr_err(loc);
-                            self.add_if_err(res);
-                        }
-                        callee_ctx.add_var(node, self).unwrap();
-                        self.add_edge(node, callee_ctx, Edge::Context(ContextEdge::Variable));
-                        Some((*input, node))
-                    } else {
-                        None
-                    }
-                } else {
-                    None
-                }
-            })
-            .collect::<BTreeMap<_, ContextVarNode>>())
-    }
-
-    /// Checks if there are any modifiers and executes them prior to executing the function
-    #[tracing::instrument(level = "trace", skip_all)]
-    fn func_call_inner(
-        &mut self,
-        entry_call: bool,
-        ctx: ContextNode,
-        func_node: FunctionNode,
-        loc: Loc,
-        inputs: Vec<ContextVarNode>,
-        params: Vec<FunctionParamNode>,
-        func_call_str: Option<&str>,
-        modifier_state: Option<ModifierState>,
-    ) -> Result<(), ExprErr> {
-        // pseudocode:
-        //  1. Create context for the call
-        //  2. Check for modifiers
-        //  3. Call modifier 0, then 1, then 2, ... then N.
-        //  4. Call this function
-        //  5. Finish modifier N.. then 2, then 1, then 0
-        let callee_ctx = if entry_call {
-            ctx
-        } else {
-            self.create_call_ctx(ctx, loc, func_node, modifier_state)?
-        };
-
-        // TODO: implement joining
-        // if !entry_call {
-        //     let mapping = params
-        //         .iter()
-        //         .zip(inputs.iter())
-        //         .map(|(param, input)| (*input, *param))
-        //         .collect::<BTreeMap<_, _>>();
-        //     ctx.join(func_node, &mapping, self);
-        // }
-
-        // handle remapping of variable names and bringing variables into the new context
-        let renamed_inputs =
-            self.map_inputs_to_params(loc, entry_call, params, inputs, callee_ctx)?;
-
-        // begin modifier handling by making sure modifiers were set
-        if !func_node.modifiers_set(self).into_expr_err(loc)? {
-            self.set_modifiers(func_node, ctx)?;
-        }
-
-        // get modifiers
-        let mods = func_node.modifiers(self);
-        self.apply_to_edges(callee_ctx, loc, &|analyzer, callee_ctx, loc| {
-            if let Some(mod_state) = &ctx.underlying(analyzer).into_expr_err(loc)?.modifier_state {
-                // we are iterating through modifiers
-                if mod_state.num + 1 < mods.len() {
-                    // use the next modifier
-                    let mut mstate = mod_state.clone();
-                    mstate.num += 1;
-                    analyzer.call_modifier_for_fn(loc, callee_ctx, func_node, mstate)
-                } else {
-                    // out of modifiers, execute the actual function call
-                    analyzer.execute_call_inner(
-                        loc,
-                        ctx,
-                        callee_ctx,
-                        func_node,
-                        &renamed_inputs,
-                        func_call_str,
-                    )
-                }
-            } else if !mods.is_empty() {
-                // we have modifiers and havent executed them, start the process of executing them
-                let state =
-                    ModifierState::new(0, loc, func_node, callee_ctx, ctx, renamed_inputs.clone());
-                analyzer.call_modifier_for_fn(loc, callee_ctx, func_node, state)
-            } else {
-                // no modifiers, just execute the function
-                analyzer.execute_call_inner(
-                    loc,
-                    ctx,
-                    callee_ctx,
-                    func_node,
-                    &renamed_inputs,
-                    func_call_str,
-                )
-            }
-        })
-    }
-
-    /// Actually executes the function
-    #[tracing::instrument(level = "trace", skip_all)]
-    fn execute_call_inner(
-        &mut self,
-        loc: Loc,
-        caller_ctx: ContextNode,
-        callee_ctx: ContextNode,
-        func_node: FunctionNode,
-        _renamed_inputs: &BTreeMap<ContextVarNode, ContextVarNode>,
-        func_call_str: Option<&str>,
-    ) -> Result<(), ExprErr> {
-        if let Some(body) = func_node.underlying(self).into_expr_err(loc)?.body.clone() {
-            // add return nodes into the subctx
-            func_node
-                .returns(self)
-                .collect::<Vec<_>>()
-                .into_iter()
-                .for_each(|ret| {
-                    if let Some(var) = ContextVar::maybe_new_from_func_ret(
-                        self,
-                        ret.underlying(self).unwrap().clone(),
-                    ) {
-                        let cvar = self.add_node(Node::ContextVar(var));
-                        callee_ctx.add_var(cvar.into(), self).unwrap();
-                        self.add_edge(cvar, callee_ctx, Edge::Context(ContextEdge::Variable));
-                    }
-                });
-
-            self.parse_ctx_statement(&body, false, Some(callee_ctx));
-            self.ctx_rets(loc, caller_ctx, callee_ctx)
-        } else {
-            let ret_ctx = Context::new_subctx(
-                callee_ctx,
-                Some(caller_ctx),
-                loc,
-                None,
-                None,
-                false,
-                self,
-                caller_ctx
-                    .underlying(self)
-                    .into_expr_err(loc)?
-                    .modifier_state
-                    .clone(),
-            )
-            .unwrap();
-            let ret_subctx = ContextNode::from(self.add_node(Node::Context(ret_ctx)));
-            self.add_edge(ret_subctx, caller_ctx, Edge::Context(ContextEdge::Continue));
-
-            let res = callee_ctx
-                .set_child_call(ret_subctx, self)
-                .into_expr_err(loc);
-            let _ = self.add_if_err(res);
-            self.apply_to_edges(callee_ctx, loc, &|analyzer, ctx, loc| {
-                func_node
-                    .returns(analyzer)
-                    .collect::<Vec<_>>()
-                    .into_iter()
-                    .try_for_each(|ret| {
-                        let underlying = ret.underlying(analyzer).unwrap();
-                        let mut var =
-                            ContextVar::new_from_func_ret(ctx, analyzer, underlying.clone())
-                                .unwrap()
-                                .expect("No type for return variable?");
-                        if let Some(func_call) = &func_call_str {
-                            var.name =
-                                format!("{}_{}", func_call, callee_ctx.new_tmp(analyzer).unwrap());
-                            var.display_name = func_call.to_string();
-                        }
-                        let node = analyzer.add_node(Node::ContextVar(var));
-                        ctx.add_var(node.into(), analyzer).into_expr_err(loc)?;
-                        analyzer.add_edge(node, ctx, Edge::Context(ContextEdge::Variable));
-                        analyzer.add_edge(node, ctx, Edge::Context(ContextEdge::Return));
-                        ctx.push_expr(ExprRet::Single(node), analyzer)
-                            .into_expr_err(loc)?;
-                        Ok(())
-                    })
-            })
-        }
-    }
-
-    fn ctx_rets(
-        &mut self,
-        loc: Loc,
-        caller_ctx: ContextNode,
-        callee_ctx: ContextNode,
-    ) -> Result<(), ExprErr> {
-        tracing::trace!(
-            "Handling function call return for: {}, {}, depth: {:?}, {:?}",
-            caller_ctx.path(self),
-            callee_ctx.path(self),
-            caller_ctx.depth(self),
-            callee_ctx.depth(self),
-        );
-        match callee_ctx.underlying(self).into_expr_err(loc)?.child {
-            Some(CallFork::Fork(w1, w2)) => {
-                self.ctx_rets(loc, caller_ctx, w1)?;
-                self.ctx_rets(loc, caller_ctx, w2)?;
-                Ok(())
-            }
-            Some(CallFork::Call(c))
-                if c.underlying(self).into_expr_err(loc)?.depth
-                    >= caller_ctx.underlying(self).into_expr_err(loc)?.depth =>
-            {
-                // follow rabbit hole
-                self.ctx_rets(loc, caller_ctx, c)?;
-                Ok(())
-            }
-            _ => {
-                if callee_ctx.is_killed(self).into_expr_err(loc)? {
-                    return Ok(());
-                }
-                let callee_depth = callee_ctx.underlying(self).into_expr_err(loc)?.depth;
-                let caller_depth = caller_ctx.underlying(self).into_expr_err(loc)?.depth;
-                if callee_depth != caller_depth {
-                    let ctx = Context::new_subctx(
-                        callee_ctx,
-                        Some(caller_ctx),
-                        loc,
-                        None,
-                        None,
-                        false,
-                        self,
-                        caller_ctx
-                            .underlying(self)
-                            .into_expr_err(loc)?
-                            .modifier_state
-                            .clone(),
-                    )
-                    .unwrap();
-                    let ret_subctx = ContextNode::from(self.add_node(Node::Context(ctx)));
-                    self.add_edge(ret_subctx, caller_ctx, Edge::Context(ContextEdge::Continue));
-
-                    let res = callee_ctx
-                        .set_child_call(ret_subctx, self)
-                        .into_expr_err(loc);
-                    let _ = self.add_if_err(res);
-
-                    let mut rets = callee_ctx.underlying(self).unwrap().ret.clone();
-
-                    if rets.is_empty() {
-                        let func_rets: Vec<FunctionReturnNode> = callee_ctx
-                            .associated_fn(self)
-                            .into_expr_err(loc)?
-                            .returns(self)
-                            .collect();
-                        func_rets
-                            .iter()
-                            .filter_map(|ret| {
-                                let n: String = ret.maybe_name(self).ok()??;
-                                let ret_loc: Loc = ret.loc(self).ok()?;
-                                Some((n, ret_loc))
-                            })
-                            .collect::<Vec<_>>()
-                            .into_iter()
-                            .try_for_each(|(name, ret_loc)| {
-                                if let Some(cvar) = callee_ctx
-                                    .var_by_name_or_recurse(self, &name)
-                                    .into_expr_err(loc)?
-                                {
-                                    let cvar = cvar.latest_version(self);
-                                    // let ret_loc = ret.loc(self).into_expr_err(loc)?;
-                                    callee_ctx
-                                        .add_return_node(ret_loc, cvar, self)
-                                        .into_expr_err(loc)?;
-                                    self.add_edge(
-                                        cvar,
-                                        callee_ctx,
-                                        Edge::Context(ContextEdge::Return),
-                                    );
-                                }
-                                Ok(())
-                            })?;
-
-                        // add unnamed rets
-                        func_rets
-                            .into_iter()
-                            .filter(|ret| ret.maybe_name(self).unwrap().is_none())
-                            .collect::<Vec<_>>()
-                            .iter()
-                            .try_for_each(|ret| {
-                                let ret_loc = ret.loc(self).into_expr_err(loc)?;
-                                let cvar = ContextVar::new_from_func_ret(
-                                    callee_ctx,
-                                    self,
-                                    ret.underlying(self).into_expr_err(loc)?.clone(),
-                                )
-                                .into_expr_err(loc)?
-                                .unwrap();
-                                let cvar =
-                                    ContextVarNode::from(self.add_node(Node::ContextVar(cvar)));
-                                callee_ctx.add_var(cvar, self).into_expr_err(loc)?;
-                                self.add_edge(
-                                    cvar,
-                                    callee_ctx,
-                                    Edge::Context(ContextEdge::Variable),
-                                );
-                                callee_ctx
-                                    .add_return_node(ret_loc, cvar, self)
-                                    .into_expr_err(loc)?;
-                                self.add_edge(cvar, callee_ctx, Edge::Context(ContextEdge::Return));
-                                Ok(())
-                            })?;
-                        rets = callee_ctx.underlying(self).unwrap().ret.clone();
-                    }
-
-                    let handle_rets = rets.iter().all(|(_, node)| node.is_some());
-                    if handle_rets {
-                        let ret = rets
-                            .into_iter()
-                            .enumerate()
-                            .map(|(i, (_, node))| {
-                                let tmp_ret = node
-                                    .unwrap()
-                                    .as_tmp(
-                                        callee_ctx.underlying(self).unwrap().loc,
-                                        ret_subctx,
-                                        self,
-                                    )
-                                    .unwrap();
-                                tmp_ret.underlying_mut(self).into_expr_err(loc)?.is_return = true;
-                                tmp_ret
-                                    .underlying_mut(self)
-                                    .into_expr_err(loc)?
-                                    .display_name = format!(
-                                    "{}.{}",
-                                    callee_ctx.associated_fn_name(self).unwrap(),
-                                    i
-                                );
-                                ret_subctx.add_var(tmp_ret, self).into_expr_err(loc)?;
-                                self.add_edge(
-                                    tmp_ret,
-                                    ret_subctx,
-                                    Edge::Context(ContextEdge::Variable),
-                                );
-                                Ok(ExprRet::Single(tmp_ret.into()))
-                            })
-                            .collect::<Result<_, ExprErr>>()?;
-                        ret_subctx
-                            .push_expr(ExprRet::Multi(ret), self)
-                            .into_expr_err(loc)?;
-                    }
-                    Ok(())
-                } else {
-                    let mut rets = callee_ctx.underlying(self).unwrap().ret.clone();
-
-                    if rets.is_empty() {
-                        callee_ctx
-                            .associated_fn(self)
-                            .into_expr_err(loc)?
-                            .returns(self)
-                            .filter_map(|ret| {
-                                let n: String = ret.maybe_name(self).ok()??;
-                                let ret_loc: Loc = ret.loc(self).ok()?;
-                                Some((n, ret_loc))
-                            })
-                            .collect::<Vec<_>>()
-                            .into_iter()
-                            .try_for_each(|(name, ret_loc)| {
-                                if let Some(cvar) = callee_ctx
-                                    .var_by_name_or_recurse(self, &name)
-                                    .into_expr_err(loc)?
-                                {
-                                    let cvar = cvar.latest_version(self);
-                                    // let ret_loc = ret.loc(self).into_expr_err(loc)?;
-                                    callee_ctx
-                                        .add_return_node(ret_loc, cvar, self)
-                                        .into_expr_err(loc)?;
-                                    self.add_edge(
-                                        cvar,
-                                        callee_ctx,
-                                        Edge::Context(ContextEdge::Return),
-                                    );
-                                }
-                                Ok(())
-                            })?;
-                        rets = callee_ctx.underlying(self).unwrap().ret.clone();
-                    }
-                    if rets.iter().all(|(_, node)| node.is_some()) {
-                        callee_ctx
-                            .push_expr(
-                                ExprRet::Multi(
-                                    rets.iter()
-                                        .map(|(_, node)| ExprRet::Single((node.unwrap()).into()))
-                                        .collect(),
-                                ),
-                                self,
-                            )
-                            .into_expr_err(loc)
-                    } else {
-                        Ok(())
-                    }
-                }
-            }
-        }
-    }
-
-    /// Calls a modifier for a function
-    #[tracing::instrument(level = "trace", skip_all)]
-    fn call_modifier_for_fn(
-        &mut self,
-        loc: Loc,
-        func_ctx: ContextNode,
-        func_node: FunctionNode,
-        mod_state: ModifierState,
-    ) -> Result<(), ExprErr> {
-        let mod_node = func_node.modifiers(self)[mod_state.num];
-        tracing::trace!(
-            "calling modifier {} for func {}",
-            mod_node.name(self).into_expr_err(loc)?,
-            func_node.name(self).into_expr_err(loc)?
-        );
-
-        let input_exprs = func_node
-            .modifier_input_vars(mod_state.num, self)
-            .into_expr_err(loc)?;
-
-        input_exprs
-            .iter()
-            .try_for_each(|expr| self.parse_ctx_expr(expr, func_ctx))?;
-        self.apply_to_edges(func_ctx, loc, &|analyzer, ctx, loc| {
-            let input_paths = if input_exprs.is_empty() {
-                ExprRet::Multi(vec![])
-            } else {
-                let Some(input_paths) = ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)?
-                else {
-                    return Err(ExprErr::NoRhs(
-                        loc,
-                        format!("No inputs to modifier, expected: {}", input_exprs.len()),
-                    ));
-                };
-
-                if matches!(input_paths, ExprRet::CtxKilled(_)) {
-                    ctx.push_expr(input_paths, analyzer).into_expr_err(loc)?;
-                    return Ok(());
-                }
-                input_paths
-            };
-
-            analyzer.func_call(
-                ctx,
-                loc,
-                &input_paths,
-                mod_node,
-                None,
-                Some(mod_state.clone()),
-            )
-        })
-    }
-
-    /// Resumes the parent function of a modifier
-    #[tracing::instrument(level = "trace", skip_all)]
-    fn resume_from_modifier(
-        &mut self,
-        ctx: ContextNode,
-        modifier_state: ModifierState,
-    ) -> Result<(), ExprErr> {
-        let mods = modifier_state.parent_fn.modifiers(self);
-        self.apply_to_edges(ctx, modifier_state.loc, &|analyzer, ctx, loc| {
-            if modifier_state.num + 1 < mods.len() {
-                // use the next modifier
-                let mut mstate = modifier_state.clone();
-                mstate.num += 1;
-
-                let loc = mods[mstate.num]
-                    .underlying(analyzer)
-                    .into_expr_err(mstate.loc)?
-                    .loc;
-
-                let pctx = Context::new_subctx(
-                    ctx,
-                    Some(modifier_state.parent_ctx),
-                    loc,
-                    None,
-                    None,
-                    false,
-                    analyzer,
-                    Some(modifier_state.clone()),
-                )
-                .unwrap();
-                let new_parent_subctx = ContextNode::from(analyzer.add_node(Node::Context(pctx)));
-
-                analyzer.add_edge(
-                    new_parent_subctx,
-                    modifier_state.parent_ctx,
-                    Edge::Context(ContextEdge::Continue),
-                );
-                ctx.set_child_call(new_parent_subctx, analyzer)
-                    .into_expr_err(modifier_state.loc)?;
-
-                analyzer.call_modifier_for_fn(
-                    mods[mstate.num]
-                        .underlying(analyzer)
-                        .into_expr_err(mstate.loc)?
-                        .loc,
-                    new_parent_subctx,
-                    mstate.parent_fn,
-                    mstate,
-                )?;
-                Ok(())
-            } else {
-                let pctx = Context::new_subctx(
-                    ctx,
-                    Some(modifier_state.parent_ctx),
-                    modifier_state.loc,
-                    None,
-                    None,
-                    false,
-                    analyzer,
-                    None,
-                )
-                .unwrap();
-                let new_parent_subctx = ContextNode::from(analyzer.add_node(Node::Context(pctx)));
-
-                analyzer.add_edge(
-                    new_parent_subctx,
-                    modifier_state.parent_ctx,
-                    Edge::Context(ContextEdge::Continue),
-                );
-                ctx.set_child_call(new_parent_subctx, analyzer)
-                    .into_expr_err(modifier_state.loc)?;
-
-                // actually execute the parent function
-                analyzer.execute_call_inner(
-                    modifier_state.loc,
-                    ctx,
-                    new_parent_subctx,
-                    modifier_state.parent_fn,
-                    &modifier_state.renamed_inputs,
-                    None,
-                )?;
-
-                fn inherit_return_from_call(
-                    analyzer: &mut (impl GraphBackend + AnalyzerBackend),
-                    loc: Loc,
-                    ctx: ContextNode,
-                ) -> Result<(), ExprErr> {
-                    let mctx =
-                        Context::new_subctx(ctx, Some(ctx), loc, None, None, false, analyzer, None)
-                            .unwrap();
-                    let modifier_after_subctx =
-                        ContextNode::from(analyzer.add_node(Node::Context(mctx)));
-
-                    ctx.set_child_call(modifier_after_subctx, analyzer)
-                        .into_expr_err(loc)?;
-                    analyzer.add_edge(
-                        modifier_after_subctx,
-                        ctx,
-                        Edge::Context(ContextEdge::Continue),
-                    );
-
-                    let ret = ctx.underlying(analyzer).unwrap().ret.clone();
-                    modifier_after_subctx.underlying_mut(analyzer).unwrap().ret = ret;
-                    Ok(())
-                }
-
-                analyzer.apply_to_edges(new_parent_subctx, loc, &|analyzer, ctx, _loc| {
-                    inherit_return_from_call(analyzer, modifier_state.loc, ctx)
-                })
-
-                // if edges.is_empty() {
-                //     inherit_return_from_call(analyzer, modifier_state.loc, new_parent_subctx)?;
-                // } else {
-                //     edges.iter().try_for_each(|i| {
-                //         inherit_return_from_call(analyzer, modifier_state.loc, *i)?;
-                //         Ok(())
-                //     })?;
-                // }
-                // Ok(())
-            }
-        })
-    }
-
-    /// Inherit the input changes from a function call
-    fn inherit_input_changes(
-        &mut self,
-        loc: Loc,
-        to_ctx: ContextNode,
-        from_ctx: ContextNode,
-        renamed_inputs: &BTreeMap<ContextVarNode, ContextVarNode>,
-    ) -> Result<(), ExprErr> {
-        if to_ctx != from_ctx {
-            self.apply_to_edges(to_ctx, loc, &|analyzer, to_ctx, loc| {
-                renamed_inputs
-                    .iter()
-                    .try_for_each(|(input_var, updated_var)| {
-                        let new_input = analyzer.advance_var_in_ctx(
-                            input_var.latest_version(analyzer),
-                            loc,
-                            to_ctx,
-                        )?;
-                        let latest_updated = updated_var.latest_version(analyzer);
-                        if let Some(updated_var_range) =
-                            latest_updated.range(analyzer).into_expr_err(loc)?
-                        {
-                            let res = new_input
-                                .set_range_min(analyzer, updated_var_range.range_min().into_owned())
-                                .into_expr_err(loc);
-                            let _ = analyzer.add_if_err(res);
-                            let res = new_input
-                                .set_range_max(analyzer, updated_var_range.range_max().into_owned())
-                                .into_expr_err(loc);
-                            let _ = analyzer.add_if_err(res);
-                            let res = new_input
-                                .set_range_exclusions(
-                                    analyzer,
-                                    updated_var_range.range_exclusions(),
-                                )
-                                .into_expr_err(loc);
-                            let _ = analyzer.add_if_err(res);
-                        }
-                        Ok(())
-                    })
-            })?;
-        }
-        Ok(())
-    }
-
-    /// Inherit the input changes from a function call
-    fn modifier_inherit_return(&mut self, mod_ctx: ContextNode, fn_ctx: ContextNode) {
-        let ret = fn_ctx.underlying(self).unwrap().ret.clone();
-        mod_ctx.underlying_mut(self).unwrap().ret = ret;
-    }
-
-    /// Inherit the storage changes from a function call
-    fn inherit_storage_changes(
-        &mut self,
-        loc: Loc,
-        inheritor_ctx: ContextNode,
-        grantor_ctx: ContextNode,
-    ) -> Result<(), ExprErr> {
-        if inheritor_ctx != grantor_ctx {
-            return self.apply_to_edges(inheritor_ctx, loc, &|analyzer, inheritor_ctx, loc| {
-                let vars = grantor_ctx.local_vars(analyzer).clone();
-                vars.iter().try_for_each(|(name, old_var)| {
-                    let var = old_var.latest_version(analyzer);
-                    let underlying = var.underlying(analyzer).into_expr_err(loc)?;
-                    if var.is_storage(analyzer).into_expr_err(loc)? {
-                        if let Some(inheritor_var) = inheritor_ctx.var_by_name(analyzer, name) {
-                            let inheritor_var = inheritor_var.latest_version(analyzer);
-                            if let Some(r) = underlying.ty.range(analyzer).into_expr_err(loc)? {
-                                let new_inheritor_var = analyzer
-                                    .advance_var_in_ctx(
-                                        inheritor_var,
-                                        underlying.loc.expect("No loc for val change"),
-                                        inheritor_ctx,
-                                    )
-                                    .unwrap();
-                                let _ = new_inheritor_var
-                                    .set_range_min(analyzer, r.range_min().into_owned());
-                                let _ = new_inheritor_var
-                                    .set_range_max(analyzer, r.range_max().into_owned());
-                                let _ = new_inheritor_var
-                                    .set_range_exclusions(analyzer, r.range_exclusions());
-                            }
-                        } else {
-                            let new_in_inheritor =
-                                analyzer.add_node(Node::ContextVar(underlying.clone()));
-                            inheritor_ctx
-                                .add_var(new_in_inheritor.into(), analyzer)
-                                .into_expr_err(loc)?;
-                            analyzer.add_edge(
-                                new_in_inheritor,
-                                inheritor_ctx,
-                                Edge::Context(ContextEdge::Variable),
-                            );
-                            analyzer.add_edge(
-                                new_in_inheritor,
-                                var,
-                                Edge::Context(ContextEdge::InheritedVariable),
-                            );
-                        }
-                    }
-                    Ok(())
-                })
-            });
-        }
-        Ok(())
-    }
-
-    fn modifiers(
-        &mut self,
-        ctx: ContextNode,
-        func: FunctionNode,
-    ) -> Result<Vec<FunctionNode>, ExprErr> {
-        use std::fmt::Write;
-        let binding = func.underlying(self).unwrap().clone();
-        let modifiers = binding.modifiers_as_base();
-        if modifiers.is_empty() {
-            Ok(vec![])
-        } else {
-            let res = modifiers
-                .iter()
-                .map(|modifier| {
-                    assert_eq!(modifier.name.identifiers.len(), 1);
-                    // construct arg string for function selector
-                    let mut mod_name = format!("{}", modifier.name.identifiers[0]);
-                    if let Some(args) = &modifier.args {
-                        let args_str = args
-                            .iter()
-                            .map(|expr| {
-                                let mctx = Context::new_subctx(
-                                    ctx,
-                                    None,
-                                    Loc::Implicit,
-                                    None,
-                                    None,
-                                    false,
-                                    self,
-                                    None,
-                                )
-                                .into_expr_err(Loc::Implicit)?;
-                                let callee_ctx =
-                                    ContextNode::from(self.add_node(Node::Context(mctx)));
-                                let _res = ctx.set_child_call(callee_ctx, self);
-                                self.parse_ctx_expr(expr, callee_ctx)?;
-                                let f: Vec<String> =
-                                    self.take_from_edge(ctx, expr.loc(), &|analyzer, ctx, loc| {
-                                        if let Some(ret) =
-                                            ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)?
-                                        {
-                                            Ok(ret.try_as_func_input_str(analyzer))
-                                        } else {
-                                            Err(ExprErr::ParseError(
-                                                loc,
-                                                "Bad modifier parse".to_string(),
-                                            ))
-                                        }
-                                    })?;
-
-                                ctx.delete_child(self).into_expr_err(expr.loc())?;
-                                Ok(f.first().unwrap().clone())
-                            })
-                            .collect::<Result<Vec<_>, ExprErr>>()?
-                            .join(", ");
-                        let _ = write!(mod_name, "{args_str}");
-                    } else {
-                        let _ = write!(mod_name, "()");
-                    }
-                    let _ = write!(mod_name, "");
-                    let found: Option<FunctionNode> = ctx
-                        .visible_modifiers(self)
-                        .unwrap()
-                        .iter()
-                        .find(|modifier| modifier.name(self).unwrap() == mod_name)
-                        .copied();
-                    Ok(found)
-                })
-                .collect::<Result<Vec<Option<_>>, ExprErr>>()?
-                .into_iter()
-                .flatten()
-                .collect::<Vec<_>>();
-            Ok(res)
-        }
-    }
-
-    fn set_modifiers(&mut self, func: FunctionNode, ctx: ContextNode) -> Result<(), ExprErr> {
-        let modifiers = self.modifiers(ctx, func)?;
-        modifiers
-            .iter()
-            .enumerate()
-            .for_each(|(i, modifier)| self.add_edge(*modifier, func, Edge::FuncModifier(i)));
-        func.underlying_mut(self).unwrap().modifiers_set = true;
-        Ok(())
-    }
-}
+pub mod func_caller;
diff --git a/crates/solc-expressions/src/func_call/modifier.rs b/crates/solc-expressions/src/func_call/modifier.rs
index a6d56a05..bec4128e 100644
--- a/crates/solc-expressions/src/func_call/modifier.rs
+++ b/crates/solc-expressions/src/func_call/modifier.rs
@@ -1,31 +1,288 @@
-use crate::{ExprErr, FuncCaller, IntoExprErr};
+//! Traits & blanket implementations that facilitate performing modifier function calls.
+
+use crate::helper::CallerHelper;
+use crate::{
+    ContextBuilder, ExprErr, IntoExprErr, func_caller::FuncCaller
+};
 
 use graph::{
-    nodes::{ContextNode, ExprRet, FunctionNode},
-    AnalyzerBackend, GraphBackend,
+    nodes::{
+        Context, ContextNode, ExprRet, FunctionNode, ModifierState,
+    },
+    AnalyzerBackend, ContextEdge, Edge, GraphBackend, Node,
 };
 
-use solang_parser::pt::{Expression, Loc};
+use solang_parser::pt::{Expression, Loc, CodeLocation};
 
 impl<T> ModifierCaller for T where
-    T: AnalyzerBackend<Expr = Expression, ExprErr = ExprErr> + Sized + GraphBackend
+    T: AnalyzerBackend<Expr = Expression, ExprErr = ExprErr> + Sized + GraphBackend + FuncCaller + CallerHelper
 {
 }
+/// A trait for dealing with modifier calls
 pub trait ModifierCaller:
-    GraphBackend + AnalyzerBackend<Expr = Expression, ExprErr = ExprErr> + Sized
+    GraphBackend + AnalyzerBackend<Expr = Expression, ExprErr = ExprErr> + Sized + FuncCaller + CallerHelper
 {
-    fn handle_modifiers(
+    /// Calls a modifier for a function
+    #[tracing::instrument(level = "trace", skip_all)]
+    fn call_modifier_for_fn(
         &mut self,
-        ctx: ContextNode,
         loc: Loc,
-        _input_paths: &ExprRet,
+        func_ctx: ContextNode,
+        func_node: FunctionNode,
+        mod_state: ModifierState,
+    ) -> Result<(), ExprErr> {
+        let mod_node = func_node.modifiers(self)[mod_state.num];
+        tracing::trace!(
+            "calling modifier {} for func {}",
+            mod_node.name(self).into_expr_err(loc)?,
+            func_node.name(self).into_expr_err(loc)?
+        );
+
+        let input_exprs = func_node
+            .modifier_input_vars(mod_state.num, self)
+            .into_expr_err(loc)?;
+
+        input_exprs
+            .iter()
+            .try_for_each(|expr| self.parse_ctx_expr(expr, func_ctx))?;
+        self.apply_to_edges(func_ctx, loc, &|analyzer, ctx, loc| {
+            let input_paths = if input_exprs.is_empty() {
+                ExprRet::Multi(vec![])
+            } else {
+                let Some(input_paths) = ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)?
+                else {
+                    return Err(ExprErr::NoRhs(
+                        loc,
+                        format!("No inputs to modifier, expected: {}", input_exprs.len()),
+                    ));
+                };
+
+                if matches!(input_paths, ExprRet::CtxKilled(_)) {
+                    ctx.push_expr(input_paths, analyzer).into_expr_err(loc)?;
+                    return Ok(());
+                }
+                input_paths
+            };
+
+            analyzer.func_call(
+                ctx,
+                loc,
+                &input_paths,
+                mod_node,
+                None,
+                Some(mod_state.clone()),
+            )
+        })
+    }
+
+    /// Resumes the parent function of a modifier
+    #[tracing::instrument(level = "trace", skip_all)]
+    fn resume_from_modifier(
+        &mut self,
+        ctx: ContextNode,
+        modifier_state: ModifierState,
+    ) -> Result<(), ExprErr> {
+        let mods = modifier_state.parent_fn.modifiers(self);
+        self.apply_to_edges(ctx, modifier_state.loc, &|analyzer, ctx, loc| {
+            if modifier_state.num + 1 < mods.len() {
+                // use the next modifier
+                let mut mstate = modifier_state.clone();
+                mstate.num += 1;
+
+                let loc = mods[mstate.num]
+                    .underlying(analyzer)
+                    .into_expr_err(mstate.loc)?
+                    .loc;
+
+                let pctx = Context::new_subctx(
+                    ctx,
+                    Some(modifier_state.parent_ctx),
+                    loc,
+                    None,
+                    None,
+                    false,
+                    analyzer,
+                    Some(modifier_state.clone()),
+                )
+                .unwrap();
+                let new_parent_subctx = ContextNode::from(analyzer.add_node(Node::Context(pctx)));
+
+                analyzer.add_edge(
+                    new_parent_subctx,
+                    modifier_state.parent_ctx,
+                    Edge::Context(ContextEdge::Continue),
+                );
+                ctx.set_child_call(new_parent_subctx, analyzer)
+                    .into_expr_err(modifier_state.loc)?;
+
+                analyzer.call_modifier_for_fn(
+                    mods[mstate.num]
+                        .underlying(analyzer)
+                        .into_expr_err(mstate.loc)?
+                        .loc,
+                    new_parent_subctx,
+                    mstate.parent_fn,
+                    mstate,
+                )?;
+                Ok(())
+            } else {
+                let pctx = Context::new_subctx(
+                    ctx,
+                    Some(modifier_state.parent_ctx),
+                    modifier_state.loc,
+                    None,
+                    None,
+                    false,
+                    analyzer,
+                    None,
+                )
+                .unwrap();
+                let new_parent_subctx = ContextNode::from(analyzer.add_node(Node::Context(pctx)));
+
+                analyzer.add_edge(
+                    new_parent_subctx,
+                    modifier_state.parent_ctx,
+                    Edge::Context(ContextEdge::Continue),
+                );
+                ctx.set_child_call(new_parent_subctx, analyzer)
+                    .into_expr_err(modifier_state.loc)?;
+
+                // actually execute the parent function
+                analyzer.execute_call_inner(
+                    modifier_state.loc,
+                    ctx,
+                    new_parent_subctx,
+                    modifier_state.parent_fn,
+                    &modifier_state.renamed_inputs,
+                    None,
+                )?;
+
+                fn inherit_return_from_call(
+                    analyzer: &mut (impl GraphBackend + AnalyzerBackend),
+                    loc: Loc,
+                    ctx: ContextNode,
+                ) -> Result<(), ExprErr> {
+                    let mctx =
+                        Context::new_subctx(ctx, Some(ctx), loc, None, None, false, analyzer, None)
+                            .unwrap();
+                    let modifier_after_subctx =
+                        ContextNode::from(analyzer.add_node(Node::Context(mctx)));
+
+                    ctx.set_child_call(modifier_after_subctx, analyzer)
+                        .into_expr_err(loc)?;
+                    analyzer.add_edge(
+                        modifier_after_subctx,
+                        ctx,
+                        Edge::Context(ContextEdge::Continue),
+                    );
+
+                    let ret = ctx.underlying(analyzer).unwrap().ret.clone();
+                    modifier_after_subctx.underlying_mut(analyzer).unwrap().ret = ret;
+                    Ok(())
+                }
+
+                analyzer.apply_to_edges(new_parent_subctx, loc, &|analyzer, ctx, _loc| {
+                    inherit_return_from_call(analyzer, modifier_state.loc, ctx)
+                })
+
+                // if edges.is_empty() {
+                //     inherit_return_from_call(analyzer, modifier_state.loc, new_parent_subctx)?;
+                // } else {
+                //     edges.iter().try_for_each(|i| {
+                //         inherit_return_from_call(analyzer, modifier_state.loc, *i)?;
+                //         Ok(())
+                //     })?;
+                // }
+                // Ok(())
+            }
+        })
+    }
+
+    /// Gets the modifiers for a function
+    fn modifiers(
+        &mut self,
+        ctx: ContextNode,
         func: FunctionNode,
-        _func_call_str: Option<String>,
-    ) -> Result<ExprRet, ExprErr> {
-        if !func.modifiers_set(self).into_expr_err(loc)? {
-            self.set_modifiers(func, ctx)?;
+    ) -> Result<Vec<FunctionNode>, ExprErr> {
+        use std::fmt::Write;
+        let binding = func.underlying(self).unwrap().clone();
+        let modifiers = binding.modifiers_as_base();
+        if modifiers.is_empty() {
+            Ok(vec![])
+        } else {
+            let res = modifiers
+                .iter()
+                .map(|modifier| {
+                    assert_eq!(modifier.name.identifiers.len(), 1);
+                    // construct arg string for function selector
+                    let mut mod_name = format!("{}", modifier.name.identifiers[0]);
+                    if let Some(args) = &modifier.args {
+                        let args_str = args
+                            .iter()
+                            .map(|expr| {
+                                let mctx = Context::new_subctx(
+                                    ctx,
+                                    None,
+                                    Loc::Implicit,
+                                    None,
+                                    None,
+                                    false,
+                                    self,
+                                    None,
+                                )
+                                .into_expr_err(Loc::Implicit)?;
+                                let callee_ctx =
+                                    ContextNode::from(self.add_node(Node::Context(mctx)));
+                                let _res = ctx.set_child_call(callee_ctx, self);
+                                self.parse_ctx_expr(expr, callee_ctx)?;
+                                let f: Vec<String> =
+                                    self.take_from_edge(ctx, expr.loc(), &|analyzer, ctx, loc| {
+                                        if let Some(ret) =
+                                            ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)?
+                                        {
+                                            Ok(ret.try_as_func_input_str(analyzer))
+                                        } else {
+                                            Err(ExprErr::ParseError(
+                                                loc,
+                                                "Bad modifier parse".to_string(),
+                                            ))
+                                        }
+                                    })?;
+
+                                ctx.delete_child(self).into_expr_err(expr.loc())?;
+                                Ok(f.first().unwrap().clone())
+                            })
+                            .collect::<Result<Vec<_>, ExprErr>>()?
+                            .join(", ");
+                        let _ = write!(mod_name, "{args_str}");
+                    } else {
+                        let _ = write!(mod_name, "()");
+                    }
+                    let _ = write!(mod_name, "");
+                    let found: Option<FunctionNode> = ctx
+                        .visible_modifiers(self)
+                        .unwrap()
+                        .iter()
+                        .find(|modifier| modifier.name(self).unwrap() == mod_name)
+                        .copied();
+                    Ok(found)
+                })
+                .collect::<Result<Vec<Option<_>>, ExprErr>>()?
+                .into_iter()
+                .flatten()
+                .collect::<Vec<_>>();
+            Ok(res)
         }
+    }
 
-        todo!()
+    /// Sets the modifiers for a function
+    fn set_modifiers(&mut self, func: FunctionNode, ctx: ContextNode) -> Result<(), ExprErr> {
+        let modifiers = self.modifiers(ctx, func)?;
+        modifiers
+            .iter()
+            .enumerate()
+            .for_each(|(i, modifier)| self.add_edge(*modifier, func, Edge::FuncModifier(i)));
+        func.underlying_mut(self).unwrap().modifiers_set = true;
+        Ok(())
     }
 }
diff --git a/crates/solc-expressions/src/func_call/namespaced_call.rs b/crates/solc-expressions/src/func_call/namespaced_call.rs
index 1086c7d9..2c2778c2 100644
--- a/crates/solc-expressions/src/func_call/namespaced_call.rs
+++ b/crates/solc-expressions/src/func_call/namespaced_call.rs
@@ -1,6 +1,9 @@
+//! Traits & blanket implementations that facilitate performing namespaced function calls.
+
 use crate::{
-    intrinsic_call::IntrinsicFuncCaller, member_access::MemberAccess, ContextBuilder, ExprErr,
-    FuncCaller, IntoExprErr,
+    func_call::helper::CallerHelper,
+    func_call::func_caller::FuncCaller,
+    intrinsic_call::IntrinsicFuncCaller, member_access::MemberAccess, ContextBuilder, ExprErr, IntoExprErr,
 };
 
 use graph::{
@@ -13,13 +16,15 @@ use shared::NodeIdx;
 use solang_parser::pt::{Expression, Identifier, Loc, NamedArgument};
 
 impl<T> NameSpaceFuncCaller for T where
-    T: AnalyzerBackend<Expr = Expression, ExprErr = ExprErr> + Sized + GraphBackend
+    T: AnalyzerBackend<Expr = Expression, ExprErr = ExprErr> + Sized + GraphBackend + CallerHelper
 {
 }
+/// A trait for performing namespaced function calls (i.e. `MyContract.myFunc(...)`)
 pub trait NameSpaceFuncCaller:
-    AnalyzerBackend<Expr = Expression, ExprErr = ExprErr> + Sized + GraphBackend
+    AnalyzerBackend<Expr = Expression, ExprErr = ExprErr> + Sized + GraphBackend + CallerHelper
 {
     #[tracing::instrument(level = "trace", skip_all)]
+    /// TODO: Call a namedspaced named input function, i.e. `MyContract.myFunc({a: 1, b: 2})`
     fn call_name_spaced_named_func(
         &mut self,
         ctx: ContextNode,
@@ -33,6 +38,7 @@ pub trait NameSpaceFuncCaller:
     }
 
     #[tracing::instrument(level = "trace", skip_all)]
+    /// Call a namedspaced function, i.e. `MyContract.myFunc(...)`
     fn call_name_spaced_func(
         &mut self,
         ctx: ContextNode,
@@ -153,6 +159,7 @@ pub trait NameSpaceFuncCaller:
         })
     }
 
+    /// Match the expression return for getting the member node
     fn match_namespaced_member(
         &mut self,
         ctx: ContextNode,
@@ -178,6 +185,7 @@ pub trait NameSpaceFuncCaller:
     }
 
     #[tracing::instrument(level = "trace", skip_all)]
+    /// Actually perform the namespaced function call
     fn call_name_spaced_func_inner(
         &mut self,
         ctx: ContextNode,
diff --git a/crates/solc-expressions/src/lib.rs b/crates/solc-expressions/src/lib.rs
index b3e4ace9..2e903258 100644
--- a/crates/solc-expressions/src/lib.rs
+++ b/crates/solc-expressions/src/lib.rs
@@ -13,7 +13,7 @@ mod loops;
 mod member_access;
 mod require;
 mod variable;
-mod yul;
+pub mod yul;
 
 pub use array::*;
 pub use bin_op::*;
@@ -28,8 +28,8 @@ pub use loops::*;
 pub use member_access::*;
 pub use require::*;
 pub use variable::*;
-pub use yul::*;
 
+/// Supertrait for parsing expressions
 pub trait ExprParser:
     BinOp + Require + Variable + Literal + Array + MemberAccess + Cmp + CondOp + List + Env
 {
@@ -39,7 +39,9 @@ impl<T> ExprParser for T where
 {
 }
 
+/// Convert some error into an expression error by attaching a code source location
 pub trait IntoExprErr<T> {
+    /// Convert into a ExprErr
     fn into_expr_err(self, loc: Loc) -> Result<T, ExprErr>;
 }
 
@@ -53,6 +55,7 @@ impl<T> IntoExprErr<T> for Result<T, graph::GraphError> {
 }
 
 #[derive(Debug, Clone, PartialOrd, Ord, PartialEq, Eq)]
+/// An error that arose from the analyzer when interpreting expressions and statements
 pub enum ExprErr {
     ParseError(Loc, String),
     NoLhs(Loc, String),
@@ -81,12 +84,14 @@ pub enum ExprErr {
 }
 
 impl ExprErr {
+    /// Convert from a graph error
     pub fn from_graph_err(loc: Loc, graph_err: graph::GraphError) -> Self {
         Self::GraphError(loc, graph_err)
     }
 }
 
 impl ExprErr {
+    /// Get the code source location of the error
     pub fn loc(&self) -> Loc {
         use ExprErr::*;
         match self {
@@ -115,6 +120,7 @@ impl ExprErr {
         }
     }
 
+    /// Get the error message
     pub fn msg(&self) -> &str {
         use ExprErr::*;
         match self {
@@ -152,6 +158,7 @@ impl ExprErr {
         }
     }
 
+    /// Get the top-level report message
     pub fn report_msg(&self) -> &str {
         use ExprErr::*;
         match self {
diff --git a/crates/solc-expressions/src/list.rs b/crates/solc-expressions/src/list.rs
index a3af89d9..39af7787 100644
--- a/crates/solc-expressions/src/list.rs
+++ b/crates/solc-expressions/src/list.rs
@@ -8,7 +8,7 @@ use graph::{
 use solang_parser::pt::{Expression, Loc, Parameter, ParameterList};
 
 impl<T> List for T where T: AnalyzerBackend<Expr = Expression, ExprErr = ExprErr> + Sized {}
-
+/// Dealing with list parsing and operations
 pub trait List: AnalyzerBackend<Expr = Expression, ExprErr = ExprErr> + Sized {
     #[tracing::instrument(level = "trace", skip_all)]
     fn list(&mut self, ctx: ContextNode, loc: Loc, params: &ParameterList) -> Result<(), ExprErr> {
diff --git a/crates/solc-expressions/src/literal.rs b/crates/solc-expressions/src/literal.rs
index 242834ae..de1d0837 100644
--- a/crates/solc-expressions/src/literal.rs
+++ b/crates/solc-expressions/src/literal.rs
@@ -13,6 +13,7 @@ use std::str::FromStr;
 
 impl<T> Literal for T where T: AnalyzerBackend + Sized {}
 
+/// Dealing with literal expression and parsing them into nodes
 pub trait Literal: AnalyzerBackend + Sized {
     fn number_literal(
         &mut self,
diff --git a/crates/solc-expressions/src/loops.rs b/crates/solc-expressions/src/loops.rs
index c7aec51a..bb02b142 100644
--- a/crates/solc-expressions/src/loops.rs
+++ b/crates/solc-expressions/src/loops.rs
@@ -11,10 +11,13 @@ impl<T> Looper for T where
     T: AnalyzerBackend<Expr = Expression, ExprErr = ExprErr> + Sized + GraphBackend
 {
 }
+
+/// Dealing with loops
 pub trait Looper:
     GraphBackend + AnalyzerBackend<Expr = Expression, ExprErr = ExprErr> + Sized
 {
     #[tracing::instrument(level = "trace", skip_all)]
+    /// Handles a for loop. Needs improvement
     fn for_loop(
         &mut self,
         loc: Loc,
@@ -38,6 +41,7 @@ pub trait Looper:
         }
     }
 
+    /// Resets all variables referenced in the loop because we don't elegantly handle loops
     fn reset_vars(&mut self, loc: Loc, ctx: ContextNode, body: &Statement) -> Result<(), ExprErr> {
         let og_ctx = ctx;
         let sctx = Context::new_subctx(ctx, None, loc, None, None, false, self, None)
@@ -81,6 +85,7 @@ pub trait Looper:
         })
     }
 
+    /// Handles a while-loop
     fn while_loop(
         &mut self,
         loc: Loc,
diff --git a/crates/solc-expressions/src/member_access/builtin_access.rs b/crates/solc-expressions/src/member_access/builtin_access.rs
index b901b282..04ad8278 100644
--- a/crates/solc-expressions/src/member_access/builtin_access.rs
+++ b/crates/solc-expressions/src/member_access/builtin_access.rs
@@ -12,9 +12,12 @@ impl<T> BuiltinAccess for T where
     T: LibraryAccess + AnalyzerBackend<Expr = Expression, ExprErr = ExprErr> + Sized
 {
 }
+
+/// Trait for performing member access on builtin types
 pub trait BuiltinAccess:
     LibraryAccess + AnalyzerBackend<Expr = Expression, ExprErr = ExprErr> + Sized
 {
+    /// Perform member access on builtin types
     fn builtin_member_access(
         &mut self,
         loc: Loc,
diff --git a/crates/solc-expressions/src/member_access/contract_access.rs b/crates/solc-expressions/src/member_access/contract_access.rs
index 28945a58..860558a8 100644
--- a/crates/solc-expressions/src/member_access/contract_access.rs
+++ b/crates/solc-expressions/src/member_access/contract_access.rs
@@ -9,7 +9,10 @@ use shared::NodeIdx;
 use solang_parser::pt::{Expression, Identifier, Loc};
 
 impl<T> ContractAccess for T where T: AnalyzerBackend<Expr = Expression, ExprErr = ExprErr> + Sized {}
+
+/// Trait for performing member access on a Contract
 pub trait ContractAccess: AnalyzerBackend<Expr = Expression, ExprErr = ExprErr> + Sized {
+    /// Perform member access on a contract
     fn contract_member_access(
         &mut self,
         member_idx: NodeIdx,
diff --git a/crates/solc-expressions/src/member_access/enum_access.rs b/crates/solc-expressions/src/member_access/enum_access.rs
index 435ce8f9..c373e772 100644
--- a/crates/solc-expressions/src/member_access/enum_access.rs
+++ b/crates/solc-expressions/src/member_access/enum_access.rs
@@ -12,9 +12,12 @@ impl<T> EnumAccess for T where
     T: LibraryAccess + AnalyzerBackend<Expr = Expression, ExprErr = ExprErr> + Sized
 {
 }
+
+/// Trait for performing member access on an enum
 pub trait EnumAccess:
     LibraryAccess + AnalyzerBackend<Expr = Expression, ExprErr = ExprErr> + Sized
 {
+    /// Perform member access on an enum
     fn enum_member_access(
         &mut self,
         _member_idx: NodeIdx,
diff --git a/crates/solc-expressions/src/member_access/library_access.rs b/crates/solc-expressions/src/member_access/library_access.rs
index 4a5d234d..164b5f0b 100644
--- a/crates/solc-expressions/src/member_access/library_access.rs
+++ b/crates/solc-expressions/src/member_access/library_access.rs
@@ -12,7 +12,10 @@ use solang_parser::pt::{Expression, Identifier};
 use std::collections::BTreeSet;
 
 impl<T> LibraryAccess for T where T: AnalyzerBackend<Expr = Expression, ExprErr = ExprErr> + Sized {}
+
+/// Trait for getting library functions for a type
 pub trait LibraryAccess: AnalyzerBackend<Expr = Expression, ExprErr = ExprErr> + Sized {
+    /// Search for a library function by name
     fn library_func_search(
         &mut self,
         ctx: ContextNode,
@@ -37,6 +40,7 @@ pub trait LibraryAccess: AnalyzerBackend<Expr = Expression, ExprErr = ExprErr> +
             })
     }
 
+    /// Get all possible library functions
     fn possible_library_funcs(&mut self, ctx: ContextNode, ty: NodeIdx) -> BTreeSet<FunctionNode> {
         let mut funcs: BTreeSet<FunctionNode> = BTreeSet::new();
         if let Some(associated_contract) = ctx.maybe_associated_contract(self).unwrap() {
diff --git a/crates/solc-expressions/src/member_access/list_access.rs b/crates/solc-expressions/src/member_access/list_access.rs
index 3923db87..79657c65 100644
--- a/crates/solc-expressions/src/member_access/list_access.rs
+++ b/crates/solc-expressions/src/member_access/list_access.rs
@@ -10,8 +10,10 @@ use shared::NodeIdx;
 use solang_parser::pt::{Expression, Identifier, Loc};
 
 impl<T> ListAccess for T where T: AnalyzerBackend<Expr = Expression, ExprErr = ExprErr> + Sized {}
+/// Handles list/array member access (indices, length, etc)
 pub trait ListAccess: AnalyzerBackend<Expr = Expression, ExprErr = ExprErr> + Sized {
     #[tracing::instrument(level = "trace", skip_all)]
+    /// Access an index of a list/array
     fn index_access(
         &mut self,
         loc: Loc,
@@ -35,6 +37,7 @@ pub trait ListAccess: AnalyzerBackend<Expr = Expression, ExprErr = ExprErr> + Si
     }
 
     #[tracing::instrument(level = "trace", skip_all)]
+    /// Match on the [`ExprRet`] of a index access expression
     fn match_index_access(
         &mut self,
         index_paths: &ExprRet,
@@ -91,6 +94,7 @@ pub trait ListAccess: AnalyzerBackend<Expr = Expression, ExprErr = ExprErr> + Si
     }
 
     #[tracing::instrument(level = "trace", skip_all)]
+    /// Get the length member of an array/list
     fn length(
         &mut self,
         loc: Loc,
@@ -114,6 +118,7 @@ pub trait ListAccess: AnalyzerBackend<Expr = Expression, ExprErr = ExprErr> + Si
     }
 
     #[tracing::instrument(level = "trace", skip_all)]
+    /// Get the length member of an array/list and create it as a temporary variable
     fn tmp_length(
         &mut self,
         arr: ContextVarNode,
@@ -188,6 +193,7 @@ pub trait ListAccess: AnalyzerBackend<Expr = Expression, ExprErr = ExprErr> + Si
     }
 
     #[tracing::instrument(level = "trace", skip_all)]
+    /// Get the length member of an array/list
     fn match_length(
         &mut self,
         ctx: ContextNode,
diff --git a/crates/solc-expressions/src/member_access/member_trait.rs b/crates/solc-expressions/src/member_access/member_trait.rs
index 370114ff..fd8c7129 100644
--- a/crates/solc-expressions/src/member_access/member_trait.rs
+++ b/crates/solc-expressions/src/member_access/member_trait.rs
@@ -18,6 +18,8 @@ impl<T> MemberAccessParts for T where
     T: BuiltinAccess + ContractAccess + EnumAccess + ListAccess + StructAccess
 {
 }
+
+/// Supertrait that coalesces various member access traits
 pub trait MemberAccessParts:
     BuiltinAccess + ContractAccess + EnumAccess + ListAccess + StructAccess
 {
@@ -27,85 +29,12 @@ impl<T> MemberAccess for T where
     T: MemberAccessParts + AnalyzerBackend<Expr = Expression, ExprErr = ExprErr> + Sized
 {
 }
+
+/// Toplevel trait for performing member access. Utilizes other `..Access` traits
 pub trait MemberAccess:
     MemberAccessParts + AnalyzerBackend<Expr = Expression, ExprErr = ExprErr> + Sized
 {
-    fn visible_member_funcs(
-        &mut self,
-        ctx: ContextNode,
-        loc: Loc,
-        member_idx: NodeIdx,
-    ) -> Result<Vec<FunctionNode>, ExprErr> {
-        let res = match self.node(member_idx) {
-            Node::ContextVar(cvar) => match &cvar.ty {
-                VarType::User(TypeNode::Contract(con_node), _) => {
-                    let mut funcs = con_node.linearized_functions(self);
-                    self
-                    .possible_library_funcs(ctx, con_node.0.into())
-                    .into_iter()
-                    .for_each(|func| {
-                        let name = func.name(self).unwrap();
-                        funcs.entry(name).or_insert(func);
-                    });
-                    funcs.values().copied().collect()
-                },
-                VarType::BuiltIn(bn, _) => self
-                    .possible_library_funcs(ctx, bn.0.into())
-                    .into_iter()
-                    .collect::<Vec<_>>(),
-                VarType::Concrete(cnode) => {
-                    let b = cnode.underlying(self).unwrap().as_builtin();
-                    let bn = self.builtin_or_add(b);
-                    self.possible_library_funcs(ctx, bn)
-                        .into_iter()
-                        .collect::<Vec<_>>()
-                }
-                VarType::User(TypeNode::Struct(sn), _) => self
-                    .possible_library_funcs(ctx, sn.0.into())
-                    .into_iter()
-                    .collect::<Vec<_>>(),
-                VarType::User(TypeNode::Enum(en), _) => self
-                    .possible_library_funcs(ctx, en.0.into())
-                    .into_iter()
-                    .collect::<Vec<_>>(),
-                VarType::User(TypeNode::Ty(ty), _) => self
-                    .possible_library_funcs(ctx, ty.0.into())
-                    .into_iter()
-                    .collect::<Vec<_>>(),
-                VarType::User(TypeNode::Func(func_node), _) => self
-                    .possible_library_funcs(ctx, func_node.0.into())
-                    .into_iter()
-                    .collect::<Vec<_>>(),
-                VarType::User(TypeNode::Unresolved(n), _) => {
-                    match self.node(*n) {
-                        Node::Unresolved(ident) => {
-                            return Err(ExprErr::Unresolved(loc, format!("The type \"{}\" is currently unresolved but should have been resolved by now. This is a bug.", ident.name)))
-                        }
-                        _ => unreachable!()
-                    }
-                }
-            },
-            Node::Contract(_) => ContractNode::from(member_idx).funcs(self),
-            Node::Concrete(_)
-            | Node::Ty(_)
-            | Node::Struct(_)
-            | Node::Function(_)
-            | Node::Enum(_)
-            | Node::Builtin(_) => self
-                .possible_library_funcs(ctx, member_idx)
-                .into_iter()
-                .collect::<Vec<_>>(),
-            e => {
-                return Err(ExprErr::MemberAccessNotFound(
-                    loc,
-                    format!("This type cannot have member functions: {:?}", e),
-                ))
-            }
-        };
-        Ok(res)
-    }
-
-    /// Gets the array type
+    /// Entry function for perform a member access
     #[tracing::instrument(level = "trace", skip_all)]
     fn member_access(
         &mut self,
@@ -135,6 +64,7 @@ pub trait MemberAccess:
         })
     }
 
+    /// Match on [`ExprRet`]s and call the member access for each
     fn match_member(
         &mut self,
         ctx: ContextNode,
@@ -156,6 +86,7 @@ pub trait MemberAccess:
         }
     }
 
+    /// Perform the member access
     fn member_access_inner(
         &mut self,
         loc: Loc,
@@ -201,6 +132,83 @@ pub trait MemberAccess:
         }
     }
 
+    /// Get visible functions for this member
+    fn visible_member_funcs(
+        &mut self,
+        ctx: ContextNode,
+        loc: Loc,
+        member_idx: NodeIdx,
+    ) -> Result<Vec<FunctionNode>, ExprErr> {
+        let res = match self.node(member_idx) {
+            Node::ContextVar(cvar) => match &cvar.ty {
+                VarType::User(TypeNode::Contract(con_node), _) => {
+                    let mut funcs = con_node.linearized_functions(self);
+                    self
+                    .possible_library_funcs(ctx, con_node.0.into())
+                    .into_iter()
+                    .for_each(|func| {
+                        let name = func.name(self).unwrap();
+                        funcs.entry(name).or_insert(func);
+                    });
+                    funcs.values().copied().collect()
+                },
+                VarType::BuiltIn(bn, _) => self
+                    .possible_library_funcs(ctx, bn.0.into())
+                    .into_iter()
+                    .collect::<Vec<_>>(),
+                VarType::Concrete(cnode) => {
+                    let b = cnode.underlying(self).unwrap().as_builtin();
+                    let bn = self.builtin_or_add(b);
+                    self.possible_library_funcs(ctx, bn)
+                        .into_iter()
+                        .collect::<Vec<_>>()
+                }
+                VarType::User(TypeNode::Struct(sn), _) => self
+                    .possible_library_funcs(ctx, sn.0.into())
+                    .into_iter()
+                    .collect::<Vec<_>>(),
+                VarType::User(TypeNode::Enum(en), _) => self
+                    .possible_library_funcs(ctx, en.0.into())
+                    .into_iter()
+                    .collect::<Vec<_>>(),
+                VarType::User(TypeNode::Ty(ty), _) => self
+                    .possible_library_funcs(ctx, ty.0.into())
+                    .into_iter()
+                    .collect::<Vec<_>>(),
+                VarType::User(TypeNode::Func(func_node), _) => self
+                    .possible_library_funcs(ctx, func_node.0.into())
+                    .into_iter()
+                    .collect::<Vec<_>>(),
+                VarType::User(TypeNode::Unresolved(n), _) => {
+                    match self.node(*n) {
+                        Node::Unresolved(ident) => {
+                            return Err(ExprErr::Unresolved(loc, format!("The type \"{}\" is currently unresolved but should have been resolved by now. This is a bug.", ident.name)))
+                        }
+                        _ => unreachable!()
+                    }
+                }
+            },
+            Node::Contract(_) => ContractNode::from(member_idx).funcs(self),
+            Node::Concrete(_)
+            | Node::Ty(_)
+            | Node::Struct(_)
+            | Node::Function(_)
+            | Node::Enum(_)
+            | Node::Builtin(_) => self
+                .possible_library_funcs(ctx, member_idx)
+                .into_iter()
+                .collect::<Vec<_>>(),
+            e => {
+                return Err(ExprErr::MemberAccessNotFound(
+                    loc,
+                    format!("This type cannot have member functions: {:?}", e),
+                ))
+            }
+        };
+        Ok(res)
+    }
+
+    /// Perform member access for a variable type
     fn member_access_var_ty(
         &mut self,
         cvar: ContextVar,
@@ -251,6 +259,7 @@ pub trait MemberAccess:
         }
     }
 
+    /// Perform a `TyNode` member access
     fn ty_member_access(
         &mut self,
         _member_idx: NodeIdx,
diff --git a/crates/solc-expressions/src/member_access/mod.rs b/crates/solc-expressions/src/member_access/mod.rs
index f86ca279..59766091 100644
--- a/crates/solc-expressions/src/member_access/mod.rs
+++ b/crates/solc-expressions/src/member_access/mod.rs
@@ -1,3 +1,6 @@
+//! This module consists of traits & blanket implementations that facilitate performing member access operations
+//! like `MyStruct.field` or `MyContract.myFunc`
+
 mod builtin_access;
 mod contract_access;
 mod enum_access;
diff --git a/crates/solc-expressions/src/member_access/struct_access.rs b/crates/solc-expressions/src/member_access/struct_access.rs
index a17eed14..41d252a5 100644
--- a/crates/solc-expressions/src/member_access/struct_access.rs
+++ b/crates/solc-expressions/src/member_access/struct_access.rs
@@ -12,9 +12,11 @@ impl<T> StructAccess for T where
     T: LibraryAccess + AnalyzerBackend<Expr = Expression, ExprErr = ExprErr> + Sized
 {
 }
+/// Trait for performing member accesses on Structs
 pub trait StructAccess:
     LibraryAccess + AnalyzerBackend<Expr = Expression, ExprErr = ExprErr> + Sized
 {
+    /// Perform member access on a struct
     fn struct_member_access(
         &mut self,
         member_idx: NodeIdx,
diff --git a/crates/solc-expressions/src/require.rs b/crates/solc-expressions/src/require.rs
index 01fc710e..e51f0b9e 100644
--- a/crates/solc-expressions/src/require.rs
+++ b/crates/solc-expressions/src/require.rs
@@ -19,6 +19,8 @@ use solang_parser::{
 use std::cmp::Ordering;
 
 impl<T> Require for T where T: Variable + BinOp + Sized + AnalyzerBackend {}
+
+/// Deals with require and assert statements, as well as adjusts bounds for variables
 pub trait Require: AnalyzerBackend + Variable + BinOp + Sized {
     /// Inverts a comparator expression
     fn inverse_expr(&self, expr: Expression) -> Expression {
@@ -582,6 +584,7 @@ pub trait Require: AnalyzerBackend + Variable + BinOp + Sized {
         }
     }
 
+    /// Do matching on [`ExprRet`]s to actually perform the require statement evaluation
     fn handle_require_inner(
         &mut self,
         ctx: ContextNode,
diff --git a/crates/solc-expressions/src/variable.rs b/crates/solc-expressions/src/variable.rs
index ba91ba49..f4640931 100644
--- a/crates/solc-expressions/src/variable.rs
+++ b/crates/solc-expressions/src/variable.rs
@@ -8,9 +8,10 @@ use graph::{
 use solang_parser::pt::{Expression, Identifier};
 
 impl<T> Variable for T where T: AnalyzerBackend<Expr = Expression, ExprErr = ExprErr> + Sized {}
-
+/// Deals with variable retrieval 
 pub trait Variable: AnalyzerBackend<Expr = Expression, ExprErr = ExprErr> + Sized {
     #[tracing::instrument(level = "trace", skip_all)]
+    /// Get a variable based on an identifier
     fn variable(
         &mut self,
         ident: &Identifier,
diff --git a/crates/solc-expressions/src/yul/mod.rs b/crates/solc-expressions/src/yul/mod.rs
index a6eb5228..aa6f8d1a 100644
--- a/crates/solc-expressions/src/yul/mod.rs
+++ b/crates/solc-expressions/src/yul/mod.rs
@@ -1,380 +1,8 @@
-use crate::{ContextBuilder, ExprErr, ExprParser, IntoExprErr};
-
-use graph::{
-    nodes::{Builtin, Context, ContextNode, ContextVar, ContextVarNode, ExprRet},
-    AnalyzerBackend, ContextEdge, Edge, Node, VarType,
-};
-
-use solang_parser::{
-    helpers::CodeLocation,
-    pt::{Expression, Loc, YulExpression, YulFor, YulStatement, YulSwitch},
-};
+//! Traits and blanket implementations for parsing with yul statements and expressions
 
+mod yul_builder;
 mod yul_cond_op;
-pub use yul_cond_op::*;
-
 mod yul_funcs;
-pub use yul_funcs::*;
-
-impl<T> YulBuilder for T where
-    T: AnalyzerBackend<Expr = Expression, ExprErr = ExprErr> + Sized + ExprParser
-{
-}
-pub trait YulBuilder:
-    AnalyzerBackend<Expr = Expression, ExprErr = ExprErr> + Sized + ExprParser
-{
-    #[tracing::instrument(level = "trace", skip_all, fields(ctx = %ctx.path(self)))]
-    fn parse_ctx_yul_statement(&mut self, stmt: &YulStatement, ctx: ContextNode)
-    where
-        Self: Sized,
-    {
-        if let Some(true) = self.add_if_err(ctx.is_ended(self).into_expr_err(stmt.loc())) {
-            return;
-        }
-        if let Some(live_edges) = self.add_if_err(ctx.live_edges(self).into_expr_err(stmt.loc())) {
-            if live_edges.is_empty() {
-                self.parse_ctx_yul_stmt_inner(stmt, ctx)
-            } else {
-                live_edges.iter().for_each(|fork_ctx| {
-                    self.parse_ctx_yul_stmt_inner(stmt, *fork_ctx);
-                });
-            }
-        }
-    }
-
-    #[tracing::instrument(level = "trace", skip_all)]
-    fn parse_ctx_yul_stmt_inner(&mut self, stmt: &YulStatement, ctx: ContextNode)
-    where
-        Self: Sized,
-    {
-        use YulStatement::*;
-        // println!("ctx: {}, yul stmt: {:?}", ctx.path(self), stmt);
-
-        let res = ctx
-            .pop_expr_latest(stmt.loc(), self)
-            .into_expr_err(stmt.loc());
-        let _ = self.add_if_err(res);
-
-        if ctx.is_killed(self).unwrap() {
-            return;
-        }
-        let ret = self.apply_to_edges(ctx, stmt.loc(), &|analyzer, ctx, _loc| {
-            match stmt {
-                Assign(loc, yul_exprs, yul_expr) => {
-                    match yul_exprs
-                        .iter()
-                        .try_for_each(|expr| analyzer.parse_ctx_yul_expr(expr, ctx))
-                    {
-                        Ok(()) => analyzer.apply_to_edges(ctx, *loc, &|analyzer, ctx, loc| {
-                            let Some(lhs_side) =
-                                ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)?
-                            else {
-                                return Err(ExprErr::NoLhs(
-                                    loc,
-                                    "No left hand side assignments in yul block".to_string(),
-                                ));
-                            };
-                            if matches!(lhs_side, ExprRet::CtxKilled(_)) {
-                                ctx.push_expr(lhs_side, analyzer).into_expr_err(loc)?;
-                                return Ok(());
-                            }
-
-                            analyzer.parse_ctx_yul_expr(yul_expr, ctx)?;
-                            analyzer.apply_to_edges(ctx, loc, &|analyzer, ctx, loc| {
-                                let Some(rhs_side) =
-                                    ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)?
-                                else {
-                                    return Err(ExprErr::NoRhs(
-                                        loc,
-                                        "No right hand side assignments in yul block".to_string(),
-                                    ));
-                                };
-
-                                if matches!(rhs_side, ExprRet::CtxKilled(_)) {
-                                    ctx.push_expr(rhs_side, analyzer).into_expr_err(loc)?;
-                                    return Ok(());
-                                }
-
-                                analyzer.match_assign_sides(ctx, loc, &lhs_side, &rhs_side)
-                            })
-                        }),
-                        Err(e) => Err(e),
-                    }
-                }
-                VariableDeclaration(loc, yul_idents, maybe_yul_expr) => {
-                    let nodes = yul_idents
-                        .iter()
-                        .map(|ident| {
-                            let b_ty = analyzer.builtin_or_add(Builtin::Uint(256));
-                            let var = ContextVar {
-                                loc: Some(ident.loc),
-                                name: ident.id.name.clone(),
-                                display_name: ident.id.name.clone(),
-                                storage: None,
-                                is_tmp: false,
-                                tmp_of: None,
-                                is_symbolic: true,
-                                is_return: false,
-                                ty: VarType::try_from_idx(analyzer, b_ty).unwrap(),
-                            };
-                            let cvar =
-                                ContextVarNode::from(analyzer.add_node(Node::ContextVar(var)));
-                            ctx.add_var(cvar, analyzer).unwrap();
-                            analyzer.add_edge(cvar, ctx, Edge::Context(ContextEdge::Variable));
-                            analyzer.advance_var_in_ctx(cvar, *loc, ctx).unwrap()
-                        })
-                        .collect::<Vec<_>>();
-
-                    if let Some(yul_expr) = maybe_yul_expr {
-                        analyzer.parse_ctx_yul_expr(yul_expr, ctx)?;
-                        analyzer.apply_to_edges(ctx, *loc, &|analyzer, ctx, loc| {
-                            let Some(ret) =
-                                ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)?
-                            else {
-                                return Err(ExprErr::NoRhs(
-                                    loc,
-                                    "No right hand side assignments in yul block".to_string(),
-                                ));
-                            };
-
-                            if matches!(ret, ExprRet::CtxKilled(_)) {
-                                ctx.push_expr(ret, analyzer).into_expr_err(loc)?;
-                                return Ok(());
-                            }
-
-                            analyzer.match_assign_yul(ctx, loc, &nodes, ret)
-                        })
-                    } else {
-                        Ok(())
-                    }
-                }
-                If(loc, yul_expr, yul_block) => {
-                    analyzer.apply_to_edges(ctx, *loc, &|analyzer, ctx, loc| {
-                        let ret = analyzer.yul_cond_op_stmt(loc, yul_expr, yul_block, ctx);
-                        let _ = analyzer.add_if_err(ret);
-                        Ok(())
-                    })
-                }
-                For(YulFor {
-                    loc,
-                    init_block: _,
-                    condition: _,
-                    post_block: _,
-                    execution_block: _,
-                }) => {
-                    let sctx =
-                        Context::new_subctx(ctx, None, *loc, None, None, false, analyzer, None)
-                            .into_expr_err(*loc)?;
-                    let subctx = ContextNode::from(analyzer.add_node(Node::Context(sctx)));
-                    ctx.set_child_call(subctx, analyzer).into_expr_err(*loc)?;
-                    analyzer.apply_to_edges(subctx, *loc, &|analyzer, subctx, loc| {
-                        let vars = subctx.local_vars(analyzer).clone();
-                        vars.iter().for_each(|(name, var)| {
-                            // widen to max range
-                            if let Some(inheritor_var) = ctx.var_by_name(analyzer, name) {
-                                let inheritor_var = inheritor_var.latest_version(analyzer);
-                                if let Some(r) = var
-                                    .underlying(analyzer)
-                                    .unwrap()
-                                    .ty
-                                    .default_range(analyzer)
-                                    .unwrap()
-                                {
-                                    let new_inheritor_var = analyzer
-                                        .advance_var_in_ctx(inheritor_var, loc, ctx)
-                                        .unwrap();
-                                    let res = new_inheritor_var
-                                        .set_range_min(analyzer, r.min)
-                                        .into_expr_err(loc);
-                                    let _ = analyzer.add_if_err(res);
-                                    let res = new_inheritor_var
-                                        .set_range_max(analyzer, r.max)
-                                        .into_expr_err(loc);
-                                    let _ = analyzer.add_if_err(res);
-                                }
-                            }
-                        });
-                        Ok(())
-                    })
-                }
-                Switch(YulSwitch {
-                    loc,
-                    condition,
-                    cases,
-                    default,
-                }) => analyzer.apply_to_edges(ctx, *loc, &|analyzer, ctx, loc| {
-                    analyzer.yul_switch_stmt(
-                        loc,
-                        condition.clone(),
-                        cases.to_vec(),
-                        default.clone(),
-                        ctx,
-                    )
-                }),
-                Leave(loc) => Err(ExprErr::Todo(
-                    *loc,
-                    "Yul `leave` statements are not currently supported".to_string(),
-                )),
-                Break(loc) => Err(ExprErr::Todo(
-                    *loc,
-                    "Yul `break` statements are not currently supported".to_string(),
-                )),
-                Continue(loc) => Err(ExprErr::Todo(
-                    *loc,
-                    "Yul `continue` statements are not currently supported".to_string(),
-                )),
-                Block(yul_block) => {
-                    yul_block
-                        .statements
-                        .iter()
-                        .for_each(|stmt| analyzer.parse_ctx_yul_stmt_inner(stmt, ctx));
-                    Ok(())
-                }
-                FunctionDefinition(yul_func_def) => Err(ExprErr::Todo(
-                    yul_func_def.loc(),
-                    "Yul `function` defintions are not currently supported".to_string(),
-                )),
-                FunctionCall(yul_func_call) => analyzer.yul_func_call(yul_func_call, ctx),
-                Error(loc) => Err(ExprErr::ParseError(
-                    *loc,
-                    "Could not parse this yul statement".to_string(),
-                )),
-            }
-        });
-        let _ = self.add_if_err(ret);
-    }
-
-    #[tracing::instrument(level = "trace", skip_all)]
-    fn parse_ctx_yul_expr(
-        &mut self,
-        expr: &YulExpression,
-        ctx: ContextNode,
-    ) -> Result<(), ExprErr> {
-        tracing::trace!("Parsing yul expression: {expr:?}");
-
-        let edges = ctx.live_edges(self).into_expr_err(expr.loc())?;
-        if edges.is_empty() {
-            self.parse_ctx_yul_expr_inner(expr, ctx)
-        } else {
-            edges
-                .iter()
-                .try_for_each(|fork_ctx| self.parse_ctx_yul_expr(expr, *fork_ctx))?;
-            Ok(())
-        }
-    }
-
-    fn parse_ctx_yul_expr_inner(
-        &mut self,
-        expr: &YulExpression,
-        ctx: ContextNode,
-    ) -> Result<(), ExprErr> {
-        use YulExpression::*;
-        match expr {
-            BoolLiteral(loc, b, _) => self.bool_literal(ctx, *loc, *b),
-            NumberLiteral(loc, int, expr, _unit) => {
-                self.number_literal(ctx, *loc, int, expr, false)
-            }
-            HexNumberLiteral(loc, b, _unit) => self.hex_num_literal(ctx, *loc, b, false),
-            HexStringLiteral(lit, _) => self.hex_literals(ctx, &[lit.clone()]),
-            StringLiteral(lit, _) => self.string_literal(ctx, lit.loc, &lit.string),
-            Variable(ident) => {
-                self.variable(ident, ctx, None)?;
-                self.apply_to_edges(ctx, ident.loc, &|analyzer, edge_ctx, loc| {
-                    if let Some(ret) = edge_ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? {
-                        if ContextVarNode::from(ret.expect_single().into_expr_err(loc)?)
-                            .is_memory(analyzer)
-                            .into_expr_err(loc)?
-                        {
-                            // its a memory based variable, push a uint instead
-                            let b = Builtin::Uint(256);
-                            let var = ContextVar::new_from_builtin(
-                                loc,
-                                analyzer.builtin_or_add(b).into(),
-                                analyzer,
-                            )
-                            .into_expr_err(loc)?;
-                            let node = analyzer.add_node(Node::ContextVar(var));
-                            edge_ctx
-                                .push_expr(ExprRet::Single(node), analyzer)
-                                .into_expr_err(loc)
-                        } else {
-                            edge_ctx.push_expr(ret, analyzer).into_expr_err(loc)
-                        }
-                    } else {
-                        Err(ExprErr::Unresolved(
-                            ident.loc,
-                            format!("Could not find variable with name: {}", ident.name),
-                        ))
-                    }
-                })
-            }
-            FunctionCall(yul_func_call) => self.yul_func_call(yul_func_call, ctx),
-            SuffixAccess(_loc, _yul_member_expr, _ident) => Err(ExprErr::Todo(
-                expr.loc(),
-                "Yul member access not yet supported".to_string(),
-            )),
-        }
-    }
-
-    fn match_assign_yul(
-        &mut self,
-        _ctx: ContextNode,
-        loc: Loc,
-        nodes: &[ContextVarNode],
-        ret: ExprRet,
-    ) -> Result<(), ExprErr> {
-        match ret {
-            s @ ExprRet::Single(_) | s @ ExprRet::SingleLiteral(_) => {
-                self.match_assign_yul_inner(loc, &nodes[0], s)?;
-            }
-            ExprRet::Multi(inner) => {
-                if inner.len() == nodes.len() {
-                    inner
-                        .into_iter()
-                        .zip(nodes.iter())
-                        .map(|(ret, node)| self.match_assign_yul_inner(loc, node, ret))
-                        .collect::<Result<Vec<_>, ExprErr>>()?;
-                } else {
-                    return Err(ExprErr::Todo(
-                        loc,
-                        format!("Differing number of assignees and assignors in yul expression, assignors: {}, assignees: {}", nodes.len(), inner.len()),
-                    ));
-                };
-            }
-            ExprRet::CtxKilled(_kind) => {}
-            ExprRet::Null => {}
-        }
-
-        Ok(())
-    }
-
-    fn match_assign_yul_inner(
-        &mut self,
-        loc: Loc,
-        node: &ContextVarNode,
-        ret: ExprRet,
-    ) -> Result<(), ExprErr> {
-        match ret.flatten() {
-            ExprRet::Single(idx) | ExprRet::SingleLiteral(idx) => {
-                let assign = ContextVarNode::from(idx);
-                let assign_ty = assign.underlying(self).into_expr_err(loc)?.ty.clone();
-                if assign_ty.is_dyn(self).into_expr_err(loc)? {
-                    let b_ty = self.builtin_or_add(Builtin::Bytes(32));
-                    node.underlying_mut(self).into_expr_err(loc)?.ty =
-                        VarType::try_from_idx(self, b_ty).unwrap();
-                } else {
-                    node.underlying_mut(self).into_expr_err(loc)?.ty = assign_ty;
-                }
-            }
-            ExprRet::Multi(_inner) => {
-                return Err(ExprErr::Todo(
-                    loc,
-                    "Multi in single assignment yul expression is unhandled".to_string(),
-                ))
-            }
-            ExprRet::CtxKilled(..) => {}
-            ExprRet::Null => {}
-        }
-        Ok(())
-    }
-}
+pub use yul_builder::*;
+pub use yul_cond_op::*;
+pub use yul_funcs::*;
\ No newline at end of file
diff --git a/crates/solc-expressions/src/yul/yul_builder.rs b/crates/solc-expressions/src/yul/yul_builder.rs
new file mode 100644
index 00000000..652ed9b2
--- /dev/null
+++ b/crates/solc-expressions/src/yul/yul_builder.rs
@@ -0,0 +1,388 @@
+//! Trait and blanket implementation for parsing yul-based statements and expressions
+
+use crate::{
+	ContextBuilder, ExprErr, ExprParser, IntoExprErr,
+	yul::YulFuncCaller,
+	yul::YulCondOp
+};
+
+use graph::{
+    nodes::{Builtin, Context, ContextNode, ContextVar, ContextVarNode, ExprRet},
+    AnalyzerBackend, ContextEdge, Edge, Node, VarType,
+};
+
+use solang_parser::{
+    helpers::CodeLocation,
+    pt::{Expression, Loc, YulExpression, YulFor, YulStatement, YulSwitch},
+};
+
+
+impl<T> YulBuilder for T where
+    T: AnalyzerBackend<Expr = Expression, ExprErr = ExprErr> + Sized + ExprParser
+{
+}
+/// Trait that processes Yul statements and expressions
+pub trait YulBuilder:
+    AnalyzerBackend<Expr = Expression, ExprErr = ExprErr> + Sized + ExprParser
+{
+    #[tracing::instrument(level = "trace", skip_all, fields(ctx = %ctx.path(self)))]
+    /// Parse a yul statement
+    fn parse_ctx_yul_statement(&mut self, stmt: &YulStatement, ctx: ContextNode)
+    where
+        Self: Sized,
+    {
+        if let Some(true) = self.add_if_err(ctx.is_ended(self).into_expr_err(stmt.loc())) {
+            return;
+        }
+        if let Some(live_edges) = self.add_if_err(ctx.live_edges(self).into_expr_err(stmt.loc())) {
+            if live_edges.is_empty() {
+                self.parse_ctx_yul_stmt_inner(stmt, ctx)
+            } else {
+                live_edges.iter().for_each(|fork_ctx| {
+                    self.parse_ctx_yul_stmt_inner(stmt, *fork_ctx);
+                });
+            }
+        }
+    }
+
+    #[tracing::instrument(level = "trace", skip_all)]
+    /// After doing some setup in `parse_ctx_yul_statement`, actually parse a yul statement
+    fn parse_ctx_yul_stmt_inner(&mut self, stmt: &YulStatement, ctx: ContextNode)
+    where
+        Self: Sized,
+    {
+        use YulStatement::*;
+        // println!("ctx: {}, yul stmt: {:?}", ctx.path(self), stmt);
+
+        let res = ctx
+            .pop_expr_latest(stmt.loc(), self)
+            .into_expr_err(stmt.loc());
+        let _ = self.add_if_err(res);
+
+        if ctx.is_killed(self).unwrap() {
+            return;
+        }
+        let ret = self.apply_to_edges(ctx, stmt.loc(), &|analyzer, ctx, _loc| {
+            match stmt {
+                Assign(loc, yul_exprs, yul_expr) => {
+                    match yul_exprs
+                        .iter()
+                        .try_for_each(|expr| analyzer.parse_ctx_yul_expr(expr, ctx))
+                    {
+                        Ok(()) => analyzer.apply_to_edges(ctx, *loc, &|analyzer, ctx, loc| {
+                            let Some(lhs_side) =
+                                ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)?
+                            else {
+                                return Err(ExprErr::NoLhs(
+                                    loc,
+                                    "No left hand side assignments in yul block".to_string(),
+                                ));
+                            };
+                            if matches!(lhs_side, ExprRet::CtxKilled(_)) {
+                                ctx.push_expr(lhs_side, analyzer).into_expr_err(loc)?;
+                                return Ok(());
+                            }
+
+                            analyzer.parse_ctx_yul_expr(yul_expr, ctx)?;
+                            analyzer.apply_to_edges(ctx, loc, &|analyzer, ctx, loc| {
+                                let Some(rhs_side) =
+                                    ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)?
+                                else {
+                                    return Err(ExprErr::NoRhs(
+                                        loc,
+                                        "No right hand side assignments in yul block".to_string(),
+                                    ));
+                                };
+
+                                if matches!(rhs_side, ExprRet::CtxKilled(_)) {
+                                    ctx.push_expr(rhs_side, analyzer).into_expr_err(loc)?;
+                                    return Ok(());
+                                }
+
+                                analyzer.match_assign_sides(ctx, loc, &lhs_side, &rhs_side)
+                            })
+                        }),
+                        Err(e) => Err(e),
+                    }
+                }
+                VariableDeclaration(loc, yul_idents, maybe_yul_expr) => {
+                    let nodes = yul_idents
+                        .iter()
+                        .map(|ident| {
+                            let b_ty = analyzer.builtin_or_add(Builtin::Uint(256));
+                            let var = ContextVar {
+                                loc: Some(ident.loc),
+                                name: ident.id.name.clone(),
+                                display_name: ident.id.name.clone(),
+                                storage: None,
+                                is_tmp: false,
+                                tmp_of: None,
+                                is_symbolic: true,
+                                is_return: false,
+                                ty: VarType::try_from_idx(analyzer, b_ty).unwrap(),
+                            };
+                            let cvar =
+                                ContextVarNode::from(analyzer.add_node(Node::ContextVar(var)));
+                            ctx.add_var(cvar, analyzer).unwrap();
+                            analyzer.add_edge(cvar, ctx, Edge::Context(ContextEdge::Variable));
+                            analyzer.advance_var_in_ctx(cvar, *loc, ctx).unwrap()
+                        })
+                        .collect::<Vec<_>>();
+
+                    if let Some(yul_expr) = maybe_yul_expr {
+                        analyzer.parse_ctx_yul_expr(yul_expr, ctx)?;
+                        analyzer.apply_to_edges(ctx, *loc, &|analyzer, ctx, loc| {
+                            let Some(ret) =
+                                ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)?
+                            else {
+                                return Err(ExprErr::NoRhs(
+                                    loc,
+                                    "No right hand side assignments in yul block".to_string(),
+                                ));
+                            };
+
+                            if matches!(ret, ExprRet::CtxKilled(_)) {
+                                ctx.push_expr(ret, analyzer).into_expr_err(loc)?;
+                                return Ok(());
+                            }
+
+                            analyzer.match_assign_yul(ctx, loc, &nodes, ret)
+                        })
+                    } else {
+                        Ok(())
+                    }
+                }
+                If(loc, yul_expr, yul_block) => {
+                    analyzer.apply_to_edges(ctx, *loc, &|analyzer, ctx, loc| {
+                        let ret = analyzer.yul_cond_op_stmt(loc, yul_expr, yul_block, ctx);
+                        let _ = analyzer.add_if_err(ret);
+                        Ok(())
+                    })
+                }
+                For(YulFor {
+                    loc,
+                    init_block: _,
+                    condition: _,
+                    post_block: _,
+                    execution_block: _,
+                }) => {
+                    let sctx =
+                        Context::new_subctx(ctx, None, *loc, None, None, false, analyzer, None)
+                            .into_expr_err(*loc)?;
+                    let subctx = ContextNode::from(analyzer.add_node(Node::Context(sctx)));
+                    ctx.set_child_call(subctx, analyzer).into_expr_err(*loc)?;
+                    analyzer.apply_to_edges(subctx, *loc, &|analyzer, subctx, loc| {
+                        let vars = subctx.local_vars(analyzer).clone();
+                        vars.iter().for_each(|(name, var)| {
+                            // widen to max range
+                            if let Some(inheritor_var) = ctx.var_by_name(analyzer, name) {
+                                let inheritor_var = inheritor_var.latest_version(analyzer);
+                                if let Some(r) = var
+                                    .underlying(analyzer)
+                                    .unwrap()
+                                    .ty
+                                    .default_range(analyzer)
+                                    .unwrap()
+                                {
+                                    let new_inheritor_var = analyzer
+                                        .advance_var_in_ctx(inheritor_var, loc, ctx)
+                                        .unwrap();
+                                    let res = new_inheritor_var
+                                        .set_range_min(analyzer, r.min)
+                                        .into_expr_err(loc);
+                                    let _ = analyzer.add_if_err(res);
+                                    let res = new_inheritor_var
+                                        .set_range_max(analyzer, r.max)
+                                        .into_expr_err(loc);
+                                    let _ = analyzer.add_if_err(res);
+                                }
+                            }
+                        });
+                        Ok(())
+                    })
+                }
+                Switch(YulSwitch {
+                    loc,
+                    condition,
+                    cases,
+                    default,
+                }) => analyzer.apply_to_edges(ctx, *loc, &|analyzer, ctx, loc| {
+                    analyzer.yul_switch_stmt(
+                        loc,
+                        condition.clone(),
+                        cases.to_vec(),
+                        default.clone(),
+                        ctx,
+                    )
+                }),
+                Leave(loc) => Err(ExprErr::Todo(
+                    *loc,
+                    "Yul `leave` statements are not currently supported".to_string(),
+                )),
+                Break(loc) => Err(ExprErr::Todo(
+                    *loc,
+                    "Yul `break` statements are not currently supported".to_string(),
+                )),
+                Continue(loc) => Err(ExprErr::Todo(
+                    *loc,
+                    "Yul `continue` statements are not currently supported".to_string(),
+                )),
+                Block(yul_block) => {
+                    yul_block
+                        .statements
+                        .iter()
+                        .for_each(|stmt| analyzer.parse_ctx_yul_stmt_inner(stmt, ctx));
+                    Ok(())
+                }
+                FunctionDefinition(yul_func_def) => Err(ExprErr::Todo(
+                    yul_func_def.loc(),
+                    "Yul `function` defintions are not currently supported".to_string(),
+                )),
+                FunctionCall(yul_func_call) => analyzer.yul_func_call(yul_func_call, ctx),
+                Error(loc) => Err(ExprErr::ParseError(
+                    *loc,
+                    "Could not parse this yul statement".to_string(),
+                )),
+            }
+        });
+        let _ = self.add_if_err(ret);
+    }
+
+    #[tracing::instrument(level = "trace", skip_all)]
+    /// Parse a yul expression
+    fn parse_ctx_yul_expr(
+        &mut self,
+        expr: &YulExpression,
+        ctx: ContextNode,
+    ) -> Result<(), ExprErr> {
+        tracing::trace!("Parsing yul expression: {expr:?}");
+
+        let edges = ctx.live_edges(self).into_expr_err(expr.loc())?;
+        if edges.is_empty() {
+            self.parse_ctx_yul_expr_inner(expr, ctx)
+        } else {
+            edges
+                .iter()
+                .try_for_each(|fork_ctx| self.parse_ctx_yul_expr(expr, *fork_ctx))?;
+            Ok(())
+        }
+    }
+
+    /// After performing some setup in `parse_ctx_yul_expr`, actually parse the yul expression
+    fn parse_ctx_yul_expr_inner(
+        &mut self,
+        expr: &YulExpression,
+        ctx: ContextNode,
+    ) -> Result<(), ExprErr> {
+        use YulExpression::*;
+        match expr {
+            BoolLiteral(loc, b, _) => self.bool_literal(ctx, *loc, *b),
+            NumberLiteral(loc, int, expr, _unit) => {
+                self.number_literal(ctx, *loc, int, expr, false)
+            }
+            HexNumberLiteral(loc, b, _unit) => self.hex_num_literal(ctx, *loc, b, false),
+            HexStringLiteral(lit, _) => self.hex_literals(ctx, &[lit.clone()]),
+            StringLiteral(lit, _) => self.string_literal(ctx, lit.loc, &lit.string),
+            Variable(ident) => {
+                self.variable(ident, ctx, None)?;
+                self.apply_to_edges(ctx, ident.loc, &|analyzer, edge_ctx, loc| {
+                    if let Some(ret) = edge_ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? {
+                        if ContextVarNode::from(ret.expect_single().into_expr_err(loc)?)
+                            .is_memory(analyzer)
+                            .into_expr_err(loc)?
+                        {
+                            // its a memory based variable, push a uint instead
+                            let b = Builtin::Uint(256);
+                            let var = ContextVar::new_from_builtin(
+                                loc,
+                                analyzer.builtin_or_add(b).into(),
+                                analyzer,
+                            )
+                            .into_expr_err(loc)?;
+                            let node = analyzer.add_node(Node::ContextVar(var));
+                            edge_ctx
+                                .push_expr(ExprRet::Single(node), analyzer)
+                                .into_expr_err(loc)
+                        } else {
+                            edge_ctx.push_expr(ret, analyzer).into_expr_err(loc)
+                        }
+                    } else {
+                        Err(ExprErr::Unresolved(
+                            ident.loc,
+                            format!("Could not find variable with name: {}", ident.name),
+                        ))
+                    }
+                })
+            }
+            FunctionCall(yul_func_call) => self.yul_func_call(yul_func_call, ctx),
+            SuffixAccess(_loc, _yul_member_expr, _ident) => Err(ExprErr::Todo(
+                expr.loc(),
+                "Yul member access not yet supported".to_string(),
+            )),
+        }
+    }
+
+    /// Match [`ExprRet`] from the sides of an `YulAssign` to perform the assignment
+    fn match_assign_yul(
+        &mut self,
+        _ctx: ContextNode,
+        loc: Loc,
+        nodes: &[ContextVarNode],
+        ret: ExprRet,
+    ) -> Result<(), ExprErr> {
+        match ret {
+            s @ ExprRet::Single(_) | s @ ExprRet::SingleLiteral(_) => {
+                self.match_assign_yul_inner(loc, &nodes[0], s)?;
+            }
+            ExprRet::Multi(inner) => {
+                if inner.len() == nodes.len() {
+                    inner
+                        .into_iter()
+                        .zip(nodes.iter())
+                        .map(|(ret, node)| self.match_assign_yul_inner(loc, node, ret))
+                        .collect::<Result<Vec<_>, ExprErr>>()?;
+                } else {
+                    return Err(ExprErr::Todo(
+                        loc,
+                        format!("Differing number of assignees and assignors in yul expression, assignors: {}, assignees: {}", nodes.len(), inner.len()),
+                    ));
+                };
+            }
+            ExprRet::CtxKilled(_kind) => {}
+            ExprRet::Null => {}
+        }
+
+        Ok(())
+    }
+
+    /// Perform the actual yul assignment
+    fn match_assign_yul_inner(
+        &mut self,
+        loc: Loc,
+        node: &ContextVarNode,
+        ret: ExprRet,
+    ) -> Result<(), ExprErr> {
+        match ret.flatten() {
+            ExprRet::Single(idx) | ExprRet::SingleLiteral(idx) => {
+                let assign = ContextVarNode::from(idx);
+                let assign_ty = assign.underlying(self).into_expr_err(loc)?.ty.clone();
+                if assign_ty.is_dyn(self).into_expr_err(loc)? {
+                    let b_ty = self.builtin_or_add(Builtin::Bytes(32));
+                    node.underlying_mut(self).into_expr_err(loc)?.ty =
+                        VarType::try_from_idx(self, b_ty).unwrap();
+                } else {
+                    node.underlying_mut(self).into_expr_err(loc)?.ty = assign_ty;
+                }
+            }
+            ExprRet::Multi(_inner) => {
+                return Err(ExprErr::Todo(
+                    loc,
+                    "Multi in single assignment yul expression is unhandled".to_string(),
+                ))
+            }
+            ExprRet::CtxKilled(..) => {}
+            ExprRet::Null => {}
+        }
+        Ok(())
+    }
+}
diff --git a/crates/solc-expressions/src/yul/yul_cond_op.rs b/crates/solc-expressions/src/yul/yul_cond_op.rs
index 8faf7127..7f5f74a2 100644
--- a/crates/solc-expressions/src/yul/yul_cond_op.rs
+++ b/crates/solc-expressions/src/yul/yul_cond_op.rs
@@ -1,4 +1,4 @@
-use crate::{require::Require, ContextBuilder, ExprErr, IntoExprErr, YulBuilder};
+use crate::{require::Require, ContextBuilder, ExprErr, IntoExprErr, yul::YulBuilder};
 
 use graph::{
     elem::*,
@@ -17,10 +17,13 @@ impl<T> YulCondOp for T where
     T: AnalyzerBackend<Expr = Expression, ExprErr = ExprErr> + Require + Sized
 {
 }
+
+/// Trait for dealing with conditional operations in yul
 pub trait YulCondOp:
     AnalyzerBackend<Expr = Expression, ExprErr = ExprErr> + Require + Sized
 {
     #[tracing::instrument(level = "trace", skip_all)]
+    /// Handle a yul conditional operation statement
     fn yul_cond_op_stmt(
         &mut self,
         loc: Loc,
@@ -98,6 +101,7 @@ pub trait YulCondOp:
     }
 
     #[tracing::instrument(level = "trace", skip_all)]
+    /// Handle a yul if-else
     fn yul_if_else(
         &mut self,
         loc: Loc,
@@ -172,6 +176,7 @@ pub trait YulCondOp:
         })
     }
 
+    /// Helper for the `true` evaluation of a yul conditional
     fn match_yul_true(
         &mut self,
         ctx: ContextNode,
@@ -214,6 +219,7 @@ pub trait YulCondOp:
         Ok(())
     }
 
+    /// Helper for the `false` evaluation of a yul conditional
     fn match_yul_false(
         &mut self,
         ctx: ContextNode,
@@ -258,6 +264,7 @@ pub trait YulCondOp:
     }
 
     #[tracing::instrument(level = "trace", skip_all)]
+    /// Handle a yul swithc statement by converting it into an if-else chain
     fn yul_switch_stmt(
         &mut self,
         loc: Loc,
@@ -274,6 +281,7 @@ pub trait YulCondOp:
 }
 
 #[derive(Clone, Debug)]
+/// A yul-based if-else chain, which represents a switch statement
 pub struct IfElseChain {
     pub if_expr: YulExpression,
     pub true_stmt: YulStatement,
@@ -281,6 +289,7 @@ pub struct IfElseChain {
 }
 
 #[derive(Clone, Debug)]
+/// Wrapper over a switch statement that denotes either another else statement or the default case
 pub enum ElseOrDefault {
     Else(Box<IfElseChain>),
     Default(YulStatement),
diff --git a/crates/solc-expressions/src/yul/yul_funcs.rs b/crates/solc-expressions/src/yul/yul_funcs.rs
index 0d28f98e..922a8f89 100644
--- a/crates/solc-expressions/src/yul/yul_funcs.rs
+++ b/crates/solc-expressions/src/yul/yul_funcs.rs
@@ -1,4 +1,4 @@
-use crate::{BinOp, Cmp, ContextBuilder, Env, ExprErr, IntoExprErr, YulBuilder};
+use crate::{BinOp, Cmp, ContextBuilder, Env, ExprErr, IntoExprErr, yul::YulBuilder};
 
 use graph::{
     elem::*,