Skip to main content

chisel/
executor.rs

1//! Executor
2//!
3//! This module contains the execution logic for the [SessionSource].
4
5use crate::prelude::{ChiselDispatcher, ChiselResult, ChiselRunner, SessionSource, SolidityHelper};
6use alloy_dyn_abi::{DynSolType, DynSolValue};
7use alloy_json_abi::EventParam;
8use alloy_primitives::{Address, B256, U256, hex};
9use eyre::{Result, WrapErr};
10use foundry_compilers::Artifact;
11use foundry_evm::{
12    backend::Backend, decode::decode_console_logs, executors::ExecutorBuilder,
13    inspectors::CheatsConfig, traces::TraceMode,
14};
15use solar::{
16    ast::{BinOpKind, ElementaryType, FunctionKind, LitKind, StateMutability, StrKind, UnOpKind},
17    interface::Symbol,
18    sema::{
19        hir::{
20            ContractId, Event, Expr, ExprKind, Function, ItemId, Res, StmtKind, Type as HirType,
21            TypeKind, Visibility,
22        },
23        ty::{Gcx, Ty, TyKind},
24    },
25};
26use std::ops::ControlFlow;
27use yansi::Paint;
28
29/// Executor implementation for [SessionSource]
30impl SessionSource {
31    /// Runs the source with the [ChiselRunner]
32    pub async fn execute(&mut self) -> Result<ChiselResult> {
33        // Recompile the project and ensure no errors occurred.
34        let output = self.build()?;
35
36        let (bytecode, final_pc) = output.enter(|output| -> Result<_> {
37            let contract = output
38                .repl_contract()
39                .ok_or_else(|| eyre::eyre!("failed to find REPL contract"))?;
40            trace!(?contract, "REPL contract");
41            let bytecode = contract
42                .get_bytecode_bytes()
43                .ok_or_else(|| eyre::eyre!("No bytecode found for `REPL` contract"))?;
44            Ok((bytecode.into_owned(), output.final_pc(contract)?))
45        })?;
46        let final_pc = final_pc.unwrap_or_default();
47        let mut runner = self.build_runner(final_pc).await?;
48        runner.run(bytecode)
49    }
50
51    /// Inspect a contract element inside of the current session
52    ///
53    /// ### Takes
54    ///
55    /// A solidity snippet
56    ///
57    /// ### Returns
58    ///
59    /// If the input is valid `Ok((continue, formatted_output))` where:
60    /// - `continue` is true if the input should be appended to the source
61    /// - `formatted_output` is the formatted value, if any
62    pub async fn inspect(&self, input: &str) -> Result<(ControlFlow<()>, Option<String>)> {
63        let line = format!("bytes memory inspectoor = abi.encode({input});");
64        let mut source = match self.clone_with_new_line(line) {
65            Ok((source, _)) => source,
66            Err(err) => {
67                debug!(%err, "failed to build new source for inspection");
68                return Ok((ControlFlow::Continue(()), None));
69            }
70        };
71
72        let mut source_without_inspector = self.clone();
73
74        // Events and tuples fails compilation due to it not being able to be encoded in
75        // `inspectoor`. If that happens, try executing without the inspector.
76        let (mut res, err) = match source.execute().await {
77            Ok(res) => (res, None),
78            Err(err) => {
79                debug!(?err, %input, "execution failed");
80                match source_without_inspector.execute().await {
81                    Ok(res) => (res, Some(err)),
82                    Err(_) => {
83                        if self.config.foundry_config.verbosity >= 3 {
84                            sh_err!("Could not inspect: {err}")?;
85                        }
86                        return Ok((ControlFlow::Continue(()), None));
87                    }
88                }
89            }
90        };
91
92        // If abi-encoding the input failed, check whether it is an event
93        if let Some(err) = err {
94            let output = source_without_inspector.build()?;
95
96            let formatted_event = output.enter(|output| {
97                let gcx = output.gcx();
98                output.get_event(input).map(|eid| format_event_definition(gcx, gcx.hir.event(eid)))
99            });
100            if let Some(formatted_event) = formatted_event {
101                return Ok((ControlFlow::Break(()), Some(formatted_event?)));
102            }
103
104            // we were unable to check the event
105            if self.config.foundry_config.verbosity >= 3 {
106                sh_err!("Failed eval: {err}")?;
107            }
108
109            debug!(%err, %input, "failed abi encode input");
110            return Ok((ControlFlow::Break(()), None));
111        }
112        drop(source_without_inspector);
113
114        let Some((stack, memory)) = &res.state else {
115            // Show traces and logs, if there are any, and return an error
116            if let Ok(decoder) = ChiselDispatcher::decode_traces(&source.config, &mut res).await {
117                ChiselDispatcher::show_traces(&decoder, &mut res).await?;
118            }
119            let decoded_logs = decode_console_logs(&res.logs);
120            if !decoded_logs.is_empty() {
121                sh_println!("{}", "Logs:".green())?;
122                for log in decoded_logs {
123                    sh_println!("  {log}")?;
124                }
125            }
126
127            return Err(eyre::eyre!("Failed to inspect expression"));
128        };
129
130        // Either the expression referred to by `input`, or the last expression,
131        // which was wrapped in `abi.encode`.
132        let generated_output = source.build()?;
133
134        // Inside the compiler closure, infer the DynSolType of the inspected expression and
135        // determine whether the REPL should continue.
136        let res_ty = generated_output.enter(|out| -> Option<(bool, DynSolType)> {
137            let gcx = out.gcx();
138
139            // Try direct lookup of `input` as a named variable in the REPL contract.
140            if let Some(direct_ty) = lookup_named_variable_type(gcx, input) {
141                return Some((false, direct_ty));
142            }
143
144            // Otherwise, find the appended `bytes memory inspectoor = abi.encode(<input>);`
145            // and pull out the first call argument.
146            let block = out.run_func_body();
147            let last = block.last()?;
148            let StmtKind::DeclSingle(vid) = last.kind else { return None };
149            let var = gcx.hir.variable(vid);
150            let init = var.initializer?;
151            let ExprKind::Call(_callee, args, _) = &init.kind else { return None };
152            let inner_expr = args.exprs().next()?;
153
154            // If the call is `func()` returning a single value, prefer the function return type.
155            if let Some(ty) = get_function_return_type(gcx, inner_expr) {
156                return Some((should_continue(inner_expr), ty));
157            }
158
159            let ty = expr_to_dyn(gcx, inner_expr, true)?;
160            Some((should_continue(inner_expr), ty))
161        });
162
163        let Some((cont, ty)) = res_ty else {
164            return Ok((ControlFlow::Continue(()), None));
165        };
166
167        // the file compiled correctly, thus the last stack item must be the memory offset of
168        // the `bytes memory inspectoor` value
169        let data = (|| -> Option<_> {
170            let mut offset: usize = stack.last()?.try_into().ok()?;
171            debug!("inspect memory @ {offset}: {}", hex::encode(memory));
172            let mem_offset = memory.get(offset..offset + 32)?;
173            let len: usize = U256::try_from_be_slice(mem_offset)?.try_into().ok()?;
174            offset += 32;
175            memory.get(offset..offset + len)
176        })();
177        let Some(data) = data else {
178            eyre::bail!("Failed to inspect last expression: could not retrieve data from memory")
179        };
180        let token = ty.abi_decode(data).wrap_err("Could not decode inspected values")?;
181        let c = if cont { ControlFlow::Continue(()) } else { ControlFlow::Break(()) };
182        Ok((c, Some(format_token(token))))
183    }
184
185    async fn build_runner(&mut self, final_pc: usize) -> Result<ChiselRunner> {
186        let (evm_env, tx_env, fork_block) = self.config.evm_opts.env().await?;
187
188        let backend = match self.config.backend.clone() {
189            Some(backend) => backend,
190            None => {
191                let fork = self.config.evm_opts.get_fork(
192                    &self.config.foundry_config,
193                    evm_env.cfg_env.chain_id,
194                    fork_block,
195                );
196                let backend = Backend::spawn(fork)?;
197                self.config.backend = Some(backend.clone());
198                backend
199            }
200        };
201
202        let executor = ExecutorBuilder::default()
203            .inspectors(|stack| {
204                stack
205                    .logs(self.config.foundry_config.live_logs)
206                    .chisel_state(final_pc)
207                    .trace_mode(TraceMode::Call)
208                    .cheatcodes(
209                        CheatsConfig::new(
210                            &self.config.foundry_config,
211                            self.config.evm_opts.clone(),
212                            None,
213                            None,
214                            None,
215                        )
216                        .into(),
217                    )
218            })
219            .gas_limit(self.config.evm_opts.gas_limit())
220            .spec_id(self.config.foundry_config.evm_spec_id())
221            .legacy_assertions(self.config.foundry_config.legacy_assertions)
222            .build(evm_env, tx_env, backend);
223
224        Ok(ChiselRunner::new(executor, U256::MAX, Address::ZERO, self.config.calldata.clone()))
225    }
226}
227
228/// Looks up `name` as a named variable in the REPL contract (state variables or run() locals)
229/// and returns its type as a [`DynSolType`].
230///
231/// Only top-level statements of `run()` are scanned. Variables declared inside nested blocks
232/// (`if`, `for`, `while`, `unchecked`, etc.) are not visible here; the caller falls back to
233/// the `inspectoor`-based path for those cases.
234fn lookup_named_variable_type(gcx: Gcx<'_>, name: &str) -> Option<DynSolType> {
235    let hir = &gcx.hir;
236    let repl = hir.contracts().find(|c| c.name.as_str() == "REPL")?;
237
238    // State variables.
239    for vid in repl.variables() {
240        let var = hir.variable(vid);
241        if var.name.map(|n| n.as_str() == name).unwrap_or(false) {
242            return solar_ty_to_dyn(gcx, gcx.type_of_item(vid.into()));
243        }
244    }
245
246    // Locals declared in run().
247    let run_fid = repl
248        .functions()
249        .find(|&f| hir.function(f).name.as_ref().map(|n| n.as_str()) == Some("run"))?;
250    let body = hir.function(run_fid).body?;
251    for stmt in body.stmts {
252        match stmt.kind {
253            StmtKind::DeclSingle(vid) => {
254                let var = hir.variable(vid);
255                if var.name.map(|n| n.as_str() == name).unwrap_or(false) {
256                    return solar_ty_to_dyn(gcx, gcx.type_of_item(vid.into()));
257                }
258            }
259            StmtKind::DeclMulti(vids, _) => {
260                for vid in vids.iter().flatten() {
261                    let var = hir.variable(*vid);
262                    if var.name.map(|n| n.as_str() == name).unwrap_or(false) {
263                        return solar_ty_to_dyn(gcx, gcx.type_of_item((*vid).into()));
264                    }
265                }
266            }
267            _ => {}
268        }
269    }
270    None
271}
272
273/// Formats a value into an inspection message
274// TODO: Verbosity option
275fn format_token(token: DynSolValue) -> String {
276    match token {
277        DynSolValue::Address(a) => {
278            format!("Type: {}\n└ Data: {}", "address".red(), a.cyan())
279        }
280        DynSolValue::FixedBytes(b, byte_len) => {
281            format!(
282                "Type: {}\n└ Data: {}",
283                format!("bytes{byte_len}").red(),
284                hex::encode_prefixed(b).cyan()
285            )
286        }
287        DynSolValue::Int(i, bit_len) => {
288            format!(
289                "Type: {}\n├ Hex: {}\n├ Hex (full word): {}\n└ Decimal: {}",
290                format!("int{bit_len}").red(),
291                format!(
292                    "0x{}",
293                    format!("{i:x}")
294                        .chars()
295                        .skip(if i.is_negative() { 64 - bit_len / 4 } else { 0 })
296                        .collect::<String>()
297                )
298                .cyan(),
299                hex::encode_prefixed(B256::from(i)).cyan(),
300                i.cyan()
301            )
302        }
303        DynSolValue::Uint(i, bit_len) => {
304            format!(
305                "Type: {}\n├ Hex: {}\n├ Hex (full word): {}\n└ Decimal: {}",
306                format!("uint{bit_len}").red(),
307                format!("0x{i:x}").cyan(),
308                hex::encode_prefixed(B256::from(i)).cyan(),
309                i.cyan()
310            )
311        }
312        DynSolValue::Bool(b) => {
313            format!("Type: {}\n└ Value: {}", "bool".red(), b.cyan())
314        }
315        DynSolValue::String(_) | DynSolValue::Bytes(_) => {
316            let hex = hex::encode(token.abi_encode());
317            let s = token.as_str();
318            format!(
319                "Type: {}\n{}├ Hex (Memory):\n├─ Length ({}): {}\n├─ Contents ({}): {}\n├ Hex (Tuple Encoded):\n├─ Pointer ({}): {}\n├─ Length ({}): {}\n└─ Contents ({}): {}",
320                if s.is_some() { "string" } else { "dynamic bytes" }.red(),
321                if let Some(s) = s {
322                    format!("├ UTF-8: {}\n", s.cyan())
323                } else {
324                    String::default()
325                },
326                "[0x00:0x20]".yellow(),
327                format!("0x{}", &hex[64..128]).cyan(),
328                "[0x20:..]".yellow(),
329                format!("0x{}", &hex[128..]).cyan(),
330                "[0x00:0x20]".yellow(),
331                format!("0x{}", &hex[..64]).cyan(),
332                "[0x20:0x40]".yellow(),
333                format!("0x{}", &hex[64..128]).cyan(),
334                "[0x40:..]".yellow(),
335                format!("0x{}", &hex[128..]).cyan(),
336            )
337        }
338        DynSolValue::FixedArray(tokens) | DynSolValue::Array(tokens) => {
339            let mut out = format!(
340                "{}({}) = {}",
341                "array".red(),
342                format!("{}", tokens.len()).yellow(),
343                '['.red()
344            );
345            for token in tokens {
346                out.push_str("\n  ├ ");
347                out.push_str(&format_token(token).replace('\n', "\n  "));
348                out.push('\n');
349            }
350            out.push_str(&']'.red().to_string());
351            out
352        }
353        DynSolValue::Tuple(tokens) => {
354            let displayed_types = tokens
355                .iter()
356                .map(|t| t.sol_type_name().unwrap_or_default())
357                .collect::<Vec<_>>()
358                .join(", ");
359            let mut out =
360                format!("{}({}) = {}", "tuple".red(), displayed_types.yellow(), '('.red());
361            for token in tokens {
362                out.push_str("\n  ├ ");
363                out.push_str(&format_token(token).replace('\n', "\n  "));
364                out.push('\n');
365            }
366            out.push_str(&')'.red().to_string());
367            out
368        }
369        _ => {
370            unimplemented!()
371        }
372    }
373}
374
375/// Formats an [`Event`] into an inspection message.
376// TODO: Verbosity option
377fn format_event_definition(gcx: Gcx<'_>, event: &Event<'_>) -> Result<String> {
378    let event_name = event.name.as_str().to_string();
379    let inputs = event
380        .parameters
381        .iter()
382        .map(|&pid| {
383            let var = gcx.hir.variable(pid);
384            let name =
385                var.name.map(|n| n.as_str().to_string()).unwrap_or_else(|| "<anonymous>".into());
386            let kind = solar_ty_to_dyn(gcx, gcx.type_of_item(pid.into()))
387                .ok_or_else(|| eyre::eyre!("Invalid type in event {event_name}"))?;
388            Ok(EventParam {
389                name,
390                ty: kind.to_string(),
391                components: vec![],
392                indexed: var.indexed,
393                internal_type: None,
394            })
395        })
396        .collect::<Result<Vec<_>>>()?;
397    let event = alloy_json_abi::Event { name: event_name, inputs, anonymous: event.anonymous };
398
399    Ok(format!(
400        "Type: {}\n├ Name: {}\n├ Signature: {:?}\n└ Selector: {:?}",
401        "event".red(),
402        SolidityHelper::new().highlight(&format!(
403            "{}({})",
404            event.name,
405            event
406                .inputs
407                .iter()
408                .map(|param| format!(
409                    "{}{}{}",
410                    param.ty,
411                    if param.indexed { " indexed" } else { "" },
412                    if param.name.is_empty() {
413                        String::default()
414                    } else {
415                        format!(" {}", param.name)
416                    },
417                ))
418                .collect::<Vec<_>>()
419                .join(", ")
420        )),
421        event.signature().cyan(),
422        event.selector().cyan(),
423    ))
424}
425
426// =============================================
427// Modified from
428// [soli](https://github.com/jpopesculian/soli)
429// =============================================
430
431/// Converts an [`Expr`] directly to a [`DynSolType`] for ABI inspection.
432///
433/// `lookup` controls whether user-defined type names are resolved via the HIR.
434fn expr_to_dyn(gcx: Gcx<'_>, expr: &Expr<'_>, lookup: bool) -> Option<DynSolType> {
435    match &expr.kind {
436        // Elementary type expression: `uint256`, `address`, etc.
437        ExprKind::Type(ty) => hir_ty_to_dyn(gcx, ty),
438
439        // `type(T)`: only meaningful as the lhs of a member access.
440        ExprKind::TypeCall(_) => None,
441
442        // Literals.
443        ExprKind::Lit(lit) => match &lit.kind {
444            LitKind::Address(_) => Some(DynSolType::Address),
445            LitKind::Bool(_) => Some(DynSolType::Bool),
446            LitKind::Str(kind, _, _) => match kind {
447                StrKind::Hex => Some(DynSolType::Bytes),
448                StrKind::Str | StrKind::Unicode => Some(DynSolType::String),
449            },
450            LitKind::Number(_) | LitKind::Rational(_) => Some(DynSolType::Uint(256)),
451            LitKind::Err(_) => None,
452        },
453
454        // Resolved identifier: `foo`.
455        ExprKind::Ident(reses) => {
456            let res = reses.first()?;
457            match *res {
458                Res::Item(ItemId::Variable(vid)) => {
459                    solar_ty_to_dyn(gcx, gcx.type_of_item(vid.into()))
460                }
461                Res::Item(ItemId::Struct(sid)) => {
462                    // Struct reference used as a constructor produces a tuple of field types.
463                    Some(DynSolType::Tuple(
464                        gcx.struct_field_types(sid)
465                            .iter()
466                            .filter_map(|&t| solar_ty_to_dyn(gcx, t))
467                            .collect(),
468                    ))
469                }
470                // Other items and builtins: handled by enclosing Call/Member expressions.
471                _ => None,
472            }
473        }
474
475        // Index/access: `arr[i]`, `MyType[]`, `MyType[N]`.
476        ExprKind::Index(base, idx) => {
477            let base_ty = expr_to_dyn(gcx, base, lookup)?;
478            let num =
479                idx.and_then(|e| parse_number_literal(e)).and_then(|n| usize::try_from(n).ok());
480            match &base.kind {
481                // Type-level indexing builds an array type expression.
482                ExprKind::Type(_) | ExprKind::TypeCall(_) => {
483                    if let Some(n) = num {
484                        Some(DynSolType::FixedArray(Box::new(base_ty), n))
485                    } else {
486                        Some(DynSolType::Array(Box::new(base_ty)))
487                    }
488                }
489                // Runtime indexing returns the element type.
490                _ => match base_ty {
491                    DynSolType::Array(inner) | DynSolType::FixedArray(inner, _) => Some(*inner),
492                    DynSolType::Bytes | DynSolType::String | DynSolType::FixedBytes(_) => {
493                        Some(DynSolType::FixedBytes(1))
494                    }
495                    other => Some(other),
496                },
497            }
498        }
499
500        // Slice: same type as the base.
501        ExprKind::Slice(base, _, _) => expr_to_dyn(gcx, base, lookup),
502
503        // Array literal `[a, b, c]`.
504        ExprKind::Array(values) => values
505            .first()
506            .and_then(|e| expr_to_dyn(gcx, e, lookup))
507            .map(|ty| DynSolType::FixedArray(Box::new(ty), values.len())),
508
509        // Tuple expression `(a, b, c)`.
510        ExprKind::Tuple(items) => Some(DynSolType::Tuple(
511            items.iter().filter_map(|opt| opt.and_then(|e| expr_to_dyn(gcx, e, lookup))).collect(),
512        )),
513
514        // Member access `lhs.member`.
515        ExprKind::Member(_, _) => resolve_member(gcx, expr, lookup),
516
517        // Function/constructor call.
518        ExprKind::Call(_, _, _) => resolve_call(gcx, expr, lookup),
519
520        // `new T`: produces a value of type T.
521        ExprKind::New(ty) => hir_ty_to_dyn(gcx, ty),
522
523        // `payable(addr)`.
524        ExprKind::Payable(_) => Some(DynSolType::Address),
525
526        // Ternary: prefer truthy branch's type, fall back to else branch.
527        ExprKind::Ternary(_, t, e) => {
528            expr_to_dyn(gcx, t, lookup).or_else(|| expr_to_dyn(gcx, e, lookup))
529        }
530
531        // Delete has no return type.
532        ExprKind::Delete(_) => None,
533
534        // Unary operations.
535        ExprKind::Unary(op, inner) => match op.kind {
536            UnOpKind::Neg => expr_to_dyn(gcx, inner, lookup).map(|ty| match ty {
537                DynSolType::Uint(n) => DynSolType::Int(n),
538                DynSolType::Int(n) => DynSolType::Uint(n),
539                x => x,
540            }),
541            UnOpKind::Not => Some(DynSolType::Bool),
542            UnOpKind::BitNot
543            | UnOpKind::PreInc
544            | UnOpKind::PreDec
545            | UnOpKind::PostInc
546            | UnOpKind::PostDec => expr_to_dyn(gcx, inner, lookup),
547        },
548
549        // Binary operations.
550        ExprKind::Binary(lhs, op, rhs) => match op.kind {
551            BinOpKind::Lt
552            | BinOpKind::Le
553            | BinOpKind::Gt
554            | BinOpKind::Ge
555            | BinOpKind::Eq
556            | BinOpKind::Ne
557            | BinOpKind::And
558            | BinOpKind::Or => Some(DynSolType::Bool),
559            BinOpKind::Add | BinOpKind::Sub | BinOpKind::Mul | BinOpKind::Div => {
560                match (expr_to_dyn(gcx, lhs, false), expr_to_dyn(gcx, rhs, false)) {
561                    (Some(DynSolType::Int(_) | DynSolType::Uint(_)), Some(DynSolType::Int(_)))
562                    | (Some(DynSolType::Int(_)), Some(DynSolType::Uint(_))) => {
563                        Some(DynSolType::Int(256))
564                    }
565                    _ => Some(DynSolType::Uint(256)),
566                }
567            }
568            BinOpKind::Rem
569            | BinOpKind::Pow
570            | BinOpKind::BitAnd
571            | BinOpKind::BitOr
572            | BinOpKind::BitXor
573            | BinOpKind::Shl
574            | BinOpKind::Shr
575            | BinOpKind::Sar => Some(DynSolType::Uint(256)),
576        },
577
578        // Assignments: type of the lhs.
579        ExprKind::Assign(lhs, _, _) => expr_to_dyn(gcx, lhs, lookup),
580
581        ExprKind::Err(_) => None,
582    }
583}
584
585/// Converts a [`HirType`] to a [`DynSolType`].
586fn hir_ty_to_dyn(gcx: Gcx<'_>, ty: &HirType<'_>) -> Option<DynSolType> {
587    match &ty.kind {
588        TypeKind::Elementary(et) => elementary_to_dyn(*et),
589        TypeKind::Array(arr) => {
590            let elem = hir_ty_to_dyn(gcx, &arr.element)?;
591            if let Some(size) = arr.size {
592                let n = parse_number_literal(size).and_then(|n| usize::try_from(n).ok());
593                if let Some(n) = n {
594                    Some(DynSolType::FixedArray(Box::new(elem), n))
595                } else {
596                    Some(DynSolType::Array(Box::new(elem)))
597                }
598            } else {
599                Some(DynSolType::Array(Box::new(elem)))
600            }
601        }
602        TypeKind::Function(f) => match f.returns.len() {
603            0 => None,
604            1 => {
605                let var = gcx.hir.variable(f.returns[0]);
606                hir_ty_to_dyn(gcx, &var.ty)
607            }
608            _ => Some(DynSolType::Tuple(
609                f.returns
610                    .iter()
611                    .filter_map(|&pid| hir_ty_to_dyn(gcx, &gcx.hir.variable(pid).ty))
612                    .collect(),
613            )),
614        },
615        TypeKind::Mapping(m) => hir_ty_to_dyn(gcx, &m.value),
616        TypeKind::Custom(item) => solar_ty_to_dyn(gcx, gcx.type_of_item(*item)),
617        TypeKind::Err(_) => None,
618    }
619}
620
621/// Resolves a member-access expression (`lhs.member`) to its [`DynSolType`].
622///
623/// `expr` must be `ExprKind::Member`.
624fn resolve_member(gcx: Gcx<'_>, expr: &Expr<'_>, lookup: bool) -> Option<DynSolType> {
625    let ExprKind::Member(lhs, ident) = &expr.kind else { return None };
626    let member = ident.name;
627
628    // `type(T).member` — type introspection.
629    if let ExprKind::TypeCall(ty) = &lhs.kind {
630        return match member.as_str() {
631            "name" => Some(DynSolType::String),
632            "creationCode" | "runtimeCode" => Some(DynSolType::Bytes),
633            "interfaceId" => Some(DynSolType::FixedBytes(4)),
634            // Only valid for integer types; custom types (enums) fall back to Uint(256).
635            "min" | "max" => match &ty.kind {
636                TypeKind::Elementary(et) => elementary_to_dyn(*et),
637                _ => Some(DynSolType::Uint(256)),
638            },
639            _ => None,
640        };
641    }
642
643    // Built-in namespace identifier: `block.timestamp`, `msg.sender`, `abi.encode`, etc.
644    if let ExprKind::Ident(reses) = &lhs.kind
645        && let Some(Res::Builtin(b)) = reses.first()
646        && let Some(ty) = builtin_member(b.name().as_str(), member.as_str())
647    {
648        return Some(ty);
649    }
650
651    // Elementary type used as a namespace: `address.balance`, `bytes.concat`, etc.
652    if let ExprKind::Type(ty) = &lhs.kind
653        && let TypeKind::Elementary(et) = &ty.kind
654    {
655        return match et {
656            ElementaryType::Address(_) => match member.as_str() {
657                "balance" => Some(DynSolType::Uint(256)),
658                "code" => Some(DynSolType::Bytes),
659                "codehash" => Some(DynSolType::FixedBytes(32)),
660                "send" => Some(DynSolType::Bool),
661                _ => None,
662            },
663            ElementaryType::Bytes => match member.as_str() {
664                "concat" => Some(DynSolType::Bytes),
665                _ => None,
666            },
667            ElementaryType::String => match member.as_str() {
668                "concat" => Some(DynSolType::String),
669                _ => None,
670            },
671            _ => None,
672        };
673    }
674
675    // Members on a resolved DynSolType (`.length`, `.pop`, `.selector`, `.address`).
676    if let Some(lhs_ty) = expr_to_dyn(gcx, lhs, lookup)
677        && let Some(ty) = dyn_member(&lhs_ty, member.as_str())
678    {
679        return Some(ty);
680    }
681
682    // HIR lookup for user-defined type members.
683    if lookup && let Some(mut chain) = expr_name_chain(gcx, lhs) {
684        chain.insert(0, member);
685        return infer_custom_type(gcx, &mut chain, None).ok().flatten();
686    }
687
688    None
689}
690
691/// Returns the type of `builtin_ns.member` for built-in global namespaces.
692fn builtin_member(builtin: &str, member: &str) -> Option<DynSolType> {
693    match builtin {
694        "block" => match member {
695            "coinbase" => Some(DynSolType::Address),
696            "timestamp" | "difficulty" | "prevrandao" | "number" | "gaslimit" | "chainid"
697            | "basefee" | "blobbasefee" => Some(DynSolType::Uint(256)),
698            _ => None,
699        },
700        "msg" => match member {
701            "sender" => Some(DynSolType::Address),
702            "gas" | "value" => Some(DynSolType::Uint(256)),
703            "data" => Some(DynSolType::Bytes),
704            "sig" => Some(DynSolType::FixedBytes(4)),
705            _ => None,
706        },
707        "tx" => match member {
708            "origin" => Some(DynSolType::Address),
709            "gasprice" => Some(DynSolType::Uint(256)),
710            _ => None,
711        },
712        "address" => match member {
713            "balance" => Some(DynSolType::Uint(256)),
714            "code" => Some(DynSolType::Bytes),
715            "codehash" => Some(DynSolType::FixedBytes(32)),
716            "send" => Some(DynSolType::Bool),
717            _ => None,
718        },
719        _ => None,
720    }
721}
722
723/// Returns the type of `ty.member` for a known [`DynSolType`].
724fn dyn_member(ty: &DynSolType, member: &str) -> Option<DynSolType> {
725    match member {
726        "length" => match ty {
727            DynSolType::Array(_)
728            | DynSolType::FixedArray(_, _)
729            | DynSolType::Bytes
730            | DynSolType::String
731            | DynSolType::FixedBytes(_) => Some(DynSolType::Uint(256)),
732            _ => None,
733        },
734        "pop" => match ty {
735            DynSolType::Array(inner) => Some(*inner.clone()),
736            _ => None,
737        },
738        // Address members.
739        "balance" => match ty {
740            DynSolType::Address => Some(DynSolType::Uint(256)),
741            _ => None,
742        },
743        "code" => match ty {
744            DynSolType::Address => Some(DynSolType::Bytes),
745            _ => None,
746        },
747        "codehash" => match ty {
748            DynSolType::Address => Some(DynSolType::FixedBytes(32)),
749            _ => None,
750        },
751        "send" => match ty {
752            DynSolType::Address => Some(DynSolType::Bool),
753            _ => None,
754        },
755        // External function members.
756        "selector" => Some(DynSolType::FixedBytes(4)),
757        "address" => Some(DynSolType::Address),
758        _ => None,
759    }
760}
761
762/// Resolves a call expression to its return [`DynSolType`].
763///
764/// `expr` must be `ExprKind::Call`.
765fn resolve_call(gcx: Gcx<'_>, expr: &Expr<'_>, lookup: bool) -> Option<DynSolType> {
766    let ExprKind::Call(callee, args, _named) = &expr.kind else { return None };
767
768    // Type cast: `uint256(x)`, `address(y)`, etc.
769    if let ExprKind::Type(ty) = &callee.kind {
770        return hir_ty_to_dyn(gcx, ty);
771    }
772
773    // Member call: `ns.method(...)`.
774    if let ExprKind::Member(lhs, method) = &callee.kind
775        && let ExprKind::Ident(reses) = &lhs.kind
776        && let Some(Res::Builtin(b)) = reses.first()
777    {
778        match b.name().as_str() {
779            "abi" => {
780                return match method.as_str() {
781                    "decode" => {
782                        let last = args.exprs().last()?;
783                        match expr_to_dyn(gcx, last, false)? {
784                            DynSolType::Tuple(tys) => Some(DynSolType::Tuple(tys)),
785                            ty => Some(DynSolType::Tuple(vec![ty])),
786                        }
787                    }
788                    s if s.starts_with("encode") => Some(DynSolType::Bytes),
789                    _ => None,
790                };
791            }
792            "string" if method.as_str() == "concat" => return Some(DynSolType::String),
793            "bytes" if method.as_str() == "concat" => return Some(DynSolType::Bytes),
794            _ => {}
795        }
796    }
797
798    // Simple identifier call: built-in global functions and HIR function calls.
799    if let ExprKind::Ident(reses) = &callee.kind {
800        match reses.first() {
801            Some(Res::Builtin(b)) => {
802                return match b.name().as_str() {
803                    "gasleft" | "addmod" | "mulmod" => Some(DynSolType::Uint(256)),
804                    "keccak256" | "sha256" | "blockhash" => Some(DynSolType::FixedBytes(32)),
805                    "ripemd160" => Some(DynSolType::FixedBytes(20)),
806                    "ecrecover" => Some(DynSolType::Address),
807                    _ => None,
808                };
809            }
810            Some(Res::Item(ItemId::Function(fid))) if lookup => {
811                let func = gcx.hir.function(*fid);
812                if !matches!(func.state_mutability, StateMutability::View | StateMutability::Pure) {
813                    return None;
814                }
815                let ret_id = *func.returns.first()?;
816                return solar_ty_to_dyn(gcx, gcx.type_of_item(ret_id.into()));
817            }
818            _ => {}
819        }
820    }
821
822    // Fall back to the callee's resolved type.
823    expr_to_dyn(gcx, callee, lookup)
824}
825
826/// Extracts a name chain from a member-access expression tree for HIR lookup.
827///
828/// The chain is ordered outermost-first so `a.b.c` produces `["c", "b", "a"]` with the root
829/// identifier at the back. This matches the convention expected by [`infer_custom_type`].
830fn expr_name_chain(gcx: Gcx<'_>, expr: &Expr<'_>) -> Option<Vec<Symbol>> {
831    match &expr.kind {
832        ExprKind::Ident(reses) => {
833            let res = reses.first()?;
834            let name = match *res {
835                Res::Item(ItemId::Variable(vid)) => gcx.hir.variable(vid).name?.name,
836                Res::Item(ItemId::Function(fid)) => gcx.hir.function(fid).name?.name,
837                Res::Item(ItemId::Contract(cid)) => gcx.hir.contract(cid).name.name,
838                Res::Builtin(b) => b.name(),
839                _ => return None,
840            };
841            Some(vec![name])
842        }
843        ExprKind::Member(lhs, ident) => {
844            let mut chain = expr_name_chain(gcx, lhs)?;
845            chain.insert(0, ident.name);
846            Some(chain)
847        }
848        _ => None,
849    }
850}
851
852/// Infers a custom type's true type by recursing through the HIR.
853///
854/// `custom_type` is a name chain ordered outermost-first (root at back). This is mutated during
855/// resolution. `contract_id` narrows the search to a specific contract scope.
856fn infer_custom_type(
857    gcx: Gcx<'_>,
858    custom_type: &mut Vec<Symbol>,
859    contract_id: Option<ContractId>,
860) -> Result<Option<DynSolType>> {
861    if let Some(last) = custom_type.last()
862        && (last.as_str() == "this" || last.as_str() == "super")
863    {
864        custom_type.pop();
865    }
866    if custom_type.is_empty() {
867        return Ok(None);
868    }
869
870    if let Some(cid) = contract_id {
871        let hir = &gcx.hir;
872        let contract = hir.contract(cid);
873
874        let cur_name = *custom_type.last().unwrap();
875        let cur = cur_name.as_str();
876
877        // Function?
878        if let Some(fid) = contract
879            .functions()
880            .find(|&f| hir.function(f).name.as_ref().map(|n| n.as_str() == cur).unwrap_or(false))
881        {
882            let func = hir.function(fid);
883            if let res @ Some(_) = func_members(func, custom_type) {
884                return Ok(res);
885            }
886
887            if func.returns.is_empty() {
888                eyre::bail!(
889                    "This call expression does not return any values to inspect. Insert as statement."
890                )
891            }
892
893            let sm = func.state_mutability;
894            if !matches!(sm, StateMutability::View | StateMutability::Pure) {
895                eyre::bail!("This function mutates state. Insert as a statement.")
896            }
897
898            let ret_id = func.returns[0];
899            let ret_var = hir.variable(ret_id);
900            return Ok(solar_ty_to_dyn(gcx, gcx.type_of_item(ret_id.into()))
901                .or_else(|| hir_ty_to_dyn(gcx, &ret_var.ty)));
902        }
903
904        // Variable?
905        if let Some(vid) = contract
906            .variables()
907            .find(|&v| hir.variable(v).name.as_ref().map(|n| n.as_str() == cur).unwrap_or(false))
908        {
909            if let Some(ty) = solar_ty_to_dyn(gcx, gcx.type_of_item(vid.into())) {
910                custom_type.pop();
911                if custom_type.is_empty() {
912                    return Ok(Some(ty));
913                }
914                let next_member = custom_type.drain(..).next().unwrap_or(Symbol::DUMMY);
915                return Ok(dyn_member(&ty, next_member.as_str()).or(Some(ty)));
916            }
917            let var = hir.variable(vid);
918            return infer_var_ty(gcx, &var.ty, custom_type);
919        }
920
921        // Struct?
922        if let Some(sid) = contract.items.iter().find_map(|i| {
923            if let ItemId::Struct(sid) = i
924                && hir.strukt(*sid).name.as_str() == cur
925            {
926                Some(*sid)
927            } else {
928                None
929            }
930        }) {
931            let inner = gcx
932                .struct_field_types(sid)
933                .iter()
934                .map(|&t| {
935                    solar_ty_to_dyn(gcx, t)
936                        .ok_or_else(|| eyre::eyre!("Struct `{cur}` has invalid fields"))
937                })
938                .collect::<Result<Vec<_>>>()?;
939            return Ok(Some(DynSolType::Tuple(inner)));
940        }
941
942        eyre::bail!(
943            "Could not find any definition in contract \"{}\" for type: {custom_type:?}",
944            contract.name.as_str()
945        )
946    }
947
948    let repl_id = gcx
949        .hir
950        .contracts_enumerated()
951        .find_map(|(cid, c)| (c.name.as_str() == "REPL").then_some(cid));
952    if let Some(repl_id) = repl_id
953        && let Ok(res) = infer_custom_type(gcx, custom_type, Some(repl_id))
954    {
955        return Ok(res);
956    }
957
958    let last_name = *custom_type.last().unwrap();
959    let last = last_name.as_str();
960    let contract_match = gcx
961        .hir
962        .contracts_enumerated()
963        .find_map(|(cid, c)| (c.name.as_str() == last).then_some(cid));
964    if let Some(cid) = contract_match {
965        custom_type.pop();
966        return infer_custom_type(gcx, custom_type, Some(cid));
967    }
968
969    Ok(None)
970}
971
972/// Infers the type from a variable's HIR type, optionally accessing a named member.
973fn infer_var_ty(
974    gcx: Gcx<'_>,
975    ty: &HirType<'_>,
976    custom_type: &mut Vec<Symbol>,
977) -> Result<Option<DynSolType>> {
978    let Some(ty) = hir_ty_to_dyn(gcx, ty) else { return Ok(None) };
979    let next_member = custom_type.drain(..).next();
980    if let Some(m) = next_member {
981        Ok(dyn_member(&ty, m.as_str()).or(Some(ty)))
982    } else {
983        Ok(Some(ty))
984    }
985}
986
987/// Get the return type of a contract method call `receiver.method()`.
988fn get_function_return_type(gcx: Gcx<'_>, expr: &Expr<'_>) -> Option<DynSolType> {
989    let ExprKind::Call(callee, _, _) = &expr.kind else { return None };
990    let ExprKind::Member(obj, fn_ident) = &callee.kind else { return None };
991    let ExprKind::Ident(reses) = &obj.kind else { return None };
992    let res = reses.first()?;
993    let var_id = match res {
994        Res::Item(ItemId::Variable(vid)) => *vid,
995        _ => return None,
996    };
997    let var_ty = gcx.type_of_item(var_id.into()).peel_refs();
998    let cid = match var_ty.kind {
999        TyKind::Contract(cid) => cid,
1000        _ => return None,
1001    };
1002
1003    let hir = &gcx.hir;
1004    let contract = hir.contract(cid);
1005    let fid = contract
1006        .functions()
1007        .find(|&f| hir.function(f).name.as_ref().map(|n| n.as_str()) == Some(fn_ident.as_str()))?;
1008    let func = hir.function(fid);
1009    let ret_id = *func.returns.first()?;
1010    solar_ty_to_dyn(gcx, gcx.type_of_item(ret_id.into()))
1011}
1012
1013/// Returns Some if the custom type is a function member access.
1014///
1015/// Ref: <https://docs.soliditylang.org/en/latest/types.html#function-types>
1016#[inline]
1017fn func_members(func: &Function<'_>, custom_type: &[Symbol]) -> Option<DynSolType> {
1018    if !matches!(func.kind, FunctionKind::Function) {
1019        return None;
1020    }
1021    if !matches!(func.visibility, Visibility::External | Visibility::Public) {
1022        return None;
1023    }
1024    match custom_type.first().unwrap().as_str() {
1025        "address" => Some(DynSolType::Address),
1026        "selector" => Some(DynSolType::FixedBytes(4)),
1027        _ => None,
1028    }
1029}
1030
1031/// Whether execution should continue after inspecting this expression.
1032#[inline]
1033fn should_continue(expr: &Expr<'_>) -> bool {
1034    match &expr.kind {
1035        // assignments and compound assignments
1036        ExprKind::Assign(_, _, _) => true,
1037        // ++/-- pre/post operations
1038        ExprKind::Unary(op, _) => matches!(
1039            op.kind,
1040            UnOpKind::PreInc | UnOpKind::PreDec | UnOpKind::PostInc | UnOpKind::PostDec
1041        ),
1042        // Array.pop()
1043        ExprKind::Call(callee, _, _) => match &callee.kind {
1044            ExprKind::Member(_, ident) => ident.as_str() == "pop",
1045            _ => false,
1046        },
1047        _ => false,
1048    }
1049}
1050
1051/// Parses an [`Expr`] number/hex literal into a `U256`. Returns `None` if the expression
1052/// is not a numeric literal.
1053///
1054/// SubDenominations are already applied to numeric literals in solar's HIR.
1055const fn parse_number_literal(expr: &Expr<'_>) -> Option<U256> {
1056    match &expr.kind {
1057        ExprKind::Lit(lit) => match &lit.kind {
1058            LitKind::Number(n) => Some(*n),
1059            _ => None,
1060        },
1061        _ => None,
1062    }
1063}
1064
1065/// Maps a solar [`ElementaryType`] to a [`DynSolType`].
1066const fn elementary_to_dyn(et: ElementaryType) -> Option<DynSolType> {
1067    Some(match et {
1068        ElementaryType::Address(_) => DynSolType::Address,
1069        ElementaryType::Bool => DynSolType::Bool,
1070        ElementaryType::String => DynSolType::String,
1071        ElementaryType::Bytes => DynSolType::Bytes,
1072        ElementaryType::Int(size) => DynSolType::Int(size.bits() as usize),
1073        ElementaryType::UInt(size) => DynSolType::Uint(size.bits() as usize),
1074        ElementaryType::FixedBytes(size) => DynSolType::FixedBytes(size.bytes() as usize),
1075        // Fixed-point numbers are not yet representable as DynSolType.
1076        ElementaryType::Fixed(_, _) | ElementaryType::UFixed(_, _) => return None,
1077    })
1078}
1079
1080/// Maps a solar [`Ty`] to a [`DynSolType`].
1081fn solar_ty_to_dyn<'gcx>(gcx: Gcx<'gcx>, ty: Ty<'gcx>) -> Option<DynSolType> {
1082    match ty.kind {
1083        TyKind::Elementary(et) => elementary_to_dyn(et),
1084        TyKind::Ref(inner, _) => solar_ty_to_dyn(gcx, inner),
1085        TyKind::Array(elem, n) => {
1086            let inner = solar_ty_to_dyn(gcx, elem)?;
1087            let size: usize = n.try_into().ok()?;
1088            Some(DynSolType::FixedArray(Box::new(inner), size))
1089        }
1090        TyKind::DynArray(elem) | TyKind::Slice(elem) => {
1091            let inner = solar_ty_to_dyn(gcx, elem)?;
1092            Some(DynSolType::Array(Box::new(inner)))
1093        }
1094        TyKind::Tuple(tys) => {
1095            Some(DynSolType::Tuple(tys.iter().filter_map(|t| solar_ty_to_dyn(gcx, *t)).collect()))
1096        }
1097        TyKind::Mapping(_, _) => None,
1098        TyKind::Struct(sid) => Some(DynSolType::Tuple(
1099            gcx.struct_field_types(sid).iter().filter_map(|t| solar_ty_to_dyn(gcx, *t)).collect(),
1100        )),
1101        TyKind::Enum(_) => Some(DynSolType::Uint(8)),
1102        TyKind::Udvt(inner, _) => solar_ty_to_dyn(gcx, inner),
1103        TyKind::Contract(_) => Some(DynSolType::Address),
1104        // For a function-pointer type we return the ABI type of what the call *produces*, not a
1105        // representation of the pointer itself. This is intentional: chisel inspects values, so
1106        // the interesting type is the returned value.  A zero-return function pointer has no
1107        // inspectable value, so we return `None`.
1108        TyKind::FnPtr(f) => match f.returns.len() {
1109            0 => None,
1110            1 => solar_ty_to_dyn(gcx, f.returns[0]),
1111            _ => Some(DynSolType::Tuple(
1112                f.returns.iter().filter_map(|t| solar_ty_to_dyn(gcx, *t)).collect(),
1113            )),
1114        },
1115        TyKind::Type(inner) => solar_ty_to_dyn(gcx, inner),
1116        TyKind::Meta(inner) => solar_ty_to_dyn(gcx, inner),
1117        TyKind::IntLiteral(neg, size) => {
1118            let bits = (size.bits() as usize).max(8);
1119            // Round up to the nearest multiple of 8 bits, capped at 256.
1120            let bits = bits.div_ceil(8) * 8;
1121            let bits = bits.min(256);
1122            if neg {
1123                Some(DynSolType::Int(bits.max(8)))
1124            } else {
1125                Some(DynSolType::Uint(bits.max(8)))
1126            }
1127        }
1128        TyKind::StringLiteral(valid_utf8, _) => {
1129            if valid_utf8 {
1130                Some(DynSolType::String)
1131            } else {
1132                Some(DynSolType::Bytes)
1133            }
1134        }
1135        TyKind::Module(_)
1136        | TyKind::BuiltinModule(_)
1137        | TyKind::Error(_, _)
1138        | TyKind::Event(_, _)
1139        | TyKind::Err(_) => None,
1140        _ => None,
1141    }
1142}
1143
1144#[cfg(test)]
1145mod tests {
1146    use super::*;
1147    use foundry_compilers::{error::SolcError, solc::Solc};
1148    use solar::sema::Compiler;
1149    use std::sync::Mutex;
1150
1151    #[test]
1152    fn test_expressions() {
1153        static EXPRESSIONS: &[(&str, DynSolType)] = {
1154            use DynSolType::*;
1155            &[
1156                // units
1157                // uint
1158                ("1 seconds", Uint(256)),
1159                ("1 minutes", Uint(256)),
1160                ("1 hours", Uint(256)),
1161                ("1 days", Uint(256)),
1162                ("1 weeks", Uint(256)),
1163                ("1 wei", Uint(256)),
1164                ("1 gwei", Uint(256)),
1165                ("1 ether", Uint(256)),
1166                // int
1167                ("-1 seconds", Int(256)),
1168                ("-1 minutes", Int(256)),
1169                ("-1 hours", Int(256)),
1170                ("-1 days", Int(256)),
1171                ("-1 weeks", Int(256)),
1172                ("-1 wei", Int(256)),
1173                ("-1 gwei", Int(256)),
1174                ("-1 ether", Int(256)),
1175                //
1176                ("true ? 1 : 0", Uint(256)),
1177                ("true ? -1 : 0", Int(256)),
1178                // misc
1179                //
1180
1181                // ops
1182                // uint
1183                ("1 + 1", Uint(256)),
1184                ("1 - 1", Uint(256)),
1185                ("1 * 1", Uint(256)),
1186                ("1 / 1", Uint(256)),
1187                ("1 % 1", Uint(256)),
1188                ("1 ** 1", Uint(256)),
1189                ("1 | 1", Uint(256)),
1190                ("1 & 1", Uint(256)),
1191                ("1 ^ 1", Uint(256)),
1192                ("1 >> 1", Uint(256)),
1193                ("1 << 1", Uint(256)),
1194                // int
1195                ("int(1) + 1", Int(256)),
1196                ("int(1) - 1", Int(256)),
1197                ("int(1) * 1", Int(256)),
1198                ("int(1) / 1", Int(256)),
1199                ("1 + int(1)", Int(256)),
1200                ("1 - int(1)", Int(256)),
1201                ("1 * int(1)", Int(256)),
1202                ("1 / int(1)", Int(256)),
1203                //
1204
1205                // assign
1206                ("uint256 a; a--", Uint(256)),
1207                ("uint256 a; --a", Uint(256)),
1208                ("uint256 a; a++", Uint(256)),
1209                ("uint256 a; ++a", Uint(256)),
1210                ("uint256 a; a   = 1", Uint(256)),
1211                ("uint256 a; a  += 1", Uint(256)),
1212                ("uint256 a; a  -= 1", Uint(256)),
1213                ("uint256 a; a  *= 1", Uint(256)),
1214                ("uint256 a; a  /= 1", Uint(256)),
1215                ("uint256 a; a  %= 1", Uint(256)),
1216                ("uint256 a; a  &= 1", Uint(256)),
1217                ("uint256 a; a  |= 1", Uint(256)),
1218                ("uint256 a; a  ^= 1", Uint(256)),
1219                ("uint256 a; a <<= 1", Uint(256)),
1220                ("uint256 a; a >>= 1", Uint(256)),
1221                //
1222
1223                // bool
1224                ("true && true", Bool),
1225                ("true || true", Bool),
1226                ("true == true", Bool),
1227                ("true != true", Bool),
1228                ("true < true", Bool),
1229                ("true <= true", Bool),
1230                ("true > true", Bool),
1231                ("true >= true", Bool),
1232                ("!true", Bool),
1233                //
1234            ]
1235        };
1236
1237        let source = &mut source();
1238
1239        let array_expressions: &[(&str, DynSolType)] = &[
1240            ("[1, 2, 3]", fixed_array(DynSolType::Uint(256), 3)),
1241            ("[uint8(1), 2, 3]", fixed_array(DynSolType::Uint(8), 3)),
1242            ("[int8(1), 2, 3]", fixed_array(DynSolType::Int(8), 3)),
1243            ("new uint256[](3)", array(DynSolType::Uint(256))),
1244            ("uint256[] memory a = new uint256[](3);\na[0]", DynSolType::Uint(256)),
1245            ("uint256[] memory a = new uint256[](3);\na[0:3]", array(DynSolType::Uint(256))),
1246        ];
1247        generic_type_test(source, array_expressions);
1248        generic_type_test(source, EXPRESSIONS);
1249    }
1250
1251    #[test]
1252    fn test_types() {
1253        static TYPES: &[(&str, DynSolType)] = {
1254            use DynSolType::*;
1255            &[
1256                // bool
1257                ("bool", Bool),
1258                ("true", Bool),
1259                ("false", Bool),
1260                //
1261
1262                // int and uint
1263                ("uint", Uint(256)),
1264                ("uint(1)", Uint(256)),
1265                ("1", Uint(256)),
1266                ("0x01", Uint(256)),
1267                ("int", Int(256)),
1268                ("int(1)", Int(256)),
1269                ("int(-1)", Int(256)),
1270                ("-1", Int(256)),
1271                ("-0x01", Int(256)),
1272                //
1273
1274                // address
1275                ("address", Address),
1276                ("address(0)", Address),
1277                ("0x690B9A9E9aa1C9dB991C7721a92d351Db4FaC990", Address),
1278                ("payable(0)", Address),
1279                ("payable(address(0))", Address),
1280                //
1281
1282                // string
1283                ("string", String),
1284                ("string(\"hello world\")", String),
1285                ("\"hello world\"", String),
1286                ("unicode\"hello world 😀\"", String),
1287                //
1288
1289                // bytes
1290                ("bytes", Bytes),
1291                ("bytes(\"hello world\")", Bytes),
1292                ("bytes(unicode\"hello world 😀\")", Bytes),
1293                ("hex\"68656c6c6f20776f726c64\"", Bytes),
1294                //
1295            ]
1296        };
1297
1298        let mut types: Vec<(String, DynSolType)> = Vec::with_capacity(96 + 32 + 100);
1299        for (n, b) in (8..=256).step_by(8).zip(1..=32) {
1300            types.push((format!("uint{n}(0)"), DynSolType::Uint(n)));
1301            types.push((format!("int{n}(0)"), DynSolType::Int(n)));
1302            types.push((format!("bytes{b}(0x00)"), DynSolType::FixedBytes(b)));
1303        }
1304
1305        for n in 0..=32 {
1306            types.push((
1307                format!("uint256[{n}]"),
1308                DynSolType::FixedArray(Box::new(DynSolType::Uint(256)), n),
1309            ));
1310        }
1311
1312        generic_type_test(&mut source(), TYPES);
1313        generic_type_test(&mut source(), &types);
1314    }
1315
1316    #[test]
1317    fn test_global_vars() {
1318        init_tracing();
1319
1320        // https://docs.soliditylang.org/en/latest/cheatsheet.html#global-variables
1321        let global_variables = {
1322            use DynSolType::*;
1323            &[
1324                // abi
1325                ("abi.decode(bytes, (uint8[13]))", Tuple(vec![FixedArray(Box::new(Uint(8)), 13)])),
1326                ("abi.decode(bytes, (address, bytes))", Tuple(vec![Address, Bytes])),
1327                ("abi.decode(bytes, (uint112, uint48))", Tuple(vec![Uint(112), Uint(48)])),
1328                ("abi.encode(_, _)", Bytes),
1329                ("abi.encodePacked(_, _)", Bytes),
1330                ("abi.encodeWithSelector(bytes4, _, _)", Bytes),
1331                ("abi.encodeCall(func(), (_, _))", Bytes),
1332                ("abi.encodeWithSignature(string, _, _)", Bytes),
1333                //
1334
1335                //
1336                ("bytes.concat()", Bytes),
1337                ("bytes.concat(_)", Bytes),
1338                ("bytes.concat(_, _)", Bytes),
1339                ("string.concat()", String),
1340                ("string.concat(_)", String),
1341                ("string.concat(_, _)", String),
1342                //
1343
1344                // block
1345                ("block.basefee", Uint(256)),
1346                ("block.chainid", Uint(256)),
1347                ("block.coinbase", Address),
1348                ("block.difficulty", Uint(256)),
1349                ("block.gaslimit", Uint(256)),
1350                ("block.number", Uint(256)),
1351                ("block.timestamp", Uint(256)),
1352                //
1353
1354                // tx
1355                ("gasleft()", Uint(256)),
1356                ("msg.data", Bytes),
1357                ("msg.sender", Address),
1358                ("msg.sig", FixedBytes(4)),
1359                ("msg.value", Uint(256)),
1360                ("tx.gasprice", Uint(256)),
1361                ("tx.origin", Address),
1362                //
1363
1364                // assertions
1365                // assert(bool)
1366                // require(bool)
1367                // revert()
1368                // revert(string)
1369                //
1370
1371                //
1372                ("blockhash(uint)", FixedBytes(32)),
1373                ("keccak256(bytes)", FixedBytes(32)),
1374                ("sha256(bytes)", FixedBytes(32)),
1375                ("ripemd160(bytes)", FixedBytes(20)),
1376                ("ecrecover(bytes32, uint8, bytes32, bytes32)", Address),
1377                ("addmod(uint, uint, uint)", Uint(256)),
1378                ("mulmod(uint, uint, uint)", Uint(256)),
1379                //
1380
1381                // address
1382                ("address(_)", Address),
1383                ("address(this)", Address),
1384                // ("super", Type::Custom("super".to_string))
1385                // (selfdestruct(address payable), None)
1386                ("address.balance", Uint(256)),
1387                ("address.code", Bytes),
1388                ("address.codehash", FixedBytes(32)),
1389                ("address.send(uint256)", Bool),
1390                // (address.transfer(uint256), None)
1391                //
1392
1393                // type
1394                ("type(C).name", String),
1395                ("type(C).creationCode", Bytes),
1396                ("type(C).runtimeCode", Bytes),
1397                ("type(I).interfaceId", FixedBytes(4)),
1398                ("type(uint256).min", Uint(256)),
1399                ("type(int128).min", Int(128)),
1400                ("type(int256).min", Int(256)),
1401                ("type(uint256).max", Uint(256)),
1402                ("type(int128).max", Int(128)),
1403                ("type(int256).max", Int(256)),
1404                ("type(Enum1).min", Uint(256)),
1405                ("type(Enum1).max", Uint(256)),
1406                // function
1407                ("this.run.address", Address),
1408                ("this.run.selector", FixedBytes(4)),
1409            ]
1410        };
1411
1412        generic_type_test(&mut source(), global_variables);
1413    }
1414
1415    #[track_caller]
1416    fn source() -> SessionSource {
1417        // synchronize solc install
1418        static PRE_INSTALL_SOLC_LOCK: Mutex<bool> = Mutex::new(false);
1419
1420        // on some CI targets installing results in weird malformed solc files, we try installing it
1421        // multiple times
1422        let version = "0.8.20";
1423        for _ in 0..3 {
1424            let mut is_preinstalled = PRE_INSTALL_SOLC_LOCK.lock().unwrap();
1425            if !*is_preinstalled {
1426                let solc = Solc::find_or_install(&version.parse().unwrap())
1427                    .map(|solc| (solc.version.clone(), solc));
1428                match solc {
1429                    Ok((v, solc)) => {
1430                        // successfully installed
1431                        let _ = sh_println!("found installed Solc v{v} @ {}", solc.solc.display());
1432                        break;
1433                    }
1434                    Err(e) => {
1435                        // try reinstalling
1436                        let _ = sh_err!("error while trying to re-install Solc v{version}: {e}");
1437                        let solc = Solc::blocking_install(&version.parse().unwrap());
1438                        if solc.map_err(SolcError::from).is_ok() {
1439                            *is_preinstalled = true;
1440                            break;
1441                        }
1442                    }
1443                }
1444            }
1445        }
1446
1447        SessionSource::new(Default::default()).unwrap()
1448    }
1449
1450    fn array(ty: DynSolType) -> DynSolType {
1451        DynSolType::Array(Box::new(ty))
1452    }
1453
1454    fn fixed_array(ty: DynSolType, len: usize) -> DynSolType {
1455        DynSolType::FixedArray(Box::new(ty), len)
1456    }
1457
1458    /// Lowers the given snippet appended to the REPL contract via solar's HIR pipeline (without
1459    /// invoking solc) and returns the resulting `DynSolType` of the last expression statement in
1460    /// the run() body.
1461    ///
1462    /// Tests bypass `SessionSource::build` (which routes through foundry-compilers + solc) so that
1463    /// inputs which are syntactically valid but semantically rejected by solc (e.g.
1464    /// `abi.decode(bytes, (uint8[13]))` or `a[0:3]` on a memory array) can still exercise the
1465    /// HIR-based type-inference engine.
1466    fn get_type_ethabi(s: &mut SessionSource, input: &str, clear: bool) -> Option<DynSolType> {
1467        if clear {
1468            s.clear();
1469        }
1470
1471        // Always declare a sample enum so `Enum1` is available for `type(Enum1)` tests.
1472        *s = s.clone_with_new_line("enum Enum1 { A }".into()).unwrap().0;
1473
1474        let input = format!("{};", input.trim_end().trim_end_matches(';'));
1475        let (new_source, _) = s.clone_with_new_line(input).unwrap();
1476        *s = new_source.clone();
1477
1478        let src = new_source.to_repl_source();
1479        let sess =
1480            solar::interface::Session::builder().with_buffer_emitter(Default::default()).build();
1481        let mut compiler = Compiler::new(sess);
1482
1483        compiler.enter_mut(|c| -> Option<DynSolType> {
1484            // Stage 1: parse + lower (mutable access required).
1485            let lowered = {
1486                let mut pcx = c.parse();
1487                let file = c
1488                    .sess()
1489                    .source_map()
1490                    .new_source_file(
1491                        std::path::PathBuf::from(new_source.file_name.clone()),
1492                        src.clone(),
1493                    )
1494                    .ok()?;
1495                pcx.add_file(file);
1496                pcx.parse();
1497                matches!(c.lower_asts(), Ok(ControlFlow::Continue(())))
1498            };
1499            if !lowered {
1500                return None;
1501            }
1502
1503            // Stage 2: walk HIR (immutable access).
1504            let gcx = c.gcx();
1505            let hir = &gcx.hir;
1506            let repl = hir.contracts().find(|c| c.name.as_str() == "REPL")?;
1507            let run_fid = repl
1508                .functions()
1509                .find(|&f| hir.function(f).name.as_ref().map(|n| n.as_str()) == Some("run"))?;
1510            let body = hir.function(run_fid).body?;
1511            let last = body.last()?;
1512            let expr = match last.kind {
1513                StmtKind::Expr(e) => e,
1514                _ => return None,
1515            };
1516            expr_to_dyn(gcx, expr, true)
1517        })
1518    }
1519
1520    fn generic_type_test<'a, T, I>(s: &mut SessionSource, input: I)
1521    where
1522        T: AsRef<str> + std::fmt::Display + 'a,
1523        I: IntoIterator<Item = &'a (T, DynSolType)> + 'a,
1524    {
1525        for (input, expected) in input {
1526            let input = input.as_ref();
1527            let ty = get_type_ethabi(s, input, true);
1528            assert_eq!(ty.as_ref(), Some(expected), "\n{input}");
1529        }
1530    }
1531
1532    fn init_tracing() {
1533        let _ = tracing_subscriber::FmtSubscriber::builder()
1534            .with_env_filter(tracing_subscriber::EnvFilter::from_default_env())
1535            .try_init();
1536    }
1537}