From 955dc6809233f961d322b8dd9c1f0d5714e78c1b Mon Sep 17 00:00:00 2001 From: brock elmore Date: Sun, 10 Dec 2023 19:55:31 -0800 Subject: [PATCH] rearchitect intrinsic calls --- .../src/func_call/intrinsic_call.rs | 1265 ----------------- .../src/func_call/intrinsic_call/abi.rs | 128 ++ .../src/func_call/intrinsic_call/address.rs | 79 + .../src/func_call/intrinsic_call/array.rs | 200 +++ .../src/func_call/intrinsic_call/block.rs | 55 + .../func_call/intrinsic_call/constructors.rs | 197 +++ .../func_call/intrinsic_call/dyn_builtin.rs | 212 +++ .../intrinsic_call/intrinsic_caller.rs | 154 ++ .../src/func_call/intrinsic_call/mod.rs | 23 + .../src/func_call/intrinsic_call/msg.rs | 39 + .../func_call/intrinsic_call/precompile.rs | 218 +++ .../src/func_call/intrinsic_call/solidity.rs | 91 ++ .../src/func_call/intrinsic_call/types.rs | 223 +++ 13 files changed, 1619 insertions(+), 1265 deletions(-) delete mode 100644 crates/solc-expressions/src/func_call/intrinsic_call.rs create mode 100644 crates/solc-expressions/src/func_call/intrinsic_call/abi.rs create mode 100644 crates/solc-expressions/src/func_call/intrinsic_call/address.rs create mode 100644 crates/solc-expressions/src/func_call/intrinsic_call/array.rs create mode 100644 crates/solc-expressions/src/func_call/intrinsic_call/block.rs create mode 100644 crates/solc-expressions/src/func_call/intrinsic_call/constructors.rs create mode 100644 crates/solc-expressions/src/func_call/intrinsic_call/dyn_builtin.rs create mode 100644 crates/solc-expressions/src/func_call/intrinsic_call/intrinsic_caller.rs create mode 100644 crates/solc-expressions/src/func_call/intrinsic_call/mod.rs create mode 100644 crates/solc-expressions/src/func_call/intrinsic_call/msg.rs create mode 100644 crates/solc-expressions/src/func_call/intrinsic_call/precompile.rs create mode 100644 crates/solc-expressions/src/func_call/intrinsic_call/solidity.rs create mode 100644 crates/solc-expressions/src/func_call/intrinsic_call/types.rs diff --git a/crates/solc-expressions/src/func_call/intrinsic_call.rs b/crates/solc-expressions/src/func_call/intrinsic_call.rs deleted file mode 100644 index a7b9d564..00000000 --- a/crates/solc-expressions/src/func_call/intrinsic_call.rs +++ /dev/null @@ -1,1265 +0,0 @@ -use crate::{ - array::Array, require::Require, ContextBuilder, ExprErr, FuncCaller, IntoExprErr, ListAccess, -}; - -use graph::{ - elem::*, - nodes::{ - BuiltInNode, Builtin, Concrete, Context, ContextNode, ContextVar, ContextVarNode, ExprRet, - StructNode, TyNode, - }, - AnalyzerBackend, ContextEdge, Edge, GraphBackend, Node, Range, SolcRange, VarType, -}; -use shared::{NodeIdx, Search}; - -use ethers_core::types::U256; -use solang_parser::pt::{Expression, Loc}; - -impl IntrinsicFuncCaller for T where - T: AnalyzerBackend + Sized + GraphBackend + Search -{ -} -pub trait IntrinsicFuncCaller: - AnalyzerBackend + Sized + GraphBackend + Search -{ - /// Calls an intrinsic/builtin function call (casts, require, etc.) - #[tracing::instrument(level = "trace", skip_all)] - fn intrinsic_func_call( - &mut self, - loc: &Loc, - input_exprs: &[Expression], - func_idx: NodeIdx, - ctx: ContextNode, - ) -> Result<(), ExprErr> { - match self.node(func_idx) { - Node::Function(underlying) => { - if let Some(func_name) = &underlying.name { - match &*func_name.name { - "abi.decode" => { - // we skip the first because that is what is being decoded. - // TODO: check if we have a concrete bytes value - fn match_decode( - ctx: ContextNode, - loc: &Loc, - ret: ExprRet, - analyzer: &mut impl AnalyzerBackend, - ) -> Result<(), ExprErr> { - match ret { - ExprRet::Single(expect_builtin) => { - match analyzer.node(expect_builtin) { - Node::Builtin(_) => { - let var = ContextVar::new_from_builtin( - *loc, - expect_builtin.into(), - analyzer, - ) - .into_expr_err(*loc)?; - 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), - ); - ctx.push_expr(ExprRet::Single(node), analyzer) - .into_expr_err(*loc)?; - Ok(()) - } - Node::ContextVar(cvar) => { - let bn = analyzer - .builtin_or_add( - cvar.ty - .as_builtin(analyzer) - .into_expr_err(*loc)?, - ) - .into(); - let var = ContextVar::new_from_builtin( - *loc, bn, analyzer, - ) - .into_expr_err(*loc)?; - 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), - ); - ctx.push_expr(ExprRet::Single(node), analyzer) - .into_expr_err(*loc)?; - Ok(()) - } - e => todo!("Unhandled type in abi.decode: {e:?}"), - } - } - ExprRet::Multi(inner) => inner.iter().try_for_each(|i| { - match_decode(ctx, loc, i.clone(), analyzer) - }), - ExprRet::CtxKilled(kind) => { - ctx.kill(analyzer, *loc, kind).into_expr_err(*loc) - } - e => panic!("This is invalid solidity: {:?}", e), - } - } - self.parse_ctx_expr(&input_exprs[1], 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::NoRhs( - loc, - "abi.decode was not given the types for decoding" - .to_string(), - )); - }; - if matches!(ret, ExprRet::CtxKilled(_)) { - ctx.push_expr(ret, analyzer).into_expr_err(loc)?; - return Ok(()); - } - match_decode(ctx, &loc, ret, analyzer) - }) - } - "abi.encode" - | "abi.encodePacked" - | "abi.encodeCall" - | "abi.encodeWithSignature" - | "abi.encodeWithSelector" => { - // currently we dont support concrete abi encoding, TODO - 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)); - ctx.add_var(node.into(), self).into_expr_err(*loc)?; - self.add_edge(node, ctx, Edge::Context(ContextEdge::Variable)); - ctx.push_expr(ExprRet::Single(node), self) - .into_expr_err(*loc)?; - Ok(()) - } - "delegatecall" | "staticcall" | "call" => { - // TODO: Check if we have the code for the address - // if we dont, model it as a unrestricted call that can make other calls - ctx.pop_expr_latest(*loc, self).into_expr_err(*loc)?; - // TODO: try to be smarter based on the address input - let booln = self.builtin_or_add(Builtin::Bool); - let bool_cvar = ContextVar::new_from_builtin(*loc, booln.into(), self) - .into_expr_err(*loc)?; - let bool_node = self.add_node(Node::ContextVar(bool_cvar)); - ctx.add_var(bool_node.into(), self).into_expr_err(*loc)?; - self.add_edge(bool_node, ctx, Edge::Context(ContextEdge::Variable)); - - 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)); - ctx.add_var(node.into(), self).into_expr_err(*loc)?; - self.add_edge(node, ctx, Edge::Context(ContextEdge::Variable)); - ctx.push_expr( - ExprRet::Multi(vec![ - ExprRet::Single(bool_node), - ExprRet::Single(node), - ]), - self, - ) - .into_expr_err(*loc)?; - Ok(()) - } - "code" => { - // TODO: try to be smarter based on the address input - 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)); - ctx.add_var(node.into(), self).into_expr_err(*loc)?; - self.add_edge(node, ctx, Edge::Context(ContextEdge::Variable)); - ctx.push_expr(ExprRet::Single(node), self) - .into_expr_err(*loc)?; - Ok(()) - } - "balance" => { - // TODO: try to be smarter based on the address input - let bn = self.builtin_or_add(Builtin::Uint(256)); - let cvar = ContextVar::new_from_builtin(*loc, bn.into(), self) - .into_expr_err(*loc)?; - let node = self.add_node(Node::ContextVar(cvar)); - ctx.add_var(node.into(), self).into_expr_err(*loc)?; - self.add_edge(node, ctx, Edge::Context(ContextEdge::Variable)); - ctx.push_expr(ExprRet::Single(node), self) - .into_expr_err(*loc)?; - Ok(()) - } - "require" | "assert" => { - self.apply_to_edges(ctx, *loc, &|analyzer, ctx, _loc| { - analyzer.handle_require(input_exprs, ctx) - }) - } - "type" => self.parse_ctx_expr(&input_exprs[0], ctx), - "push" => { - if input_exprs.len() == 1 { - // array.push() is valid syntax. It pushes a new - // empty element onto the expr ret stack - self.parse_ctx_expr(&input_exprs[0], ctx)?; - self.apply_to_edges(ctx, *loc, &|analyzer, ctx, loc| { - let Some(array) = - ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? - else { - return Err(ExprErr::NoLhs( - loc, - "array[].push(..) was not an array to push to" - .to_string(), - )); - }; - - let arr = array.expect_single().into_expr_err(loc)?; - let arr = ContextVarNode::from(arr).latest_version(analyzer); - // get length - let len = analyzer.tmp_length(arr, ctx, loc); - - let len_as_idx = - len.as_tmp(loc, ctx, analyzer).into_expr_err(loc)?; - // set length as index - analyzer.index_into_array_inner( - ctx, - loc, - ExprRet::Single(arr.latest_version(analyzer).into()), - ExprRet::Single(len_as_idx.latest_version(analyzer).into()), - )?; - Ok(()) - }) - } else if input_exprs.len() == 2 { - self.parse_ctx_expr(&input_exprs[0], ctx)?; - self.apply_to_edges(ctx, *loc, &|analyzer, ctx, loc| { - let Some(array) = - ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? - else { - return Err(ExprErr::NoLhs( - loc, - "array[].push(..) was not an array to push to" - .to_string(), - )); - }; - if matches!(array, ExprRet::CtxKilled(_)) { - ctx.push_expr(array, analyzer).into_expr_err(loc)?; - return Ok(()); - } - analyzer.parse_ctx_expr(&input_exprs[1], ctx)?; - analyzer.apply_to_edges(ctx, loc, &|analyzer, ctx, loc| { - let Some(new_elem) = ctx - .pop_expr_latest(loc, analyzer) - .into_expr_err(loc)? - else { - return Err(ExprErr::NoRhs( - loc, - "array[].push(..) was not given an element to push" - .to_string(), - )); - }; - - if matches!(new_elem, ExprRet::CtxKilled(_)) { - ctx.push_expr(new_elem, analyzer).into_expr_err(loc)?; - return Ok(()); - } - - let arr = array.expect_single().into_expr_err(loc)?; - let arr = - ContextVarNode::from(arr).latest_version(analyzer); - // get length - let len = analyzer.tmp_length(arr, ctx, loc); - - let len_as_idx = - len.as_tmp(loc, ctx, analyzer).into_expr_err(loc)?; - // set length as index - analyzer.index_into_array_inner( - ctx, - loc, - ExprRet::Single(arr.latest_version(analyzer).into()), - ExprRet::Single( - len_as_idx.latest_version(analyzer).into(), - ), - )?; - let index = ctx - .pop_expr_latest(loc, analyzer) - .into_expr_err(loc)? - .unwrap(); - if matches!(index, ExprRet::CtxKilled(_)) { - ctx.push_expr(index, analyzer).into_expr_err(loc)?; - return Ok(()); - } - // assign index to new_elem - analyzer.match_assign_sides(ctx, loc, &index, &new_elem) - }) - }) - } else { - return Err(ExprErr::InvalidFunctionInput( - *loc, - format!( - "array[].push(..) expected 0 or 1 inputs, got: {}", - input_exprs.len() - ), - )); - } - } - "pop" => { - if input_exprs.len() != 1 { - return Err(ExprErr::InvalidFunctionInput( - *loc, - format!( - "array[].pop() expected 0 inputs, got: {}", - input_exprs.len() - ), - )); - } - self.parse_ctx_expr(&input_exprs[0], ctx)?; - self.apply_to_edges(ctx, *loc, &|analyzer, ctx, loc| { - let Some(array) = - ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? - else { - return Err(ExprErr::NoLhs( - loc, - "array[].pop() was not an array to pop from".to_string(), - )); - }; - if matches!(array, ExprRet::CtxKilled(_)) { - ctx.push_expr(array, analyzer).into_expr_err(loc)?; - return Ok(()); - } - - // get the array - let arr = array.expect_single().into_expr_err(loc)?; - let arr = ContextVarNode::from(arr).latest_version(analyzer); - - // get length - analyzer.match_length(ctx, loc, array, false)?; - let Some(len) = - ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? - else { - return Err(ExprErr::NoLhs( - loc, - "array[].pop() was not an array to pop from".to_string(), - )); - }; - let len = len.expect_single().into_expr_err(loc)?; - let next_len = analyzer.advance_var_in_ctx(len.into(), loc, ctx)?; - next_len - .set_range_min( - analyzer, - Elem::from(len) - Elem::from(Concrete::from(U256::from(1))), - ) - .into_expr_err(loc)?; - next_len - .set_range_max( - analyzer, - Elem::from(len) - Elem::from(Concrete::from(U256::from(1))), - ) - .into_expr_err(loc)?; - - // set length as index - analyzer.index_into_array_inner( - ctx, - loc, - ExprRet::Single(arr.latest_version(analyzer).into()), - ExprRet::Single(next_len.latest_version(analyzer).into()), - ) - }) - } - "concat" => self.concat(loc, input_exprs, ctx), - "keccak256" => { - self.parse_ctx_expr(&input_exprs[0], ctx)?; - self.apply_to_edges(ctx, *loc, &|analyzer, ctx, loc| { - let Some(_input) = - ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? - else { - return Err(ExprErr::NoRhs( - loc, - "abi.decode was not given the types for decoding" - .to_string(), - )); - }; - let var = ContextVar::new_from_builtin( - loc, - analyzer.builtin_or_add(Builtin::Bytes(32)).into(), - analyzer, - ) - .into_expr_err(loc)?; - let cvar = analyzer.add_node(Node::ContextVar(var)); - ctx.push_expr(ExprRet::Single(cvar), analyzer) - .into_expr_err(loc)?; - Ok(()) - }) - } - "sha256" => { - self.parse_ctx_expr(&input_exprs[0], ctx)?; - self.apply_to_edges(ctx, *loc, &|analyzer, ctx, loc| { - let Some(input) = - ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? - else { - return Err(ExprErr::NoRhs( - loc, - "abi.decode was not given the types for decoding" - .to_string(), - )); - }; - if matches!(input, ExprRet::CtxKilled(_)) { - ctx.push_expr(input, analyzer).into_expr_err(loc)?; - return Ok(()); - } - let var = ContextVar::new_from_builtin( - loc, - analyzer.builtin_or_add(Builtin::Bytes(32)).into(), - analyzer, - ) - .into_expr_err(loc)?; - let cvar = analyzer.add_node(Node::ContextVar(var)); - ctx.push_expr(ExprRet::Single(cvar), analyzer) - .into_expr_err(loc)?; - Ok(()) - }) - } - "ripemd160" => { - self.parse_ctx_expr(&input_exprs[0], ctx)?; - self.apply_to_edges(ctx, *loc, &|analyzer, ctx, loc| { - let Some(input) = - ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? - else { - return Err(ExprErr::NoRhs( - loc, - "abi.decode was not given the types for decoding" - .to_string(), - )); - }; - if matches!(input, ExprRet::CtxKilled(_)) { - ctx.push_expr(input, analyzer).into_expr_err(loc)?; - return Ok(()); - } - let var = ContextVar::new_from_builtin( - loc, - analyzer.builtin_or_add(Builtin::Bytes(32)).into(), - analyzer, - ) - .into_expr_err(loc)?; - let cvar = analyzer.add_node(Node::ContextVar(var)); - ctx.push_expr(ExprRet::Single(cvar), analyzer) - .into_expr_err(loc)?; - Ok(()) - }) - } - "blockhash" => { - self.parse_ctx_expr(&input_exprs[0], ctx)?; - self.apply_to_edges(ctx, *loc, &|analyzer, ctx, loc| { - let Some(input) = - ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? - else { - return Err(ExprErr::NoRhs( - loc, - "blockhash function was not provided a block number" - .to_string(), - )); - }; - if matches!(input, ExprRet::CtxKilled(_)) { - ctx.push_expr(input, analyzer).into_expr_err(loc)?; - return Ok(()); - } - let var = ContextVar::new_from_builtin( - loc, - analyzer.builtin_or_add(Builtin::Bytes(32)).into(), - analyzer, - ) - .into_expr_err(loc)?; - let cvar = analyzer.add_node(Node::ContextVar(var)); - ctx.push_expr(ExprRet::Single(cvar), analyzer) - .into_expr_err(loc)?; - Ok(()) - }) - } - "gasleft" => { - let var = ContextVar::new_from_builtin( - *loc, - self.builtin_or_add(Builtin::Uint(64)).into(), - self, - ) - .into_expr_err(*loc)?; - let cvar = self.add_node(Node::ContextVar(var)); - ctx.push_expr(ExprRet::Single(cvar), self) - .into_expr_err(*loc)?; - Ok(()) - } - "ecrecover" => { - self.parse_inputs(ctx, *loc, input_exprs)?; - - self.apply_to_edges(ctx, *loc, &|analyzer, ctx, loc| { - let cctx = Context::new_subctx( - ctx, - None, - loc, - None, - Some(func_idx.into()), - true, - analyzer, - None, - ) - .into_expr_err(loc)?; - let call_ctx = analyzer.add_node(Node::Context(cctx)); - ctx.set_child_call(call_ctx.into(), analyzer) - .into_expr_err(loc)?; - let call_node = analyzer.add_node(Node::FunctionCall); - analyzer.add_edge( - call_node, - func_idx, - Edge::Context(ContextEdge::Call), - ); - analyzer.add_edge( - call_node, - ctx, - Edge::Context(ContextEdge::Subcontext), - ); - analyzer.add_edge( - call_ctx, - call_node, - Edge::Context(ContextEdge::Subcontext), - ); - - let Some(input) = - ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? - else { - return Err(ExprErr::NoRhs( - loc, - "ecrecover did not receive inputs".to_string(), - )); - }; - - if matches!(input, ExprRet::CtxKilled(_)) { - ctx.push_expr(input, analyzer).into_expr_err(loc)?; - return Ok(()); - } - - let mut inner_vals = vec![]; - match input { - ExprRet::Single(var) | ExprRet::SingleLiteral(var) => { - inner_vals.push( - ContextVarNode::from(var) - .display_name(analyzer) - .unwrap(), - ); - } - _ => inner_vals.push("".to_string()), - } - let inner_name = - inner_vals.into_iter().collect::>().join(", "); - let mut var = ContextVar::new_from_builtin( - loc, - analyzer.builtin_or_add(Builtin::Address).into(), - analyzer, - ) - .into_expr_err(loc)?; - var.display_name = format!("ecrecover({})", inner_name); - var.is_symbolic = true; - var.is_return = true; - let cvar = analyzer.add_node(Node::ContextVar(var)); - ctx.add_var(cvar.into(), analyzer).into_expr_err(loc)?; - analyzer.add_edge( - cvar, - call_ctx, - Edge::Context(ContextEdge::Variable), - ); - analyzer.add_edge( - cvar, - call_ctx, - Edge::Context(ContextEdge::Return), - ); - ContextNode::from(call_ctx) - .add_return_node(loc, cvar.into(), analyzer) - .into_expr_err(loc)?; - - let rctx = Context::new_subctx( - call_ctx.into(), - Some(ctx), - loc, - None, - None, - true, - analyzer, - None, - ) - .into_expr_err(loc)?; - let ret_ctx = analyzer.add_node(Node::Context(rctx)); - ContextNode::from(call_ctx) - .set_child_call(ret_ctx.into(), analyzer) - .into_expr_err(loc)?; - analyzer.add_edge( - ret_ctx, - call_ctx, - Edge::Context(ContextEdge::Continue), - ); - - let tmp_ret = ContextVarNode::from(cvar) - .as_tmp( - ContextNode::from(call_ctx) - .underlying(analyzer) - .unwrap() - .loc, - ret_ctx.into(), - analyzer, - ) - .unwrap(); - tmp_ret.underlying_mut(analyzer).unwrap().is_return = true; - tmp_ret.underlying_mut(analyzer).unwrap().display_name = - format!("ecrecover({}).return", inner_name); - ctx.add_var(tmp_ret, analyzer).into_expr_err(loc)?; - analyzer.add_edge( - tmp_ret, - ret_ctx, - Edge::Context(ContextEdge::Variable), - ); - - ContextNode::from(ret_ctx) - .push_expr(ExprRet::Single(tmp_ret.into()), analyzer) - .into_expr_err(loc)?; - Ok(()) - }) - } - "addmod" => { - // TODO: actually calcuate this if possible - self.parse_inputs(ctx, *loc, input_exprs)?; - - self.apply_to_edges(ctx, *loc, &|analyzer, ctx, loc| { - ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)?; - let var = ContextVar::new_from_builtin( - loc, - analyzer.builtin_or_add(Builtin::Uint(256)).into(), - analyzer, - ) - .into_expr_err(loc)?; - let cvar = analyzer.add_node(Node::ContextVar(var)); - ctx.push_expr(ExprRet::Single(cvar), analyzer) - .into_expr_err(loc)?; - Ok(()) - }) - } - "mulmod" => { - // TODO: actually calcuate this if possible - self.parse_inputs(ctx, *loc, input_exprs)?; - self.apply_to_edges(ctx, *loc, &|analyzer, ctx, loc| { - ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)?; - let var = ContextVar::new_from_builtin( - loc, - analyzer.builtin_or_add(Builtin::Uint(256)).into(), - analyzer, - ) - .into_expr_err(loc)?; - let cvar = analyzer.add_node(Node::ContextVar(var)); - ctx.push_expr(ExprRet::Single(cvar), analyzer) - .into_expr_err(loc)?; - Ok(()) - }) - } - "wrap" => { - if input_exprs.len() != 2 { - return Err(ExprErr::InvalidFunctionInput(*loc, format!("Expected a member type and an input to the wrap function, but got: {:?}", input_exprs))); - } - - self.parse_inputs(ctx, *loc, input_exprs)?; - self.apply_to_edges(ctx, *loc, &|analyzer, ctx, loc| { - let Some(input) = - ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? - else { - return Err(ExprErr::NoRhs( - loc, - "ecrecover did not receive inputs".to_string(), - )); - }; - input.expect_length(2).into_expr_err(loc)?; - let ret = input.as_vec(); - let wrapping_ty = ret[0].expect_single().into_expr_err(loc)?; - let var = ContextVar::new_from_ty( - loc, - TyNode::from(wrapping_ty), - ctx, - analyzer, - ) - .into_expr_err(loc)?; - let to_be_wrapped = ret[1].expect_single().into_expr_err(loc)?; - let cvar = - ContextVarNode::from(analyzer.add_node(Node::ContextVar(var))); - let next = analyzer.advance_var_in_ctx(cvar, loc, ctx)?; - let expr = Elem::Expr(RangeExpr::new( - Elem::from(to_be_wrapped), - RangeOp::Cast, - Elem::from(cvar), - )); - next.set_range_min(analyzer, expr.clone()) - .into_expr_err(loc)?; - next.set_range_max(analyzer, expr).into_expr_err(loc)?; - ctx.push_expr(ExprRet::Single(cvar.into()), analyzer) - .into_expr_err(loc) - }) - } - "unwrap" => { - self.parse_inputs(ctx, *loc, input_exprs)?; - self.apply_to_edges(ctx, *loc, &|analyzer, ctx, loc| { - let Some(input) = - ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? - else { - return Err(ExprErr::NoRhs( - loc, - "ecrecover did not receive inputs".to_string(), - )); - }; - input.expect_length(2).into_expr_err(loc)?; - let ret = input.as_vec(); - let wrapping_ty = ret[0].expect_single().into_expr_err(loc)?; - let mut var = ContextVar::new_from_builtin( - loc, - BuiltInNode::from( - TyNode::from(wrapping_ty) - .underlying(analyzer) - .into_expr_err(loc)? - .ty, - ), - analyzer, - ) - .into_expr_err(loc)?; - let to_be_unwrapped = ret[1].expect_single().into_expr_err(loc)?; - var.display_name = format!( - "{}.unwrap({})", - TyNode::from(wrapping_ty) - .name(analyzer) - .into_expr_err(loc)?, - ContextVarNode::from(to_be_unwrapped) - .display_name(analyzer) - .into_expr_err(loc)? - ); - - let cvar = - ContextVarNode::from(analyzer.add_node(Node::ContextVar(var))); - cvar.set_range_min(analyzer, Elem::from(to_be_unwrapped)) - .into_expr_err(loc)?; - cvar.set_range_max(analyzer, Elem::from(to_be_unwrapped)) - .into_expr_err(loc)?; - let next = analyzer.advance_var_in_ctx(cvar, loc, ctx)?; - let expr = Elem::Expr(RangeExpr::new( - Elem::from(to_be_unwrapped), - RangeOp::Cast, - Elem::from(cvar), - )); - next.set_range_min(analyzer, expr.clone()) - .into_expr_err(loc)?; - next.set_range_max(analyzer, expr).into_expr_err(loc)?; - ctx.push_expr(ExprRet::Single(cvar.into()), analyzer) - .into_expr_err(loc) - }) - } - e => Err(ExprErr::Todo( - *loc, - format!("builtin function: {e:?} doesn't exist or isn't implemented"), - )), - } - } else { - panic!("unnamed builtin?") - } - } - Node::Builtin(Builtin::Array(_)) => { - // create a new list - self.parse_ctx_expr(&input_exprs[0], ctx)?; - self.apply_to_edges(ctx, *loc, &|analyzer, ctx, loc| { - let Some(len_var) = ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? - else { - return Err(ExprErr::NoRhs(loc, "Array creation failed".to_string())); - }; - - if matches!(len_var, ExprRet::CtxKilled(_)) { - ctx.push_expr(len_var, analyzer).into_expr_err(loc)?; - return Ok(()); - } - let len_cvar = len_var.expect_single().into_expr_err(loc)?; - - let ty = VarType::try_from_idx(analyzer, func_idx); - - let new_arr = ContextVar { - loc: Some(loc), - name: format!("tmp_arr{}", ctx.new_tmp(analyzer).into_expr_err(loc)?), - display_name: "arr".to_string(), - storage: None, - is_tmp: true, - is_symbolic: false, - is_return: false, - tmp_of: None, - ty: ty.expect("No type for node"), - }; - - let arr = ContextVarNode::from(analyzer.add_node(Node::ContextVar(new_arr))); - - let len_var = ContextVar { - loc: Some(loc), - name: arr.name(analyzer).into_expr_err(loc)? + ".length", - display_name: arr.display_name(analyzer).unwrap() + ".length", - storage: None, - is_tmp: true, - tmp_of: None, - is_symbolic: true, - is_return: false, - ty: ContextVarNode::from(len_cvar) - .underlying(analyzer) - .into_expr_err(loc)? - .ty - .clone(), - }; - - let len_cvar = analyzer.add_node(Node::ContextVar(len_var)); - analyzer.add_edge(arr, ctx, Edge::Context(ContextEdge::Variable)); - ctx.add_var(arr, analyzer).into_expr_err(loc)?; - analyzer.add_edge(len_cvar, ctx, Edge::Context(ContextEdge::Variable)); - ctx.add_var(len_cvar.into(), analyzer).into_expr_err(loc)?; - analyzer.add_edge(len_cvar, arr, Edge::Context(ContextEdge::AttrAccess)); - - // update the length - if let Some(r) = arr.ref_range(analyzer).into_expr_err(loc)? { - let min = r.evaled_range_min(analyzer).into_expr_err(loc)?; - let max = r.evaled_range_max(analyzer).into_expr_err(loc)?; - - if let Some(mut rd) = min.maybe_range_dyn() { - rd.len = Elem::from(len_cvar); - arr.set_range_min(analyzer, Elem::ConcreteDyn(Box::new(rd))) - .into_expr_err(loc)?; - } - - if let Some(mut rd) = max.maybe_range_dyn() { - rd.len = Elem::from(len_cvar); - arr.set_range_min(analyzer, Elem::ConcreteDyn(Box::new(rd))) - .into_expr_err(loc)?; - } - } - - ctx.push_expr(ExprRet::Single(arr.into()), analyzer) - .into_expr_err(loc)?; - Ok(()) - }) - } - Node::Builtin(ty) => { - // it is a cast - let ty = ty.clone(); - fn cast_match( - ctx: ContextNode, - loc: Loc, - analyzer: &mut (impl GraphBackend + AnalyzerBackend), - ty: &Builtin, - ret: ExprRet, - func_idx: NodeIdx, - ) -> Result<(), ExprErr> { - match ret { - ExprRet::CtxKilled(kind) => { - ctx.kill(analyzer, loc, kind).into_expr_err(loc) - } - ExprRet::Null => Ok(()), - ExprRet::Single(cvar) | ExprRet::SingleLiteral(cvar) => { - let new_var = ContextVarNode::from(cvar) - .as_cast_tmp(loc, ctx, ty.clone(), analyzer) - .into_expr_err(loc)?; - - new_var.underlying_mut(analyzer).into_expr_err(loc)?.ty = - VarType::try_from_idx(analyzer, func_idx).expect(""); - // cast the ranges - if let Some(r) = ContextVarNode::from(cvar) - .range(analyzer) - .into_expr_err(loc)? - { - let curr_range = - SolcRange::try_from_builtin(ty).expect("No default range"); - let mut min = r - .range_min() - .into_owned() - .cast(curr_range.range_min().into_owned()); - - min.cache_minimize(analyzer).into_expr_err(loc)?; - let mut max = r - .range_max() - .into_owned() - .cast(curr_range.range_max().into_owned()); - - max.cache_maximize(analyzer).into_expr_err(loc)?; - - let existing_max = - r.evaled_range_max(analyzer).into_expr_err(loc)?; - // Check if the max value has changed once the cast is applied. - // If it hasnt, then the cast had no effect and we should adjust the naming - // to not muddle the CLI - if let Some(std::cmp::Ordering::Equal) = max - .maximize(analyzer) - .into_expr_err(loc)? - .range_ord(&existing_max) - { - // its a noop, reflect that in the naming - new_var.underlying_mut(analyzer).unwrap().display_name = - ContextVarNode::from(cvar) - .display_name(analyzer) - .into_expr_err(loc)?; - } - - new_var.set_range_min(analyzer, min).into_expr_err(loc)?; - new_var.set_range_max(analyzer, max).into_expr_err(loc)?; - // cast the range exclusions - TODO: verify this is correct - let mut exclusions = r.range_exclusions(); - exclusions.iter_mut().for_each(|range| { - *range = - range.clone().cast(curr_range.range_min().into_owned()); - }); - new_var - .set_range_exclusions(analyzer, exclusions) - .into_expr_err(loc)?; - } - - ctx.push_expr(ExprRet::Single(new_var.into()), analyzer) - .into_expr_err(loc)?; - Ok(()) - } - ExprRet::Multi(inner) => inner - .into_iter() - .try_for_each(|i| cast_match(ctx, loc, analyzer, ty, i, func_idx)), - } - } - - self.parse_ctx_expr(&input_exprs[0], 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::NoRhs(loc, "Array creation failed".to_string())); - }; - - if matches!(ret, ExprRet::CtxKilled(_)) { - ctx.push_expr(ret, analyzer).into_expr_err(loc)?; - return Ok(()); - } - - cast_match(ctx, loc, analyzer, &ty, ret, func_idx) - }) - } - Node::ContextVar(_c) => { - // its a user type - // TODO: figure out if we actually need to do anything? - // input_exprs - // .iter() - // .try_for_each(|expr| self.parse_ctx_expr(expr, ctx))?; - - // self.apply_to_edges(ctx, *loc, &|analyzer, ctx, loc| { - // }) - - ctx.push_expr(ExprRet::Single(func_idx), self) - .into_expr_err(*loc)?; - Ok(()) - } - Node::Contract(_) => { - if !input_exprs.is_empty() { - self.parse_ctx_expr(&input_exprs[0], ctx)?; - } - self.apply_to_edges(ctx, *loc, &|analyzer, ctx, loc| { - if !input_exprs.is_empty() { - let Some(ret) = ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? - else { - return Err(ExprErr::NoRhs( - loc, - "Contract creation failed".to_string(), - )); - }; - if matches!(ret, ExprRet::CtxKilled(_)) { - ctx.push_expr(ret, analyzer).into_expr_err(loc)?; - return Ok(()); - } - } - - let var = match ContextVar::maybe_from_user_ty(analyzer, loc, func_idx) { - Some(v) => v, - None => { - return Err(ExprErr::VarBadType( - loc, - format!( - "Could not create context variable from user type: {:?}", - analyzer.node(func_idx) - ), - )) - } - }; - // let idx = ret.expect_single().into_expr_err(loc)?; - let contract_cvar = - ContextVarNode::from(analyzer.add_node(Node::ContextVar(var))); - // contract_cvar - // .set_range_min(analyzer, Elem::from(idx)) - // .into_expr_err(loc)?; - // contract_cvar - // .set_range_max(analyzer, Elem::from(idx)) - // .into_expr_err(loc)?; - ctx.push_expr(ExprRet::Single(contract_cvar.into()), analyzer) - .into_expr_err(loc) - }) - } - Node::Unresolved(_) => { - self.parse_inputs(ctx, *loc, input_exprs)?; - - self.apply_to_edges(ctx, *loc, &|analyzer, ctx, loc| { - let Some(inputs) = ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? else { - return Err(ExprErr::NoRhs(loc, "Function call failed".to_string())) - }; - - if matches!(inputs, ExprRet::CtxKilled(_)) { - ctx.push_expr(inputs, analyzer).into_expr_err(loc)?; - return Ok(()); - } - let visible_funcs = ctx.visible_funcs(analyzer).into_expr_err(loc)? - .iter() - .map(|func| func.name(analyzer).unwrap()) - .collect::>(); - - if let Node::Unresolved(ident) = analyzer.node(func_idx) { - Err(ExprErr::FunctionNotFound( - loc, - format!( - "Could not find function: \"{}{}\", context: {}, visible functions: {:#?}", - ident.name, - inputs.try_as_func_input_str(analyzer), - ctx.path(analyzer), - visible_funcs - ) - )) - } else { - unreachable!() - } - }) - } - Node::Struct(_) => { - // struct construction - let strukt = StructNode::from(func_idx); - let var = - ContextVar::new_from_struct(*loc, strukt, ctx, self).into_expr_err(*loc)?; - let cvar = self.add_node(Node::ContextVar(var)); - ctx.add_var(cvar.into(), self).into_expr_err(*loc)?; - self.add_edge(cvar, ctx, Edge::Context(ContextEdge::Variable)); - - self.parse_inputs(ctx, *loc, input_exprs)?; - self.apply_to_edges(ctx, *loc, &|analyzer, ctx, loc| { - let Some(inputs) = ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? - else { - return Err(ExprErr::NoRhs( - loc, - "Struct Function call failed".to_string(), - )); - }; - - let inputs = inputs.as_vec(); - // set struct fields - strukt - .fields(analyzer) - .iter() - .zip(inputs) - .try_for_each(|(field, input)| { - let field_cvar = ContextVar::maybe_new_from_field( - analyzer, - loc, - ContextVarNode::from(cvar) - .underlying(analyzer) - .into_expr_err(loc)?, - field.underlying(analyzer).unwrap().clone(), - ) - .expect("Invalid struct field"); - - let fc_node = analyzer.add_node(Node::ContextVar(field_cvar)); - analyzer.add_edge( - fc_node, - cvar, - Edge::Context(ContextEdge::AttrAccess), - ); - analyzer.add_edge(fc_node, ctx, Edge::Context(ContextEdge::Variable)); - ctx.add_var(fc_node.into(), analyzer).into_expr_err(loc)?; - let field_as_ret = ExprRet::Single(fc_node); - analyzer.match_assign_sides(ctx, loc, &field_as_ret, &input)?; - let _ = ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)?; - Ok(()) - })?; - - ctx.push_expr(ExprRet::Single(cvar), analyzer) - .into_expr_err(loc) - }) - } - e => Err(ExprErr::FunctionNotFound(*loc, format!("{e:?}"))), - } - } - - #[tracing::instrument(level = "trace", skip_all)] - fn concat( - &mut self, - loc: &Loc, - input_exprs: &[Expression], - ctx: ContextNode, - ) -> Result<(), ExprErr> { - input_exprs[1..].iter().try_for_each(|expr| { - self.parse_ctx_expr(expr, ctx)?; - self.apply_to_edges(ctx, *loc, &|analyzer, ctx, loc| { - let input = ctx - .pop_expr_latest(loc, analyzer) - .into_expr_err(loc)? - .unwrap_or(ExprRet::Null); - ctx.append_tmp_expr(input, analyzer).into_expr_err(loc) - }) - })?; - - self.apply_to_edges(ctx, *loc, &|analyzer, ctx, loc| { - let Some(inputs) = ctx.pop_tmp_expr(loc, analyzer).into_expr_err(loc)? else { - return Err(ExprErr::NoRhs(loc, "Concatenation failed".to_string())); - }; - if matches!(inputs, ExprRet::CtxKilled(_)) { - ctx.push_expr(inputs, analyzer).into_expr_err(loc)?; - return Ok(()); - } - let inputs = inputs.as_vec(); - if inputs.is_empty() { - ctx.push_expr(ExprRet::Multi(vec![]), analyzer) - .into_expr_err(loc)?; - Ok(()) - } else { - let start = &inputs[0]; - if inputs.len() > 1 { - analyzer.match_concat(ctx, loc, start.clone(), &inputs[1..], None) - } else { - analyzer.match_concat(ctx, loc, start.clone(), &[], None) - } - } - }) - } - - fn match_concat( - &mut self, - ctx: ContextNode, - loc: Loc, - curr: ExprRet, - inputs: &[ExprRet], - accum_node: Option, - ) -> Result<(), ExprErr> { - if let Some(accum_node) = accum_node { - match curr.flatten() { - ExprRet::Single(var) | ExprRet::SingleLiteral(var) => { - self.concat_inner(loc, accum_node, ContextVarNode::from(var))?; - ctx.push_expr(ExprRet::Single(accum_node.into()), self) - .into_expr_err(loc)?; - Ok(()) - } - ExprRet::Null => { - ctx.push_expr(ExprRet::Single(accum_node.into()), self) - .into_expr_err(loc)?; - Ok(()) - } - ExprRet::Multi(inner) => inner - .into_iter() - .try_for_each(|i| self.match_concat(ctx, loc, i, inputs, Some(accum_node))), - ExprRet::CtxKilled(kind) => ctx.kill(self, loc, kind).into_expr_err(loc), - } - } else { - match curr.flatten() { - ExprRet::Single(var) | ExprRet::SingleLiteral(var) => { - let acc = ContextVarNode::from(var) - .as_tmp(loc, ctx, self) - .into_expr_err(loc)?; - inputs - .iter() - .map(|i| self.match_concat(ctx, loc, i.clone(), inputs, Some(acc))) - .collect::, ExprErr>>()?; - ctx.push_expr(ExprRet::Single(acc.into()), self) - .into_expr_err(loc)?; - Ok(()) - } - ExprRet::Null => Err(ExprErr::NoRhs( - loc, - "No input provided to concat function".to_string(), - )), - ExprRet::Multi(inner) => inner - .into_iter() - .try_for_each(|i| self.match_concat(ctx, loc, i, inputs, None)), - ExprRet::CtxKilled(kind) => ctx.kill(self, loc, kind).into_expr_err(loc), - } - } - } - - fn concat_inner( - &mut self, - loc: Loc, - accum: ContextVarNode, - right: ContextVarNode, - ) -> Result<(), ExprErr> { - match ( - accum.ty(self).into_expr_err(loc)?, - right.ty(self).into_expr_err(loc)?, - ) { - (VarType::Concrete(accum_cnode), VarType::Concrete(right_cnode)) => { - let new_ty = match ( - accum_cnode.underlying(self).into_expr_err(loc)?, - right_cnode.underlying(self).into_expr_err(loc)?, - ) { - (accum_node @ Concrete::String(..), right_node @ Concrete::String(..)) => { - let new_val = accum_node.clone().concat(right_node).unwrap(); - let new_cnode = self.add_node(Node::Concrete(new_val)); - VarType::Concrete(new_cnode.into()) - } - (accum_node @ Concrete::DynBytes(..), right_node @ Concrete::DynBytes(..)) => { - let new_val = accum_node.clone().concat(right_node).unwrap(); - let new_cnode = self.add_node(Node::Concrete(new_val)); - VarType::Concrete(new_cnode.into()) - } - (a, b) => { - // Invalid solidity - return Err(ExprErr::InvalidFunctionInput(loc, format!("Type mismatch: {a:?} for left hand side and type: {b:?} for right hand side"))); - } - }; - accum.underlying_mut(self).into_expr_err(loc)?.ty = new_ty; - Ok(()) - } - (VarType::Concrete(accum_cnode), VarType::BuiltIn(_bn, Some(r2))) => { - let underlying = accum_cnode.underlying(self).into_expr_err(loc)?; - // let val = match underlying { - // Concrete::String(val) => { - // val - // .chars() - // .enumerate() - // .map(|(i, v)| { - // let idx = Elem::from(Concrete::from(U256::from(i))); - // let mut bytes = [0x00; 32]; - // v.encode_utf8(&mut bytes[..]); - // let v = Elem::from(Concrete::Bytes(1, H256::from(bytes))); - // (idx, v) - // }) - // .collect::>() - // } - // Concrete::DynBytes(val) => { - // val - // .iter() - // .enumerate() - // .map(|(i, v)| { - // let idx = Elem::from(Concrete::from(U256::from(i))); - // let mut bytes = [0x00; 32]; - // bytes[0] = *v; - // let v = Elem::from(Concrete::Bytes(1, H256::from(bytes))); - // (idx, v) - // }) - // .collect::>() - // } - // b => return Err(ExprErr::InvalidFunctionInput(loc, format!("Type mismatch: expected String or Bytes for concat input but found: {b:?}"))) - // }; - // TODO: Extend with bn - - let range = SolcRange::from(underlying.clone()).unwrap(); - let min = range.min.clone().concat(r2.min.clone()); - let max = range.max.clone().concat(r2.max.clone()); - accum.set_range_min(self, min).into_expr_err(loc)?; - accum.set_range_max(self, max).into_expr_err(loc)?; - - let new_ty = - VarType::BuiltIn(self.builtin_or_add(Builtin::String).into(), Some(range)); - accum.underlying_mut(self).into_expr_err(loc)?.ty = new_ty; - Ok(()) - } - (VarType::BuiltIn(_bn, Some(r)), VarType::BuiltIn(_bn2, Some(r2))) => { - // TODO: improve length calculation here - let min = r.min.clone().concat(r2.min.clone()); - let max = r.max.clone().concat(r2.max.clone()); - accum.set_range_min(self, min).into_expr_err(loc)?; - accum.set_range_max(self, max).into_expr_err(loc)?; - Ok(()) - } - (_, _) => Ok(()), - } - } -} diff --git a/crates/solc-expressions/src/func_call/intrinsic_call/abi.rs b/crates/solc-expressions/src/func_call/intrinsic_call/abi.rs new file mode 100644 index 00000000..15ed452c --- /dev/null +++ b/crates/solc-expressions/src/func_call/intrinsic_call/abi.rs @@ -0,0 +1,128 @@ +use crate::{ + ContextBuilder, ExprErr, IntoExprErr, +}; + +use graph::{ + nodes::{ + Builtin, ContextNode, ContextVar, ExprRet, + }, + AnalyzerBackend, ContextEdge, Edge, Node, +}; + +use solang_parser::pt::{Expression, Loc}; + +impl AbiCaller for T where T: AnalyzerBackend + Sized {} +pub trait AbiCaller: AnalyzerBackend + Sized { + fn abi_call(&mut self, func_name: String, input_exprs: &[Expression], loc: Loc, ctx: ContextNode) -> Result<(), ExprErr> { + match &*func_name { + "abi.decode" => { + // we skip the first because that is what is being decoded. + // TODO: check if we have a concrete bytes value + fn match_decode( + ctx: ContextNode, + loc: &Loc, + ret: ExprRet, + analyzer: &mut impl AnalyzerBackend, + ) -> Result<(), ExprErr> { + match ret { + ExprRet::Single(expect_builtin) => { + match analyzer.node(expect_builtin) { + Node::Builtin(_) => { + let var = ContextVar::new_from_builtin( + *loc, + expect_builtin.into(), + analyzer, + ) + .into_expr_err(*loc)?; + 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), + ); + ctx.push_expr(ExprRet::Single(node), analyzer) + .into_expr_err(*loc)?; + Ok(()) + } + Node::ContextVar(cvar) => { + let bn = analyzer + .builtin_or_add( + cvar.ty + .as_builtin(analyzer) + .into_expr_err(*loc)?, + ) + .into(); + let var = ContextVar::new_from_builtin( + *loc, bn, analyzer, + ) + .into_expr_err(*loc)?; + 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), + ); + ctx.push_expr(ExprRet::Single(node), analyzer) + .into_expr_err(*loc)?; + Ok(()) + } + e => todo!("Unhandled type in abi.decode: {e:?}"), + } + } + ExprRet::Multi(inner) => inner.iter().try_for_each(|i| { + match_decode(ctx, loc, i.clone(), analyzer) + }), + ExprRet::CtxKilled(kind) => { + ctx.kill(analyzer, *loc, kind).into_expr_err(*loc) + } + e => panic!("This is invalid solidity: {:?}", e), + } + } + self.parse_ctx_expr(&input_exprs[1], 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::NoRhs( + loc, + "abi.decode was not given the types for decoding" + .to_string(), + )); + }; + if matches!(ret, ExprRet::CtxKilled(_)) { + ctx.push_expr(ret, analyzer).into_expr_err(loc)?; + return Ok(()); + } + match_decode(ctx, &loc, ret, analyzer) + }) + } + "abi.encode" + | "abi.encodePacked" + | "abi.encodeCall" + | "abi.encodeWithSignature" + | "abi.encodeWithSelector" => { + // currently we dont support concrete abi encoding, TODO + 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)); + ctx.add_var(node.into(), self).into_expr_err(loc)?; + self.add_edge(node, ctx, Edge::Context(ContextEdge::Variable)); + ctx.push_expr(ExprRet::Single(node), self) + .into_expr_err(loc)?; + Ok(()) + } + _ => Err(ExprErr::FunctionNotFound( + loc, + format!( + "Could not find abi function: \"{func_name}\", context: {}", + ctx.path(self), + ) + )) + } + } +} \ No newline at end of file diff --git a/crates/solc-expressions/src/func_call/intrinsic_call/address.rs b/crates/solc-expressions/src/func_call/intrinsic_call/address.rs new file mode 100644 index 00000000..af687a35 --- /dev/null +++ b/crates/solc-expressions/src/func_call/intrinsic_call/address.rs @@ -0,0 +1,79 @@ +use crate::{ + ExprErr, IntoExprErr, +}; + +use graph::{ + nodes::{ + Builtin, ContextNode, ContextVar, ExprRet, + }, + AnalyzerBackend, ContextEdge, Edge, Node, +}; + +use solang_parser::pt::{Expression, Loc}; + +impl AddressCaller for T where T: AnalyzerBackend + Sized {} +pub trait AddressCaller: AnalyzerBackend + Sized { + fn address_call(&mut self, func_name: String, _input_exprs: &[Expression], loc: Loc, ctx: ContextNode) -> Result<(), ExprErr> { + match &*func_name { + "delegatecall" | "staticcall" | "call" => { + // TODO: Check if we have the code for the address + // if we dont, model it as a unrestricted call that can make other calls + ctx.pop_expr_latest(loc, self).into_expr_err(loc)?; + // TODO: try to be smarter based on the address input + let booln = self.builtin_or_add(Builtin::Bool); + let bool_cvar = ContextVar::new_from_builtin(loc, booln.into(), self) + .into_expr_err(loc)?; + let bool_node = self.add_node(Node::ContextVar(bool_cvar)); + ctx.add_var(bool_node.into(), self).into_expr_err(loc)?; + self.add_edge(bool_node, ctx, Edge::Context(ContextEdge::Variable)); + + 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)); + ctx.add_var(node.into(), self).into_expr_err(loc)?; + self.add_edge(node, ctx, Edge::Context(ContextEdge::Variable)); + ctx.push_expr( + ExprRet::Multi(vec![ + ExprRet::Single(bool_node), + ExprRet::Single(node), + ]), + self, + ) + .into_expr_err(loc)?; + Ok(()) + } + "code" => { + // TODO: try to be smarter based on the address input + 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)); + ctx.add_var(node.into(), self).into_expr_err(loc)?; + self.add_edge(node, ctx, Edge::Context(ContextEdge::Variable)); + ctx.push_expr(ExprRet::Single(node), self) + .into_expr_err(loc)?; + Ok(()) + } + "balance" => { + // TODO: try to be smarter based on the address input + let bn = self.builtin_or_add(Builtin::Uint(256)); + let cvar = ContextVar::new_from_builtin(loc, bn.into(), self) + .into_expr_err(loc)?; + let node = self.add_node(Node::ContextVar(cvar)); + ctx.add_var(node.into(), self).into_expr_err(loc)?; + self.add_edge(node, ctx, Edge::Context(ContextEdge::Variable)); + ctx.push_expr(ExprRet::Single(node), self) + .into_expr_err(loc)?; + Ok(()) + } + _ => Err(ExprErr::FunctionNotFound( + loc, + format!( + "Could not find builtin address function: \"{func_name}\", context: {}", + ctx.path(self), + ) + )) + } + } +} \ No newline at end of file diff --git a/crates/solc-expressions/src/func_call/intrinsic_call/array.rs b/crates/solc-expressions/src/func_call/intrinsic_call/array.rs new file mode 100644 index 00000000..c1ede935 --- /dev/null +++ b/crates/solc-expressions/src/func_call/intrinsic_call/array.rs @@ -0,0 +1,200 @@ +use crate::{ + array::Array, ContextBuilder, ExprErr, IntoExprErr, ListAccess, +}; + +use graph::{ + elem::*, + nodes::{ + Concrete, ContextNode, ContextVarNode, ExprRet, + }, + AnalyzerBackend, +}; + +use ethers_core::types::U256; +use solang_parser::pt::{Expression, Loc}; + +impl ArrayCaller for T where T: AnalyzerBackend + Sized {} +pub trait ArrayCaller: AnalyzerBackend + Sized { + fn array_call(&mut self, func_name: String, input_exprs: &[Expression], loc: Loc, ctx: ContextNode) -> Result<(), ExprErr> { + match &*func_name { + "push" => { + if input_exprs.len() == 1 { + // array.push() is valid syntax. It pushes a new + // empty element onto the expr ret stack + self.parse_ctx_expr(&input_exprs[0], ctx)?; + self.apply_to_edges(ctx, loc, &|analyzer, ctx, loc| { + let Some(array) = + ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? + else { + return Err(ExprErr::NoLhs( + loc, + "array[].push(..) was not an array to push to" + .to_string(), + )); + }; + + let arr = array.expect_single().into_expr_err(loc)?; + let arr = ContextVarNode::from(arr).latest_version(analyzer); + // get length + let len = analyzer.tmp_length(arr, ctx, loc); + + let len_as_idx = + len.as_tmp(loc, ctx, analyzer).into_expr_err(loc)?; + // set length as index + analyzer.index_into_array_inner( + ctx, + loc, + ExprRet::Single(arr.latest_version(analyzer).into()), + ExprRet::Single(len_as_idx.latest_version(analyzer).into()), + )?; + Ok(()) + }) + } else if input_exprs.len() == 2 { + // array.push(value) + self.parse_ctx_expr(&input_exprs[0], ctx)?; + self.apply_to_edges(ctx, loc, &|analyzer, ctx, loc| { + let Some(array) = + ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? + else { + return Err(ExprErr::NoLhs( + loc, + "array[].push(..) was not an array to push to" + .to_string(), + )); + }; + if matches!(array, ExprRet::CtxKilled(_)) { + ctx.push_expr(array, analyzer).into_expr_err(loc)?; + return Ok(()); + } + analyzer.parse_ctx_expr(&input_exprs[1], ctx)?; + analyzer.apply_to_edges(ctx, loc, &|analyzer, ctx, loc| { + let Some(new_elem) = ctx + .pop_expr_latest(loc, analyzer) + .into_expr_err(loc)? + else { + return Err(ExprErr::NoRhs( + loc, + "array[].push(..) was not given an element to push" + .to_string(), + )); + }; + + if matches!(new_elem, ExprRet::CtxKilled(_)) { + ctx.push_expr(new_elem, analyzer).into_expr_err(loc)?; + return Ok(()); + } + + let arr = array.expect_single().into_expr_err(loc)?; + let arr = + ContextVarNode::from(arr).latest_version(analyzer); + // get length + let len = analyzer.tmp_length(arr, ctx, loc); + + let len_as_idx = + len.as_tmp(loc, ctx, analyzer).into_expr_err(loc)?; + // set length as index + analyzer.index_into_array_inner( + ctx, + loc, + ExprRet::Single(arr.latest_version(analyzer).into()), + ExprRet::Single( + len_as_idx.latest_version(analyzer).into(), + ), + )?; + let index = ctx + .pop_expr_latest(loc, analyzer) + .into_expr_err(loc)? + .unwrap(); + if matches!(index, ExprRet::CtxKilled(_)) { + ctx.push_expr(index, analyzer).into_expr_err(loc)?; + return Ok(()); + } + // assign index to new_elem + analyzer.match_assign_sides(ctx, loc, &index, &new_elem) + }) + }) + } else { + return Err(ExprErr::InvalidFunctionInput( + loc, + format!( + "array[].push(..) expected 0 or 1 inputs, got: {}", + input_exprs.len() + ), + )); + } + } + "pop" => { + if input_exprs.len() != 1 { + return Err(ExprErr::InvalidFunctionInput( + loc, + format!( + "array[].pop() expected 0 inputs, got: {}", + input_exprs.len() + ), + )); + } + self.parse_ctx_expr(&input_exprs[0], ctx)?; + self.apply_to_edges(ctx, loc, &|analyzer, ctx, loc| { + let Some(array) = + ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? + else { + return Err(ExprErr::NoLhs( + loc, + "array[].pop() was not an array to pop from".to_string(), + )); + }; + if matches!(array, ExprRet::CtxKilled(_)) { + ctx.push_expr(array, analyzer).into_expr_err(loc)?; + return Ok(()); + } + + // get the array + let arr = array.expect_single().into_expr_err(loc)?; + let arr = ContextVarNode::from(arr).latest_version(analyzer); + + // get length + analyzer.match_length(ctx, loc, array, false)?; + let Some(len) = + ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? + else { + return Err(ExprErr::NoLhs( + loc, + "array[].pop() was not an array to pop from".to_string(), + )); + }; + let len = len.expect_single().into_expr_err(loc)?; + let next_len = analyzer.advance_var_in_ctx(len.into(), loc, ctx)?; + next_len + .set_range_min( + analyzer, + Elem::from(len) - Elem::from(Concrete::from(U256::from(1))), + ) + .into_expr_err(loc)?; + next_len + .set_range_max( + analyzer, + Elem::from(len) - Elem::from(Concrete::from(U256::from(1))), + ) + .into_expr_err(loc)?; + + // set length as index + analyzer.index_into_array_inner( + ctx, + loc, + ExprRet::Single(arr.latest_version(analyzer).into()), + ExprRet::Single(next_len.latest_version(analyzer).into()), + ) + }) + } + _ => Err(ExprErr::FunctionNotFound( + loc, + format!( + "Could not find builtin array function: \"{func_name}\", context: {}", + ctx.path(self), + ) + )) + } + } +} + + diff --git a/crates/solc-expressions/src/func_call/intrinsic_call/block.rs b/crates/solc-expressions/src/func_call/intrinsic_call/block.rs new file mode 100644 index 00000000..5757b0ff --- /dev/null +++ b/crates/solc-expressions/src/func_call/intrinsic_call/block.rs @@ -0,0 +1,55 @@ +use crate::{ + ContextBuilder, ExprErr, IntoExprErr, +}; + +use graph::{ + nodes::{ + Builtin, ContextNode, ContextVar, ExprRet, + }, + AnalyzerBackend, Node, +}; + +use solang_parser::pt::{Expression, Loc}; + +impl BlockCaller for T where T: AnalyzerBackend + Sized {} +pub trait BlockCaller: AnalyzerBackend + Sized { + fn block_call(&mut self, func_name: String, input_exprs: &[Expression], loc: Loc, ctx: ContextNode) -> Result<(), ExprErr> { + match &*func_name { + "blockhash" => { + self.parse_ctx_expr(&input_exprs[0], ctx)?; + self.apply_to_edges(ctx, loc, &|analyzer, ctx, loc| { + let Some(input) = + ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? + else { + return Err(ExprErr::NoRhs( + loc, + "blockhash function was not provided a block number" + .to_string(), + )); + }; + if matches!(input, ExprRet::CtxKilled(_)) { + ctx.push_expr(input, analyzer).into_expr_err(loc)?; + return Ok(()); + } + let var = ContextVar::new_from_builtin( + loc, + analyzer.builtin_or_add(Builtin::Bytes(32)).into(), + analyzer, + ) + .into_expr_err(loc)?; + let cvar = analyzer.add_node(Node::ContextVar(var)); + ctx.push_expr(ExprRet::Single(cvar), analyzer) + .into_expr_err(loc)?; + Ok(()) + }) + } + _ => Err(ExprErr::FunctionNotFound( + loc, + format!( + "Could not find builtin block function: \"{func_name}\", context: {}", + ctx.path(self), + ) + )) + } + } +} \ No newline at end of file diff --git a/crates/solc-expressions/src/func_call/intrinsic_call/constructors.rs b/crates/solc-expressions/src/func_call/intrinsic_call/constructors.rs new file mode 100644 index 00000000..bfa88e5e --- /dev/null +++ b/crates/solc-expressions/src/func_call/intrinsic_call/constructors.rs @@ -0,0 +1,197 @@ +use crate::{ + ContextBuilder, ExprErr, FuncCaller, IntoExprErr, +}; + +use graph::{ + elem::*, + nodes::{ + ContextNode, ContextVar, ContextVarNode, ExprRet, + StructNode, + }, + AnalyzerBackend, ContextEdge, Edge, Node, Range, VarType, +}; +use shared::NodeIdx; + +use solang_parser::pt::{Expression, Loc}; + +impl ConstructorCaller for T where T: AnalyzerBackend + Sized {} +pub trait ConstructorCaller: AnalyzerBackend + Sized { + fn construct_array(&mut self, func_idx: NodeIdx, input_exprs: &[Expression], loc: Loc, ctx: ContextNode) -> Result<(), ExprErr> { + // create a new list + self.parse_ctx_expr(&input_exprs[0], ctx)?; + self.apply_to_edges(ctx, loc, &|analyzer, ctx, loc| { + let Some(len_var) = ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? + else { + return Err(ExprErr::NoRhs(loc, "Array creation failed".to_string())); + }; + + if matches!(len_var, ExprRet::CtxKilled(_)) { + ctx.push_expr(len_var, analyzer).into_expr_err(loc)?; + return Ok(()); + } + let len_cvar = len_var.expect_single().into_expr_err(loc)?; + + let ty = VarType::try_from_idx(analyzer, func_idx); + + let new_arr = ContextVar { + loc: Some(loc), + name: format!("tmp_arr{}", ctx.new_tmp(analyzer).into_expr_err(loc)?), + display_name: "arr".to_string(), + storage: None, + is_tmp: true, + is_symbolic: false, + is_return: false, + tmp_of: None, + ty: ty.expect("No type for node"), + }; + + let arr = ContextVarNode::from(analyzer.add_node(Node::ContextVar(new_arr))); + + let len_var = ContextVar { + loc: Some(loc), + name: arr.name(analyzer).into_expr_err(loc)? + ".length", + display_name: arr.display_name(analyzer).unwrap() + ".length", + storage: None, + is_tmp: true, + tmp_of: None, + is_symbolic: true, + is_return: false, + ty: ContextVarNode::from(len_cvar) + .underlying(analyzer) + .into_expr_err(loc)? + .ty + .clone(), + }; + + let len_cvar = analyzer.add_node(Node::ContextVar(len_var)); + analyzer.add_edge(arr, ctx, Edge::Context(ContextEdge::Variable)); + ctx.add_var(arr, analyzer).into_expr_err(loc)?; + analyzer.add_edge(len_cvar, ctx, Edge::Context(ContextEdge::Variable)); + ctx.add_var(len_cvar.into(), analyzer).into_expr_err(loc)?; + analyzer.add_edge(len_cvar, arr, Edge::Context(ContextEdge::AttrAccess)); + + // update the length + if let Some(r) = arr.ref_range(analyzer).into_expr_err(loc)? { + let min = r.evaled_range_min(analyzer).into_expr_err(loc)?; + let max = r.evaled_range_max(analyzer).into_expr_err(loc)?; + + if let Some(mut rd) = min.maybe_range_dyn() { + rd.len = Elem::from(len_cvar); + arr.set_range_min(analyzer, Elem::ConcreteDyn(Box::new(rd))) + .into_expr_err(loc)?; + } + + if let Some(mut rd) = max.maybe_range_dyn() { + rd.len = Elem::from(len_cvar); + arr.set_range_min(analyzer, Elem::ConcreteDyn(Box::new(rd))) + .into_expr_err(loc)?; + } + } + + ctx.push_expr(ExprRet::Single(arr.into()), analyzer) + .into_expr_err(loc)?; + Ok(()) + }) + } + + fn construct_contract(&mut self, func_idx: NodeIdx, input_exprs: &[Expression], loc: Loc, ctx: ContextNode) -> Result<(), ExprErr> { + // construct a new contract + if !input_exprs.is_empty() { + self.parse_ctx_expr(&input_exprs[0], ctx)?; + } + self.apply_to_edges(ctx, loc, &|analyzer, ctx, loc| { + if !input_exprs.is_empty() { + let Some(ret) = ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? + else { + return Err(ExprErr::NoRhs( + loc, + "Contract creation failed".to_string(), + )); + }; + if matches!(ret, ExprRet::CtxKilled(_)) { + ctx.push_expr(ret, analyzer).into_expr_err(loc)?; + return Ok(()); + } + } + + let var = match ContextVar::maybe_from_user_ty(analyzer, loc, func_idx) { + Some(v) => v, + None => { + return Err(ExprErr::VarBadType( + loc, + format!( + "Could not create context variable from user type: {:?}", + analyzer.node(func_idx) + ), + )) + } + }; + // let idx = ret.expect_single().into_expr_err(loc)?; + let contract_cvar = + ContextVarNode::from(analyzer.add_node(Node::ContextVar(var))); + // contract_cvar + // .set_range_min(analyzer, Elem::from(idx)) + // .into_expr_err(loc)?; + // contract_cvar + // .set_range_max(analyzer, Elem::from(idx)) + // .into_expr_err(loc)?; + ctx.push_expr(ExprRet::Single(contract_cvar.into()), analyzer) + .into_expr_err(loc) + }) + } + + fn construct_struct(&mut self, func_idx: NodeIdx, input_exprs: &[Expression], loc: Loc, ctx: ContextNode) -> Result<(), ExprErr> { + // struct construction + let strukt = StructNode::from(func_idx); + let var = + ContextVar::new_from_struct(loc, strukt, ctx, self).into_expr_err(loc)?; + let cvar = self.add_node(Node::ContextVar(var)); + ctx.add_var(cvar.into(), self).into_expr_err(loc)?; + self.add_edge(cvar, ctx, Edge::Context(ContextEdge::Variable)); + + self.parse_inputs(ctx, loc, input_exprs)?; + self.apply_to_edges(ctx, loc, &|analyzer, ctx, loc| { + let Some(inputs) = ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? + else { + return Err(ExprErr::NoRhs( + loc, + "Struct Function call failed".to_string(), + )); + }; + + let inputs = inputs.as_vec(); + // set struct fields + strukt + .fields(analyzer) + .iter() + .zip(inputs) + .try_for_each(|(field, input)| { + let field_cvar = ContextVar::maybe_new_from_field( + analyzer, + loc, + ContextVarNode::from(cvar) + .underlying(analyzer) + .into_expr_err(loc)?, + field.underlying(analyzer).unwrap().clone(), + ) + .expect("Invalid struct field"); + + let fc_node = analyzer.add_node(Node::ContextVar(field_cvar)); + analyzer.add_edge( + fc_node, + cvar, + Edge::Context(ContextEdge::AttrAccess), + ); + analyzer.add_edge(fc_node, ctx, Edge::Context(ContextEdge::Variable)); + ctx.add_var(fc_node.into(), analyzer).into_expr_err(loc)?; + let field_as_ret = ExprRet::Single(fc_node); + analyzer.match_assign_sides(ctx, loc, &field_as_ret, &input)?; + let _ = ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)?; + Ok(()) + })?; + + ctx.push_expr(ExprRet::Single(cvar), analyzer) + .into_expr_err(loc) + }) + } +} \ No newline at end of file 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 new file mode 100644 index 00000000..93e0d859 --- /dev/null +++ b/crates/solc-expressions/src/func_call/intrinsic_call/dyn_builtin.rs @@ -0,0 +1,212 @@ +use crate::{ + ContextBuilder, ExprErr, IntoExprErr, +}; + +use graph::{ + nodes::{ + Builtin, Concrete, ContextNode, ContextVarNode, ExprRet, + }, + AnalyzerBackend, Node, SolcRange, VarType, +}; + +use solang_parser::pt::{Expression, Loc}; + +impl DynBuiltinCaller for T where T: AnalyzerBackend + Sized {} +pub trait DynBuiltinCaller: AnalyzerBackend + Sized { + fn dyn_builtin_call(&mut self, func_name: String, input_exprs: &[Expression], loc: Loc, ctx: ContextNode) -> Result<(), ExprErr> { + match &*func_name { + "concat" => self.concat(&loc, input_exprs, ctx), + _ => Err(ExprErr::FunctionNotFound( + loc, + format!( + "Could not find builtin dynamic builtin function: \"{func_name}\", context: {}", + ctx.path(self), + ) + )) + } + } + + + #[tracing::instrument(level = "trace", skip_all)] + fn concat( + &mut self, + loc: &Loc, + input_exprs: &[Expression], + ctx: ContextNode, + ) -> Result<(), ExprErr> { + input_exprs[1..].iter().try_for_each(|expr| { + self.parse_ctx_expr(expr, ctx)?; + self.apply_to_edges(ctx, *loc, &|analyzer, ctx, loc| { + let input = ctx + .pop_expr_latest(loc, analyzer) + .into_expr_err(loc)? + .unwrap_or(ExprRet::Null); + ctx.append_tmp_expr(input, analyzer).into_expr_err(loc) + }) + })?; + + self.apply_to_edges(ctx, *loc, &|analyzer, ctx, loc| { + let Some(inputs) = ctx.pop_tmp_expr(loc, analyzer).into_expr_err(loc)? else { + return Err(ExprErr::NoRhs(loc, "Concatenation failed".to_string())); + }; + if matches!(inputs, ExprRet::CtxKilled(_)) { + ctx.push_expr(inputs, analyzer).into_expr_err(loc)?; + return Ok(()); + } + let inputs = inputs.as_vec(); + if inputs.is_empty() { + ctx.push_expr(ExprRet::Multi(vec![]), analyzer) + .into_expr_err(loc)?; + Ok(()) + } else { + let start = &inputs[0]; + if inputs.len() > 1 { + analyzer.match_concat(ctx, loc, start.clone(), &inputs[1..], None) + } else { + analyzer.match_concat(ctx, loc, start.clone(), &[], None) + } + } + }) + } + + fn match_concat( + &mut self, + ctx: ContextNode, + loc: Loc, + curr: ExprRet, + inputs: &[ExprRet], + accum_node: Option, + ) -> Result<(), ExprErr> { + if let Some(accum_node) = accum_node { + match curr.flatten() { + ExprRet::Single(var) | ExprRet::SingleLiteral(var) => { + self.concat_inner(loc, accum_node, ContextVarNode::from(var))?; + ctx.push_expr(ExprRet::Single(accum_node.into()), self) + .into_expr_err(loc)?; + Ok(()) + } + ExprRet::Null => { + ctx.push_expr(ExprRet::Single(accum_node.into()), self) + .into_expr_err(loc)?; + Ok(()) + } + ExprRet::Multi(inner) => inner + .into_iter() + .try_for_each(|i| self.match_concat(ctx, loc, i, inputs, Some(accum_node))), + ExprRet::CtxKilled(kind) => ctx.kill(self, loc, kind).into_expr_err(loc), + } + } else { + match curr.flatten() { + ExprRet::Single(var) | ExprRet::SingleLiteral(var) => { + let acc = ContextVarNode::from(var) + .as_tmp(loc, ctx, self) + .into_expr_err(loc)?; + inputs + .iter() + .map(|i| self.match_concat(ctx, loc, i.clone(), inputs, Some(acc))) + .collect::, ExprErr>>()?; + ctx.push_expr(ExprRet::Single(acc.into()), self) + .into_expr_err(loc)?; + Ok(()) + } + ExprRet::Null => Err(ExprErr::NoRhs( + loc, + "No input provided to concat function".to_string(), + )), + ExprRet::Multi(inner) => inner + .into_iter() + .try_for_each(|i| self.match_concat(ctx, loc, i, inputs, None)), + ExprRet::CtxKilled(kind) => ctx.kill(self, loc, kind).into_expr_err(loc), + } + } + } + + fn concat_inner( + &mut self, + loc: Loc, + accum: ContextVarNode, + right: ContextVarNode, + ) -> Result<(), ExprErr> { + match ( + accum.ty(self).into_expr_err(loc)?, + right.ty(self).into_expr_err(loc)?, + ) { + (VarType::Concrete(accum_cnode), VarType::Concrete(right_cnode)) => { + let new_ty = match ( + accum_cnode.underlying(self).into_expr_err(loc)?, + right_cnode.underlying(self).into_expr_err(loc)?, + ) { + (accum_node @ Concrete::String(..), right_node @ Concrete::String(..)) => { + let new_val = accum_node.clone().concat(right_node).unwrap(); + let new_cnode = self.add_node(Node::Concrete(new_val)); + VarType::Concrete(new_cnode.into()) + } + (accum_node @ Concrete::DynBytes(..), right_node @ Concrete::DynBytes(..)) => { + let new_val = accum_node.clone().concat(right_node).unwrap(); + let new_cnode = self.add_node(Node::Concrete(new_val)); + VarType::Concrete(new_cnode.into()) + } + (a, b) => { + // Invalid solidity + return Err(ExprErr::InvalidFunctionInput(loc, format!("Type mismatch: {a:?} for left hand side and type: {b:?} for right hand side"))); + } + }; + accum.underlying_mut(self).into_expr_err(loc)?.ty = new_ty; + Ok(()) + } + (VarType::Concrete(accum_cnode), VarType::BuiltIn(_bn, Some(r2))) => { + let underlying = accum_cnode.underlying(self).into_expr_err(loc)?; + // let val = match underlying { + // Concrete::String(val) => { + // val + // .chars() + // .enumerate() + // .map(|(i, v)| { + // let idx = Elem::from(Concrete::from(U256::from(i))); + // let mut bytes = [0x00; 32]; + // v.encode_utf8(&mut bytes[..]); + // let v = Elem::from(Concrete::Bytes(1, H256::from(bytes))); + // (idx, v) + // }) + // .collect::>() + // } + // Concrete::DynBytes(val) => { + // val + // .iter() + // .enumerate() + // .map(|(i, v)| { + // let idx = Elem::from(Concrete::from(U256::from(i))); + // let mut bytes = [0x00; 32]; + // bytes[0] = *v; + // let v = Elem::from(Concrete::Bytes(1, H256::from(bytes))); + // (idx, v) + // }) + // .collect::>() + // } + // b => return Err(ExprErr::InvalidFunctionInput(loc, format!("Type mismatch: expected String or Bytes for concat input but found: {b:?}"))) + // }; + // TODO: Extend with bn + + let range = SolcRange::from(underlying.clone()).unwrap(); + let min = range.min.clone().concat(r2.min.clone()); + let max = range.max.clone().concat(r2.max.clone()); + accum.set_range_min(self, min).into_expr_err(loc)?; + accum.set_range_max(self, max).into_expr_err(loc)?; + + let new_ty = + VarType::BuiltIn(self.builtin_or_add(Builtin::String).into(), Some(range)); + accum.underlying_mut(self).into_expr_err(loc)?.ty = new_ty; + Ok(()) + } + (VarType::BuiltIn(_bn, Some(r)), VarType::BuiltIn(_bn2, Some(r2))) => { + // TODO: improve length calculation here + let min = r.min.clone().concat(r2.min.clone()); + let max = r.max.clone().concat(r2.max.clone()); + accum.set_range_min(self, min).into_expr_err(loc)?; + accum.set_range_max(self, max).into_expr_err(loc)?; + Ok(()) + } + (_, _) => Ok(()), + } + } +} \ No newline at end of file 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 new file mode 100644 index 00000000..4ebd7f7d --- /dev/null +++ b/crates/solc-expressions/src/func_call/intrinsic_call/intrinsic_caller.rs @@ -0,0 +1,154 @@ +use crate::{ + ContextBuilder, ExprErr, FuncCaller, IntoExprErr, + intrinsic_call::{ + MsgCaller, AbiCaller, AddressCaller, ArrayCaller, + BlockCaller, DynBuiltinCaller, PrecompileCaller, + SolidityCaller, TypesCaller, ConstructorCaller + } +}; + +use graph::{ + nodes::{ + Builtin, ContextNode, ExprRet, + }, + AnalyzerBackend, Node, +}; +use shared::{NodeIdx}; + +use solang_parser::pt::{Expression, Loc}; + +pub trait CallerParts: AbiCaller + AddressCaller + ArrayCaller + + BlockCaller + DynBuiltinCaller + PrecompileCaller + + SolidityCaller + TypesCaller + ConstructorCaller + MsgCaller {} + +impl CallerParts for T where T: AbiCaller + AddressCaller + ArrayCaller + + BlockCaller + DynBuiltinCaller + PrecompileCaller + + SolidityCaller + TypesCaller + ConstructorCaller + MsgCaller {} + + + +impl IntrinsicFuncCaller for T where + T: AnalyzerBackend + Sized + CallerParts +{ +} +pub trait IntrinsicFuncCaller: + AnalyzerBackend + Sized + CallerParts +{ + /// Calls an intrinsic/builtin function call (casts, require, etc.) + #[tracing::instrument(level = "trace", skip_all)] + fn intrinsic_func_call( + &mut self, + loc: &Loc, + input_exprs: &[Expression], + func_idx: NodeIdx, + ctx: ContextNode, + ) -> Result<(), ExprErr> { + match self.node(func_idx) { + Node::Function(underlying) => { + if let Some(func_name) = &underlying.name { + match &*func_name.name { + // abi + _ if func_name.name.starts_with("abi.") => { + self.abi_call(func_name.name.clone(), input_exprs, *loc, ctx) + } + // address + "delegatecall" | "staticcall" | "call" | "code" | "balance" => { + self.address_call(func_name.name.clone(), input_exprs, *loc, ctx) + } + // array + "push" | "pop" => { + self.array_call(func_name.name.clone(), input_exprs, *loc, ctx) + } + // block + "blockhash" => { + self.block_call(func_name.name.clone(), input_exprs, *loc, ctx) + } + // dynamic sized builtins + "concat" => { + self.dyn_builtin_call(func_name.name.clone(), input_exprs, *loc, ctx) + } + // msg + "gasleft" => { + self.msg_call(func_name.name.clone(), input_exprs, *loc, ctx) + } + // precompiles + "sha256" | "ripemd160" | "ecrecover" => { + self.precompile_call(func_name.name.clone(), func_idx, input_exprs, *loc, ctx) + } + // solidity + "keccak256" | "addmod" | "mulmod" | "require" | "assert" => { + self.solidity_call(func_name.name.clone(), input_exprs, *loc, ctx) + } + // typing + "type" | "wrap" | "unwrap" => { + self.types_call(func_name.name.clone(), input_exprs, *loc, ctx) + } + e => Err(ExprErr::Todo( + *loc, + format!("builtin function: {e:?} doesn't exist or isn't implemented"), + )), + } + } else { + panic!("unnamed builtin?") + } + } + Node::Builtin(Builtin::Array(_)) => { + // construct a new array + self.construct_array(func_idx, input_exprs, *loc, ctx) + } + Node::Contract(_) => { + // construct a new contract + self.construct_contract(func_idx, input_exprs, *loc, ctx) + } + Node::Struct(_) => { + // construct a struct + self.construct_struct(func_idx, input_exprs, *loc, ctx) + } + Node::Builtin(ty) => { + // cast to type + self.cast(ty.clone(), func_idx, input_exprs, *loc, ctx) + } + Node::ContextVar(_c) => { + // its a user type, just push it onto the stack + ctx.push_expr(ExprRet::Single(func_idx), self) + .into_expr_err(*loc)?; + Ok(()) + } + Node::Unresolved(_) => { + // Try to give a nice error + self.parse_inputs(ctx, *loc, input_exprs)?; + + self.apply_to_edges(ctx, *loc, &|analyzer, ctx, loc| { + let Some(inputs) = ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? else { + return Err(ExprErr::NoRhs(loc, "Function call failed".to_string())) + }; + + if matches!(inputs, ExprRet::CtxKilled(_)) { + ctx.push_expr(inputs, analyzer).into_expr_err(loc)?; + return Ok(()); + } + let visible_funcs = ctx.visible_funcs(analyzer).into_expr_err(loc)? + .iter() + .map(|func| func.name(analyzer).unwrap()) + .collect::>(); + + if let Node::Unresolved(ident) = analyzer.node(func_idx) { + Err(ExprErr::FunctionNotFound( + loc, + format!( + "Could not find function: \"{}{}\", context: {}, visible functions: {:#?}", + ident.name, + inputs.try_as_func_input_str(analyzer), + ctx.path(analyzer), + visible_funcs + ) + )) + } else { + unreachable!() + } + }) + } + e => Err(ExprErr::FunctionNotFound(*loc, format!("Unhandled function call type: {e:?}"))), + } + } +} diff --git a/crates/solc-expressions/src/func_call/intrinsic_call/mod.rs b/crates/solc-expressions/src/func_call/intrinsic_call/mod.rs new file mode 100644 index 00000000..d3a50e38 --- /dev/null +++ b/crates/solc-expressions/src/func_call/intrinsic_call/mod.rs @@ -0,0 +1,23 @@ +mod abi; +mod address; +mod array; +mod block; +mod constructors; +mod dyn_builtin; +mod intrinsic_caller; +mod msg; +mod precompile; +mod solidity; +mod types; + +pub use abi::*; +pub use address::*; +pub use array::*; +pub use block::*; +pub use constructors::*; +pub use dyn_builtin::*; +pub use intrinsic_caller::*; +pub use msg::*; +pub use precompile::*; +pub use solidity::*; +pub use types::*; \ No newline at end of file diff --git a/crates/solc-expressions/src/func_call/intrinsic_call/msg.rs b/crates/solc-expressions/src/func_call/intrinsic_call/msg.rs new file mode 100644 index 00000000..249679d8 --- /dev/null +++ b/crates/solc-expressions/src/func_call/intrinsic_call/msg.rs @@ -0,0 +1,39 @@ +use crate::{ + ExprErr, IntoExprErr, +}; + +use graph::{ + nodes::{ + Builtin, ContextNode, ContextVar, ExprRet, + }, + AnalyzerBackend, Node, +}; + +use solang_parser::pt::{Expression, Loc}; + +impl MsgCaller for T where T: AnalyzerBackend + Sized {} +pub trait MsgCaller: AnalyzerBackend + Sized { + fn msg_call(&mut self, func_name: String, _input_exprs: &[Expression], loc: Loc, ctx: ContextNode) -> Result<(), ExprErr> { + match &*func_name { + "gasleft" => { + let var = ContextVar::new_from_builtin( + loc, + self.builtin_or_add(Builtin::Uint(64)).into(), + self, + ) + .into_expr_err(loc)?; + let cvar = self.add_node(Node::ContextVar(var)); + ctx.push_expr(ExprRet::Single(cvar), self) + .into_expr_err(loc)?; + Ok(()) + } + _ => Err(ExprErr::FunctionNotFound( + loc, + format!( + "Could not find builtin msg function: \"{func_name}\", context: {}", + ctx.path(self), + ) + )) + } + } +} \ No newline at end of file diff --git a/crates/solc-expressions/src/func_call/intrinsic_call/precompile.rs b/crates/solc-expressions/src/func_call/intrinsic_call/precompile.rs new file mode 100644 index 00000000..0e8e1e0e --- /dev/null +++ b/crates/solc-expressions/src/func_call/intrinsic_call/precompile.rs @@ -0,0 +1,218 @@ +use crate::{ + ContextBuilder, ExprErr, FuncCaller, IntoExprErr, +}; + +use graph::{ + nodes::{ + Builtin, Context, ContextNode, ContextVar, ContextVarNode, ExprRet, + }, + AnalyzerBackend, ContextEdge, Edge, Node, +}; +use shared::{NodeIdx}; + +use solang_parser::pt::{Expression, Loc}; + +impl PrecompileCaller for T where T: AnalyzerBackend + Sized {} +pub trait PrecompileCaller: AnalyzerBackend + Sized { + fn precompile_call(&mut self, func_name: String, func_idx: NodeIdx, input_exprs: &[Expression], loc: Loc, ctx: ContextNode) -> Result<(), ExprErr> { + match &*func_name { + "sha256" => { + self.parse_ctx_expr(&input_exprs[0], ctx)?; + self.apply_to_edges(ctx, loc, &|analyzer, ctx, loc| { + let Some(input) = + ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? + else { + return Err(ExprErr::NoRhs( + loc, + "abi.decode was not given the types for decoding" + .to_string(), + )); + }; + if matches!(input, ExprRet::CtxKilled(_)) { + ctx.push_expr(input, analyzer).into_expr_err(loc)?; + return Ok(()); + } + let var = ContextVar::new_from_builtin( + loc, + analyzer.builtin_or_add(Builtin::Bytes(32)).into(), + analyzer, + ) + .into_expr_err(loc)?; + let cvar = analyzer.add_node(Node::ContextVar(var)); + ctx.push_expr(ExprRet::Single(cvar), analyzer) + .into_expr_err(loc)?; + Ok(()) + }) + } + "ripemd160" => { + self.parse_ctx_expr(&input_exprs[0], ctx)?; + self.apply_to_edges(ctx, loc, &|analyzer, ctx, loc| { + let Some(input) = + ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? + else { + return Err(ExprErr::NoRhs( + loc, + "abi.decode was not given the types for decoding" + .to_string(), + )); + }; + if matches!(input, ExprRet::CtxKilled(_)) { + ctx.push_expr(input, analyzer).into_expr_err(loc)?; + return Ok(()); + } + let var = ContextVar::new_from_builtin( + loc, + analyzer.builtin_or_add(Builtin::Bytes(32)).into(), + analyzer, + ) + .into_expr_err(loc)?; + let cvar = analyzer.add_node(Node::ContextVar(var)); + ctx.push_expr(ExprRet::Single(cvar), analyzer) + .into_expr_err(loc)?; + Ok(()) + }) + } + "ecrecover" => { + self.parse_inputs(ctx, loc, input_exprs)?; + + self.apply_to_edges(ctx, loc, &|analyzer, ctx, loc| { + let cctx = Context::new_subctx( + ctx, + None, + loc, + None, + Some(func_idx.into()), + true, + analyzer, + None, + ) + .into_expr_err(loc)?; + let call_ctx = analyzer.add_node(Node::Context(cctx)); + ctx.set_child_call(call_ctx.into(), analyzer) + .into_expr_err(loc)?; + let call_node = analyzer.add_node(Node::FunctionCall); + analyzer.add_edge( + call_node, + func_idx, + Edge::Context(ContextEdge::Call), + ); + analyzer.add_edge( + call_node, + ctx, + Edge::Context(ContextEdge::Subcontext), + ); + analyzer.add_edge( + call_ctx, + call_node, + Edge::Context(ContextEdge::Subcontext), + ); + + let Some(input) = + ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? + else { + return Err(ExprErr::NoRhs( + loc, + "ecrecover did not receive inputs".to_string(), + )); + }; + + if matches!(input, ExprRet::CtxKilled(_)) { + ctx.push_expr(input, analyzer).into_expr_err(loc)?; + return Ok(()); + } + + let mut inner_vals = vec![]; + match input { + ExprRet::Single(var) | ExprRet::SingleLiteral(var) => { + inner_vals.push( + ContextVarNode::from(var) + .display_name(analyzer) + .unwrap(), + ); + } + _ => inner_vals.push("".to_string()), + } + let inner_name = + inner_vals.into_iter().collect::>().join(", "); + let mut var = ContextVar::new_from_builtin( + loc, + analyzer.builtin_or_add(Builtin::Address).into(), + analyzer, + ) + .into_expr_err(loc)?; + var.display_name = format!("ecrecover({})", inner_name); + var.is_symbolic = true; + var.is_return = true; + let cvar = analyzer.add_node(Node::ContextVar(var)); + ctx.add_var(cvar.into(), analyzer).into_expr_err(loc)?; + analyzer.add_edge( + cvar, + call_ctx, + Edge::Context(ContextEdge::Variable), + ); + analyzer.add_edge( + cvar, + call_ctx, + Edge::Context(ContextEdge::Return), + ); + ContextNode::from(call_ctx) + .add_return_node(loc, cvar.into(), analyzer) + .into_expr_err(loc)?; + + let rctx = Context::new_subctx( + call_ctx.into(), + Some(ctx), + loc, + None, + None, + true, + analyzer, + None, + ) + .into_expr_err(loc)?; + let ret_ctx = analyzer.add_node(Node::Context(rctx)); + ContextNode::from(call_ctx) + .set_child_call(ret_ctx.into(), analyzer) + .into_expr_err(loc)?; + analyzer.add_edge( + ret_ctx, + call_ctx, + Edge::Context(ContextEdge::Continue), + ); + + let tmp_ret = ContextVarNode::from(cvar) + .as_tmp( + ContextNode::from(call_ctx) + .underlying(analyzer) + .unwrap() + .loc, + ret_ctx.into(), + analyzer, + ) + .unwrap(); + tmp_ret.underlying_mut(analyzer).unwrap().is_return = true; + tmp_ret.underlying_mut(analyzer).unwrap().display_name = + format!("ecrecover({}).return", inner_name); + ctx.add_var(tmp_ret, analyzer).into_expr_err(loc)?; + analyzer.add_edge( + tmp_ret, + ret_ctx, + Edge::Context(ContextEdge::Variable), + ); + + ContextNode::from(ret_ctx) + .push_expr(ExprRet::Single(tmp_ret.into()), analyzer) + .into_expr_err(loc)?; + Ok(()) + }) + } + _ => Err(ExprErr::FunctionNotFound( + loc, + format!( + "Could not find precompile function: \"{func_name}\", context: {}", + ctx.path(self), + ) + )) + } + } +} diff --git a/crates/solc-expressions/src/func_call/intrinsic_call/solidity.rs b/crates/solc-expressions/src/func_call/intrinsic_call/solidity.rs new file mode 100644 index 00000000..4b5e7edb --- /dev/null +++ b/crates/solc-expressions/src/func_call/intrinsic_call/solidity.rs @@ -0,0 +1,91 @@ +use crate::{ + require::Require, ContextBuilder, ExprErr, FuncCaller, IntoExprErr, +}; + +use graph::{ + nodes::{ + Builtin, ContextNode, ContextVar, ExprRet, + }, + AnalyzerBackend, Node, +}; + +use solang_parser::pt::{Expression, Loc}; + +impl SolidityCaller for T where T: AnalyzerBackend + Sized {} +pub trait SolidityCaller: AnalyzerBackend + Sized { + fn solidity_call(&mut self, func_name: String, input_exprs: &[Expression], loc: Loc, ctx: ContextNode) -> Result<(), ExprErr> { + match &*func_name { + "keccak256" => { + self.parse_ctx_expr(&input_exprs[0], ctx)?; + self.apply_to_edges(ctx, loc, &|analyzer, ctx, loc| { + let Some(_input) = + ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? + else { + return Err(ExprErr::NoRhs( + loc, + "abi.decode was not given the types for decoding" + .to_string(), + )); + }; + let var = ContextVar::new_from_builtin( + loc, + analyzer.builtin_or_add(Builtin::Bytes(32)).into(), + analyzer, + ) + .into_expr_err(loc)?; + let cvar = analyzer.add_node(Node::ContextVar(var)); + ctx.push_expr(ExprRet::Single(cvar), analyzer) + .into_expr_err(loc)?; + Ok(()) + }) + } + "addmod" => { + // TODO: actually calcuate this if possible + self.parse_inputs(ctx, loc, input_exprs)?; + + self.apply_to_edges(ctx, loc, &|analyzer, ctx, loc| { + ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)?; + let var = ContextVar::new_from_builtin( + loc, + analyzer.builtin_or_add(Builtin::Uint(256)).into(), + analyzer, + ) + .into_expr_err(loc)?; + let cvar = analyzer.add_node(Node::ContextVar(var)); + ctx.push_expr(ExprRet::Single(cvar), analyzer) + .into_expr_err(loc)?; + Ok(()) + }) + } + "mulmod" => { + // TODO: actually calcuate this if possible + self.parse_inputs(ctx, loc, input_exprs)?; + self.apply_to_edges(ctx, loc, &|analyzer, ctx, loc| { + ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)?; + let var = ContextVar::new_from_builtin( + loc, + analyzer.builtin_or_add(Builtin::Uint(256)).into(), + analyzer, + ) + .into_expr_err(loc)?; + let cvar = analyzer.add_node(Node::ContextVar(var)); + ctx.push_expr(ExprRet::Single(cvar), analyzer) + .into_expr_err(loc)?; + Ok(()) + }) + } + "require" | "assert" => { + self.apply_to_edges(ctx, loc, &|analyzer, ctx, _loc| { + analyzer.handle_require(input_exprs, ctx) + }) + } + _ => Err(ExprErr::FunctionNotFound( + loc, + format!( + "Could not find builtin solidity function: \"{func_name}\", context: {}", + ctx.path(self), + ) + )) + } + } +} \ No newline at end of file diff --git a/crates/solc-expressions/src/func_call/intrinsic_call/types.rs b/crates/solc-expressions/src/func_call/intrinsic_call/types.rs new file mode 100644 index 00000000..611d40d6 --- /dev/null +++ b/crates/solc-expressions/src/func_call/intrinsic_call/types.rs @@ -0,0 +1,223 @@ +use crate::{ + ContextBuilder, ExprErr, FuncCaller, IntoExprErr, +}; + +use graph::{ + elem::*, + nodes::{ + BuiltInNode, Builtin, ContextNode, ContextVar, ContextVarNode, ExprRet, TyNode, + }, + AnalyzerBackend, GraphBackend, Node, Range, SolcRange, VarType, +}; +use shared::{NodeIdx}; + +use solang_parser::pt::{Expression, Loc}; + +impl TypesCaller for T where T: AnalyzerBackend + Sized {} +pub trait TypesCaller: AnalyzerBackend + Sized { + fn types_call(&mut self, func_name: String, input_exprs: &[Expression], loc: Loc, ctx: ContextNode) -> Result<(), ExprErr> { + match &*func_name { + "type" => self.parse_ctx_expr(&input_exprs[0], ctx), + "wrap" => { + if input_exprs.len() != 2 { + return Err(ExprErr::InvalidFunctionInput(loc, format!("Expected a member type and an input to the wrap function, but got: {:?}", input_exprs))); + } + + self.parse_inputs(ctx, loc, input_exprs)?; + self.apply_to_edges(ctx, loc, &|analyzer, ctx, loc| { + let Some(input) = + ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? + else { + return Err(ExprErr::NoRhs( + loc, + "ecrecover did not receive inputs".to_string(), + )); + }; + input.expect_length(2).into_expr_err(loc)?; + let ret = input.as_vec(); + let wrapping_ty = ret[0].expect_single().into_expr_err(loc)?; + let var = ContextVar::new_from_ty( + loc, + TyNode::from(wrapping_ty), + ctx, + analyzer, + ) + .into_expr_err(loc)?; + let to_be_wrapped = ret[1].expect_single().into_expr_err(loc)?; + let cvar = + ContextVarNode::from(analyzer.add_node(Node::ContextVar(var))); + let next = analyzer.advance_var_in_ctx(cvar, loc, ctx)?; + let expr = Elem::Expr(RangeExpr::new( + Elem::from(to_be_wrapped), + RangeOp::Cast, + Elem::from(cvar), + )); + next.set_range_min(analyzer, expr.clone()) + .into_expr_err(loc)?; + next.set_range_max(analyzer, expr).into_expr_err(loc)?; + ctx.push_expr(ExprRet::Single(cvar.into()), analyzer) + .into_expr_err(loc) + }) + } + "unwrap" => { + self.parse_inputs(ctx, loc, input_exprs)?; + self.apply_to_edges(ctx, loc, &|analyzer, ctx, loc| { + let Some(input) = + ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? + else { + return Err(ExprErr::NoRhs( + loc, + "ecrecover did not receive inputs".to_string(), + )); + }; + input.expect_length(2).into_expr_err(loc)?; + let ret = input.as_vec(); + let wrapping_ty = ret[0].expect_single().into_expr_err(loc)?; + let mut var = ContextVar::new_from_builtin( + loc, + BuiltInNode::from( + TyNode::from(wrapping_ty) + .underlying(analyzer) + .into_expr_err(loc)? + .ty, + ), + analyzer, + ) + .into_expr_err(loc)?; + let to_be_unwrapped = ret[1].expect_single().into_expr_err(loc)?; + var.display_name = format!( + "{}.unwrap({})", + TyNode::from(wrapping_ty) + .name(analyzer) + .into_expr_err(loc)?, + ContextVarNode::from(to_be_unwrapped) + .display_name(analyzer) + .into_expr_err(loc)? + ); + + let cvar = + ContextVarNode::from(analyzer.add_node(Node::ContextVar(var))); + cvar.set_range_min(analyzer, Elem::from(to_be_unwrapped)) + .into_expr_err(loc)?; + cvar.set_range_max(analyzer, Elem::from(to_be_unwrapped)) + .into_expr_err(loc)?; + let next = analyzer.advance_var_in_ctx(cvar, loc, ctx)?; + let expr = Elem::Expr(RangeExpr::new( + Elem::from(to_be_unwrapped), + RangeOp::Cast, + Elem::from(cvar), + )); + next.set_range_min(analyzer, expr.clone()) + .into_expr_err(loc)?; + next.set_range_max(analyzer, expr).into_expr_err(loc)?; + ctx.push_expr(ExprRet::Single(cvar.into()), analyzer) + .into_expr_err(loc) + }) + } + _ => Err(ExprErr::FunctionNotFound( + loc, + format!( + "Could not find builtin types function: \"{func_name}\", context: {}", + ctx.path(self), + ) + )) + } + } + + fn cast(&mut self, ty: Builtin, func_idx: NodeIdx, input_exprs: &[Expression], loc: Loc, ctx: ContextNode) -> Result<(), ExprErr> { + // it is a cast + fn cast_match( + ctx: ContextNode, + loc: Loc, + analyzer: &mut (impl GraphBackend + AnalyzerBackend), + ty: &Builtin, + ret: ExprRet, + func_idx: NodeIdx, + ) -> Result<(), ExprErr> { + match ret { + ExprRet::CtxKilled(kind) => { + ctx.kill(analyzer, loc, kind).into_expr_err(loc) + } + ExprRet::Null => Ok(()), + ExprRet::Single(cvar) | ExprRet::SingleLiteral(cvar) => { + let new_var = ContextVarNode::from(cvar) + .as_cast_tmp(loc, ctx, ty.clone(), analyzer) + .into_expr_err(loc)?; + + new_var.underlying_mut(analyzer).into_expr_err(loc)?.ty = + VarType::try_from_idx(analyzer, func_idx).expect(""); + // cast the ranges + if let Some(r) = ContextVarNode::from(cvar) + .range(analyzer) + .into_expr_err(loc)? + { + let curr_range = + SolcRange::try_from_builtin(ty).expect("No default range"); + let mut min = r + .range_min() + .into_owned() + .cast(curr_range.range_min().into_owned()); + + min.cache_minimize(analyzer).into_expr_err(loc)?; + let mut max = r + .range_max() + .into_owned() + .cast(curr_range.range_max().into_owned()); + + max.cache_maximize(analyzer).into_expr_err(loc)?; + + let existing_max = + r.evaled_range_max(analyzer).into_expr_err(loc)?; + // Check if the max value has changed once the cast is applied. + // If it hasnt, then the cast had no effect and we should adjust the naming + // to not muddle the CLI + if let Some(std::cmp::Ordering::Equal) = max + .maximize(analyzer) + .into_expr_err(loc)? + .range_ord(&existing_max) + { + // its a noop, reflect that in the naming + new_var.underlying_mut(analyzer).unwrap().display_name = + ContextVarNode::from(cvar) + .display_name(analyzer) + .into_expr_err(loc)?; + } + + new_var.set_range_min(analyzer, min).into_expr_err(loc)?; + new_var.set_range_max(analyzer, max).into_expr_err(loc)?; + // cast the range exclusions - TODO: verify this is correct + let mut exclusions = r.range_exclusions(); + exclusions.iter_mut().for_each(|range| { + *range = + range.clone().cast(curr_range.range_min().into_owned()); + }); + new_var + .set_range_exclusions(analyzer, exclusions) + .into_expr_err(loc)?; + } + + ctx.push_expr(ExprRet::Single(new_var.into()), analyzer) + .into_expr_err(loc)?; + Ok(()) + } + ExprRet::Multi(inner) => inner + .into_iter() + .try_for_each(|i| cast_match(ctx, loc, analyzer, ty, i, func_idx)), + } + } + + self.parse_ctx_expr(&input_exprs[0], 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::NoRhs(loc, "Cast had no target type".to_string())); + }; + + if matches!(ret, ExprRet::CtxKilled(_)) { + ctx.push_expr(ret, analyzer).into_expr_err(loc)?; + return Ok(()); + } + + cast_match(ctx, loc, analyzer, &ty, ret, func_idx) + }) + } +} \ No newline at end of file