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,
13    core::evm::{BlockEnvFor, FoundryEvmNetwork, SpecFor, TxEnvFor},
14    decode::decode_console_logs,
15    executors::ExecutorBuilder,
16    inspectors::CheatsConfig,
17    traces::TraceRequirements,
18};
19use solar::{
20    ast::{ElementaryType, LitKind, StrKind, UnOpKind},
21    sema::{
22        hir::{Event, Expr, ExprKind, StmtKind},
23        ty::{Gcx, Ty, TyKind},
24    },
25};
26use std::ops::ControlFlow;
27use yansi::Paint;
28
29/// Executor implementation for [SessionSource]
30impl<FEN: FoundryEvmNetwork> SessionSource<FEN> {
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                let should_execute = self
81                    .clone_with_new_line(input.to_string())
82                    .ok()
83                    .and_then(|(source, do_execute)| {
84                        if !do_execute {
85                            return None;
86                        }
87                        source.build().ok().map(|output| {
88                            output.enter(|output| {
89                                let body = output.run_func_body();
90                                let Some(last) = body.last() else { return false };
91                                let StmtKind::Expr(expr) = last.kind else { return false };
92                                should_continue(expr)
93                            })
94                        })
95                    })
96                    .unwrap_or(false);
97                if should_execute {
98                    return Ok((ControlFlow::Continue(()), None));
99                }
100                match source_without_inspector.execute().await {
101                    Ok(res) => (res, Some(err)),
102                    Err(_) => {
103                        if self.config.foundry_config.verbosity >= 3 {
104                            sh_err!("Could not inspect: {err}")?;
105                        }
106                        return Ok((ControlFlow::Continue(()), None));
107                    }
108                }
109            }
110        };
111
112        // If abi-encoding the input failed, check whether it is an event
113        if let Some(err) = err {
114            let output = source_without_inspector.build()?;
115
116            let formatted_event = output.enter(|output| {
117                let gcx = output.gcx();
118                output.get_event(input).map(|eid| format_event_definition(gcx, gcx.hir.event(eid)))
119            });
120            if let Some(formatted_event) = formatted_event {
121                return Ok((ControlFlow::Break(()), Some(formatted_event?)));
122            }
123
124            // we were unable to check the event
125            if self.config.foundry_config.verbosity >= 3 {
126                sh_err!("Failed eval: {err}")?;
127            }
128
129            debug!(%err, %input, "failed abi encode input");
130            return Ok((ControlFlow::Break(()), None));
131        }
132        drop(source_without_inspector);
133
134        let Some((stack, memory)) = &res.state else {
135            // Show traces and logs, if there are any, and return an error
136            if let Ok(decoder) = ChiselDispatcher::decode_traces(&source.config, &mut res).await {
137                ChiselDispatcher::<FEN>::show_traces(&decoder, &mut res).await?;
138            }
139            let decoded_logs = decode_console_logs(&res.logs);
140            if !decoded_logs.is_empty() {
141                sh_println!("{}", "Logs:".green())?;
142                for log in decoded_logs {
143                    sh_println!("  {log}")?;
144                }
145            }
146
147            return Err(eyre::eyre!("Failed to inspect expression"));
148        };
149
150        // Either the expression referred to by `input`, or the last expression,
151        // which was wrapped in `abi.encode`.
152        let generated_output = source.build()?;
153
154        // Inside the compiler closure, infer the DynSolType of the inspected expression and
155        // determine whether the REPL should continue.
156        let res_ty = generated_output.enter(|out| -> Option<(bool, DynSolType)> {
157            let gcx = out.gcx();
158
159            // Find the appended `bytes memory inspectoor = abi.encode(<input>);` and pull out the
160            // first call argument.
161            let block = out.run_func_body();
162            let last = block.last()?;
163            let StmtKind::DeclSingle(vid) = last.kind else { return None };
164            let var = gcx.hir.variable(vid);
165            let init = var.initializer?;
166            let ExprKind::Call(_callee, args, _) = &init.kind else { return None };
167            let inner_expr = args.exprs().next()?;
168
169            let ty = expr_to_dyn(gcx, inner_expr)?;
170            Some((should_continue(inner_expr), ty))
171        });
172
173        let Some((cont, ty)) = res_ty else {
174            return Ok((ControlFlow::Continue(()), None));
175        };
176
177        // the file compiled correctly, thus the last stack item must be the memory offset of
178        // the `bytes memory inspectoor` value
179        let data = (|| -> Option<_> {
180            let mut offset: usize = stack.last()?.try_into().ok()?;
181            debug!("inspect memory @ {offset}: {}", hex::encode(memory));
182            let mem_offset = memory.get(offset..offset + 32)?;
183            let len: usize = U256::try_from_be_slice(mem_offset)?.try_into().ok()?;
184            offset += 32;
185            memory.get(offset..offset + len)
186        })();
187        let Some(data) = data else {
188            eyre::bail!("Failed to inspect last expression: could not retrieve data from memory");
189        };
190        let token = ty.abi_decode(data).wrap_err("Could not decode inspected values")?;
191        let c = if cont { ControlFlow::Continue(()) } else { ControlFlow::Break(()) };
192        Ok((c, Some(format_token(token))))
193    }
194
195    async fn build_runner(&mut self, final_pc: usize) -> Result<ChiselRunner<FEN>> {
196        let (evm_env, tx_env, fork_block) =
197            self.config.evm_opts.env::<SpecFor<FEN>, BlockEnvFor<FEN>, TxEnvFor<FEN>>().await?;
198
199        let backend = match self.config.backend.clone() {
200            Some(backend) => backend,
201            None => {
202                let fork = self.config.evm_opts.get_fork(
203                    &self.config.foundry_config,
204                    evm_env.cfg_env.chain_id,
205                    fork_block,
206                );
207                let backend = Backend::spawn(fork)?;
208                self.config.backend = Some(backend.clone());
209                backend
210            }
211        };
212
213        let executor = ExecutorBuilder::default()
214            .inspectors(|stack| {
215                stack
216                    .logs(self.config.foundry_config.live_logs)
217                    .chisel_state(final_pc)
218                    .trace_requirements(TraceRequirements::none().with_calls(true))
219                    .cheatcodes(
220                        CheatsConfig::new(
221                            &self.config.foundry_config,
222                            self.config.evm_opts.clone(),
223                            None,
224                            None,
225                            None,
226                            false,
227                        )
228                        .into(),
229                    )
230            })
231            .gas_limit(self.config.evm_opts.gas_limit())
232            .spec_id(self.config.foundry_config.evm_spec_id::<SpecFor<FEN>>())
233            .legacy_assertions(self.config.foundry_config.legacy_assertions)
234            .build(evm_env, tx_env, backend);
235
236        Ok(ChiselRunner::new(executor, U256::MAX, Address::ZERO, self.config.calldata.clone()))
237    }
238}
239
240/// Formats a value into an inspection message
241// TODO: Verbosity option
242fn format_token(token: DynSolValue) -> String {
243    match token {
244        DynSolValue::Address(a) => {
245            format!("Type: {}\n└ Data: {}", "address".red(), a.cyan())
246        }
247        DynSolValue::FixedBytes(b, byte_len) => {
248            format!(
249                "Type: {}\n└ Data: {}",
250                format!("bytes{byte_len}").red(),
251                hex::encode_prefixed(b).cyan()
252            )
253        }
254        DynSolValue::Int(i, bit_len) => {
255            format!(
256                "Type: {}\n├ Hex: {}\n├ Hex (full word): {}\n└ Decimal: {}",
257                format!("int{bit_len}").red(),
258                format!(
259                    "0x{}",
260                    format!("{i:x}")
261                        .chars()
262                        .skip(if i.is_negative() { 64 - bit_len / 4 } else { 0 })
263                        .collect::<String>()
264                )
265                .cyan(),
266                hex::encode_prefixed(B256::from(i)).cyan(),
267                i.cyan()
268            )
269        }
270        DynSolValue::Uint(i, bit_len) => {
271            format!(
272                "Type: {}\n├ Hex: {}\n├ Hex (full word): {}\n└ Decimal: {}",
273                format!("uint{bit_len}").red(),
274                format!("0x{i:x}").cyan(),
275                hex::encode_prefixed(B256::from(i)).cyan(),
276                i.cyan()
277            )
278        }
279        DynSolValue::Bool(b) => {
280            format!("Type: {}\n└ Value: {}", "bool".red(), b.cyan())
281        }
282        DynSolValue::Bytes(bytes) => {
283            format!(
284                "Type: {}\n└ Data: {}",
285                "dynamic bytes".red(),
286                hex::encode_prefixed(bytes).cyan()
287            )
288        }
289        token @ DynSolValue::String(_) => {
290            let hex = hex::encode(token.abi_encode());
291            let s = token.as_str().expect("matched string value");
292            format!(
293                "Type: {}\n├ UTF-8: {}\n├ Hex (Memory):\n├─ Length ({}): {}\n├─ Contents ({}): {}\n├ Hex (Tuple Encoded):\n├─ Pointer ({}): {}\n├─ Length ({}): {}\n└─ Contents ({}): {}",
294                "string".red(),
295                s.cyan(),
296                "[0x00:0x20]".yellow(),
297                format!("0x{}", &hex[64..128]).cyan(),
298                "[0x20:..]".yellow(),
299                format!("0x{}", &hex[128..]).cyan(),
300                "[0x00:0x20]".yellow(),
301                format!("0x{}", &hex[..64]).cyan(),
302                "[0x20:0x40]".yellow(),
303                format!("0x{}", &hex[64..128]).cyan(),
304                "[0x40:..]".yellow(),
305                format!("0x{}", &hex[128..]).cyan(),
306            )
307        }
308        DynSolValue::FixedArray(tokens) | DynSolValue::Array(tokens) => {
309            let mut out = format!(
310                "{}({}) = {}",
311                "array".red(),
312                format!("{}", tokens.len()).yellow(),
313                '['.red()
314            );
315            for token in tokens {
316                out.push_str("\n  ├ ");
317                out.push_str(&format_token(token).replace('\n', "\n  "));
318                out.push('\n');
319            }
320            out.push_str(&']'.red().to_string());
321            out
322        }
323        DynSolValue::Tuple(tokens) => {
324            let displayed_types = tokens
325                .iter()
326                .map(|t| t.sol_type_name().unwrap_or_default())
327                .collect::<Vec<_>>()
328                .join(", ");
329            let mut out =
330                format!("{}({}) = {}", "tuple".red(), displayed_types.yellow(), '('.red());
331            for token in tokens {
332                out.push_str("\n  ├ ");
333                out.push_str(&format_token(token).replace('\n', "\n  "));
334                out.push('\n');
335            }
336            out.push_str(&')'.red().to_string());
337            out
338        }
339        _ => {
340            unimplemented!()
341        }
342    }
343}
344
345/// Formats an [`Event`] into an inspection message.
346// TODO: Verbosity option
347fn format_event_definition(gcx: Gcx<'_>, event: &Event<'_>) -> Result<String> {
348    let event_name = event.name.as_str().to_string();
349    let inputs = event
350        .parameters
351        .iter()
352        .map(|&pid| {
353            let var = gcx.hir.variable(pid);
354            let name =
355                var.name.map(|n| n.as_str().to_string()).unwrap_or_else(|| "<anonymous>".into());
356            let kind = solar_ty_to_dyn(gcx, gcx.type_of_item(pid.into()))
357                .ok_or_else(|| eyre::eyre!("Invalid type in event {event_name}"))?;
358            Ok(EventParam {
359                name,
360                ty: kind.to_string(),
361                components: vec![],
362                indexed: var.indexed,
363                internal_type: None,
364            })
365        })
366        .collect::<Result<Vec<_>>>()?;
367    let event = alloy_json_abi::Event { name: event_name, inputs, anonymous: event.anonymous };
368
369    Ok(format!(
370        "Type: {}\n├ Name: {}\n├ Signature: {:?}\n└ Selector: {:?}",
371        "event".red(),
372        SolidityHelper::new().highlight(&format!(
373            "{}({})",
374            event.name,
375            event
376                .inputs
377                .iter()
378                .map(|param| format!(
379                    "{}{}{}",
380                    param.ty,
381                    if param.indexed { " indexed" } else { "" },
382                    if param.name.is_empty() {
383                        String::default()
384                    } else {
385                        format!(" {}", param.name)
386                    },
387                ))
388                .collect::<Vec<_>>()
389                .join(", ")
390        )),
391        event.signature().cyan(),
392        event.selector().cyan(),
393    ))
394}
395
396/// Converts an [`Expr`] directly to a [`DynSolType`] for ABI inspection.
397fn expr_to_dyn(gcx: Gcx<'_>, expr: &Expr<'_>) -> Option<DynSolType> {
398    gcx.type_of_expr(expr.id).and_then(|ty| solar_expr_ty_to_dyn(gcx, ty, expr))
399}
400
401/// Whether execution should continue after inspecting this expression.
402#[inline]
403fn should_continue(expr: &Expr<'_>) -> bool {
404    match &expr.kind {
405        // assignments and compound assignments
406        ExprKind::Assign(_, _, _) => true,
407        // Delete expressions.
408        ExprKind::Delete(_) => true,
409        // ++/-- pre/post operations
410        ExprKind::Unary(op, _) => matches!(
411            op.kind,
412            UnOpKind::PreInc | UnOpKind::PreDec | UnOpKind::PostInc | UnOpKind::PostDec
413        ),
414        // Array.pop()
415        ExprKind::Call(callee, _, _) => match &callee.kind {
416            ExprKind::Member(_, ident) => ident.as_str() == "pop",
417            _ => false,
418        },
419        _ => false,
420    }
421}
422
423/// Maps a solar [`ElementaryType`] to a [`DynSolType`].
424const fn elementary_to_dyn(et: ElementaryType) -> Option<DynSolType> {
425    Some(match et {
426        ElementaryType::Address(_) => DynSolType::Address,
427        ElementaryType::Bool => DynSolType::Bool,
428        ElementaryType::String => DynSolType::String,
429        ElementaryType::Bytes => DynSolType::Bytes,
430        ElementaryType::Int(size) => DynSolType::Int(size.bits() as usize),
431        ElementaryType::UInt(size) => DynSolType::Uint(size.bits() as usize),
432        ElementaryType::FixedBytes(size) => DynSolType::FixedBytes(size.bytes() as usize),
433        // Fixed-point numbers are not yet representable as DynSolType.
434        ElementaryType::Fixed(_, _) | ElementaryType::UFixed(_, _) => return None,
435    })
436}
437
438/// Maps a solar [`Ty`] to a [`DynSolType`].
439fn solar_expr_ty_to_dyn<'gcx>(gcx: Gcx<'gcx>, ty: Ty<'gcx>, expr: &Expr<'_>) -> Option<DynSolType> {
440    // `expr` is the inspected expression inside Chisel's generated `abi.encode(...)` call. Solar
441    // currently reports hex string literals as `StringLiteral`, but solc ABI-encodes
442    // `hex"..."` literals as dynamic bytes in that context.
443    let expr = expr.peel_parens();
444    if matches!(expr.kind, ExprKind::Lit(lit) if matches!(lit.kind, LitKind::Str(StrKind::Hex, ..)))
445    {
446        return Some(DynSolType::Bytes);
447    }
448
449    solar_ty_to_dyn(gcx, ty)
450}
451
452fn solar_ty_to_dyn<'gcx>(gcx: Gcx<'gcx>, ty: Ty<'gcx>) -> Option<DynSolType> {
453    match ty.kind {
454        TyKind::Elementary(et) => elementary_to_dyn(et),
455        TyKind::Ref(inner, _) => solar_ty_to_dyn(gcx, inner),
456        TyKind::Array(elem, n) => {
457            let inner = solar_ty_to_dyn(gcx, elem)?;
458            let size: usize = n.try_into().ok()?;
459            Some(DynSolType::FixedArray(Box::new(inner), size))
460        }
461        TyKind::DynArray(elem) => {
462            let inner = solar_ty_to_dyn(gcx, elem)?;
463            Some(DynSolType::Array(Box::new(inner)))
464        }
465        TyKind::Slice(array) => solar_ty_to_dyn(gcx, array),
466        TyKind::Tuple(tys) => {
467            Some(DynSolType::Tuple(tys.iter().filter_map(|t| solar_ty_to_dyn(gcx, *t)).collect()))
468        }
469        TyKind::Mapping(_, _) => None,
470        TyKind::Struct(sid) => Some(DynSolType::Tuple(
471            gcx.struct_field_types(sid).iter().filter_map(|t| solar_ty_to_dyn(gcx, *t)).collect(),
472        )),
473        TyKind::Enum(_) => Some(DynSolType::Uint(8)),
474        TyKind::Udvt(inner, _) => solar_ty_to_dyn(gcx, inner),
475        TyKind::Contract(_) => Some(DynSolType::Address),
476        // For a function-pointer type we return the ABI type of what the call *produces*, not a
477        // representation of the pointer itself. This is intentional: chisel inspects values, so
478        // the interesting type is the returned value.  A zero-return function pointer has no
479        // inspectable value, so we return `None`.
480        TyKind::Fn(f) => match f.returns.len() {
481            0 => None,
482            1 => solar_ty_to_dyn(gcx, f.returns[0]),
483            _ => Some(DynSolType::Tuple(
484                f.returns.iter().filter_map(|t| solar_ty_to_dyn(gcx, *t)).collect(),
485            )),
486        },
487        TyKind::Type(inner) => solar_ty_to_dyn(gcx, inner),
488        TyKind::Meta(inner) => solar_ty_to_dyn(gcx, inner),
489        TyKind::IntLiteral(neg, size, _) => {
490            let bits = (size.bits() as usize).max(8);
491            // Round up to the nearest multiple of 8 bits, capped at 256.
492            let bits = bits.div_ceil(8) * 8;
493            let bits = bits.min(256);
494            if neg {
495                Some(DynSolType::Int(bits.max(8)))
496            } else {
497                Some(DynSolType::Uint(bits.max(8)))
498            }
499        }
500        TyKind::StringLiteral(valid_utf8, _) => {
501            if valid_utf8 {
502                Some(DynSolType::String)
503            } else {
504                Some(DynSolType::Bytes)
505            }
506        }
507        TyKind::Module(_)
508        | TyKind::BuiltinModule(_)
509        | TyKind::Error(_, _)
510        | TyKind::Event(_, _)
511        | TyKind::Err(_) => None,
512        _ => None,
513    }
514}
515
516#[cfg(test)]
517mod tests {
518    use super::*;
519    use foundry_compilers::{error::SolcError, solc::Solc};
520    use foundry_evm::core::evm::EthEvmNetwork;
521    use solar::sema::Compiler;
522    use std::sync::Mutex;
523
524    type TestSessionSource = SessionSource<EthEvmNetwork>;
525
526    #[test]
527    fn test_expressions() {
528        static EXPRESSIONS: &[(&str, DynSolType)] = {
529            use DynSolType::*;
530            &[
531                // units
532                // uint
533                ("1 seconds", Uint(8)),
534                ("1 minutes", Uint(8)),
535                ("1 hours", Uint(16)),
536                ("1 days", Uint(24)),
537                ("1 weeks", Uint(24)),
538                ("1 wei", Uint(8)),
539                ("1 gwei", Uint(32)),
540                ("1 ether", Uint(64)),
541                // int
542                ("-1 seconds", Int(8)),
543                ("-1 minutes", Int(8)),
544                ("-1 hours", Int(16)),
545                ("-1 days", Int(24)),
546                ("-1 weeks", Int(24)),
547                ("-1 wei", Int(8)),
548                ("-1 gwei", Int(32)),
549                ("-1 ether", Int(64)),
550                //
551                ("true ? 1 : 0", Uint(8)),
552                // misc
553                //
554
555                // ops
556                // uint
557                ("1 + 1", Uint(8)),
558                ("1 - 1", Uint(8)),
559                ("1 * 1", Uint(8)),
560                ("1 / 1", Uint(8)),
561                ("1 % 1", Uint(8)),
562                ("1 ** 1", Uint(8)),
563                ("1 | 1", Uint(8)),
564                ("1 & 1", Uint(8)),
565                ("1 ^ 1", Uint(8)),
566                ("1 >> 1", Uint(8)),
567                ("1 << 1", Uint(8)),
568                // int
569                ("int(1) + 1", Int(256)),
570                ("int(1) - 1", Int(256)),
571                ("int(1) * 1", Int(256)),
572                ("int(1) / 1", Int(256)),
573                ("1 + int(1)", Int(256)),
574                ("1 - int(1)", Int(256)),
575                ("1 * int(1)", Int(256)),
576                ("1 / int(1)", Int(256)),
577                //
578
579                // assign
580                ("uint256 a; a--", Uint(256)),
581                ("uint256 a; --a", Uint(256)),
582                ("uint256 a; a++", Uint(256)),
583                ("uint256 a; ++a", Uint(256)),
584                ("uint256 a; a   = 1", Uint(256)),
585                ("uint256 a; a  += 1", Uint(256)),
586                ("uint256 a; a  -= 1", Uint(256)),
587                ("uint256 a; a  *= 1", Uint(256)),
588                ("uint256 a; a  /= 1", Uint(256)),
589                ("uint256 a; a  %= 1", Uint(256)),
590                ("uint256 a; a  &= 1", Uint(256)),
591                ("uint256 a; a  |= 1", Uint(256)),
592                ("uint256 a; a  ^= 1", Uint(256)),
593                ("uint256 a; a <<= 1", Uint(256)),
594                ("uint256 a; a >>= 1", Uint(256)),
595                //
596
597                // bool
598                ("true && true", Bool),
599                ("true || true", Bool),
600                ("true == true", Bool),
601                ("true != true", Bool),
602                ("!true", Bool),
603                //
604            ]
605        };
606
607        let source = &mut source();
608
609        let array_expressions: &[(&str, DynSolType)] = &[
610            ("[1, 2, 3]", fixed_array(DynSolType::Uint(8), 3)),
611            ("[uint8(1), 2, 3]", fixed_array(DynSolType::Uint(8), 3)),
612            ("[int8(1), 2, 3]", fixed_array(DynSolType::Int(8), 3)),
613            ("new uint256[](3)", array(DynSolType::Uint(256))),
614            ("uint256[] memory a = new uint256[](3);\na[0]", DynSolType::Uint(256)),
615        ];
616        generic_type_test(source, array_expressions);
617        generic_type_test(source, EXPRESSIONS);
618    }
619
620    #[test]
621    fn test_types() {
622        static TYPES: &[(&str, DynSolType)] = {
623            use DynSolType::*;
624            &[
625                // bool
626                ("bool", Bool),
627                ("true", Bool),
628                ("false", Bool),
629                //
630
631                // int and uint
632                ("uint", Uint(256)),
633                ("uint(1)", Uint(256)),
634                ("1", Uint(8)),
635                ("0x01", Uint(8)),
636                ("int", Int(256)),
637                ("int(1)", Int(256)),
638                ("int(-1)", Int(256)),
639                ("-1", Int(8)),
640                ("-0x01", Int(8)),
641                //
642
643                // address
644                ("address", Address),
645                ("address(0)", Address),
646                ("0x690B9A9E9aa1C9dB991C7721a92d351Db4FaC990", Address),
647                ("payable(0)", Address),
648                ("payable(address(0))", Address),
649                //
650
651                // string
652                ("string", String),
653                ("string(\"hello world\")", String),
654                ("\"hello world\"", String),
655                ("unicode\"hello world 😀\"", String),
656                //
657
658                // bytes
659                ("bytes", Bytes),
660                ("bytes(\"hello world\")", Bytes),
661                ("bytes(unicode\"hello world 😀\")", Bytes),
662                ("hex\"68656c6c6f20776f726c64\"", Bytes),
663                //
664            ]
665        };
666
667        let mut types: Vec<(String, DynSolType)> = Vec::with_capacity(96 + 32 + 100);
668        for (n, b) in (8..=256).step_by(8).zip(1..=32) {
669            types.push((format!("uint{n}(0)"), DynSolType::Uint(n)));
670            types.push((format!("int{n}(0)"), DynSolType::Int(n)));
671            types.push((format!("bytes{b}(0x00)"), DynSolType::FixedBytes(b)));
672        }
673
674        for n in 1..=32 {
675            types.push((
676                format!("uint256[{n}]"),
677                DynSolType::FixedArray(Box::new(DynSolType::Uint(256)), n),
678            ));
679        }
680
681        generic_type_test(&mut source(), TYPES);
682        generic_type_test(&mut source(), &types);
683    }
684
685    #[test]
686    fn test_global_vars() {
687        init_tracing();
688
689        // https://docs.soliditylang.org/en/latest/cheatsheet.html#global-variables
690        let global_variables = {
691            use DynSolType::*;
692            &[
693                // abi
694                ("abi.decode(bytes(\"\"), (uint8[13]))", FixedArray(Box::new(Uint(8)), 13)),
695                ("abi.decode(bytes(\"\"), (address, bytes))", Tuple(vec![Address, Bytes])),
696                ("abi.decode(bytes(\"\"), (uint112, uint48))", Tuple(vec![Uint(112), Uint(48)])),
697                ("abi.encode(1, 2)", Bytes),
698                ("abi.encodePacked(uint256(1), uint256(2))", Bytes),
699                ("abi.encodeWithSelector(bytes4(0), 1, 2)", Bytes),
700                ("abi.encodeWithSignature(\"f(uint256)\", 1)", Bytes),
701                //
702
703                //
704                ("bytes.concat()", Bytes),
705                ("bytes.concat(bytes(\"\"))", Bytes),
706                ("bytes.concat(bytes(\"\"), bytes(\"\"))", Bytes),
707                ("string.concat()", String),
708                ("string.concat(\"\")", String),
709                ("string.concat(\"\", \"\")", String),
710                //
711
712                // block
713                ("block.basefee", Uint(256)),
714                ("block.chainid", Uint(256)),
715                ("block.coinbase", Address),
716                ("block.difficulty", Uint(256)),
717                ("block.gaslimit", Uint(256)),
718                ("block.number", Uint(256)),
719                ("block.timestamp", Uint(256)),
720                //
721
722                // tx
723                ("gasleft()", Uint(256)),
724                ("msg.data", Bytes),
725                ("msg.sender", Address),
726                ("msg.sig", FixedBytes(4)),
727                ("msg.value", Uint(256)),
728                ("tx.gasprice", Uint(256)),
729                ("tx.origin", Address),
730                //
731
732                // assertions
733                // assert(bool)
734                // require(bool)
735                // revert()
736                // revert(string)
737                //
738
739                //
740                ("blockhash(0)", FixedBytes(32)),
741                ("keccak256(bytes(\"\"))", FixedBytes(32)),
742                ("sha256(bytes(\"\"))", FixedBytes(32)),
743                ("ripemd160(bytes(\"\"))", FixedBytes(20)),
744                ("ecrecover(bytes32(0), 0, bytes32(0), bytes32(0))", Address),
745                ("addmod(1, 2, 3)", Uint(256)),
746                ("mulmod(1, 2, 3)", Uint(256)),
747                //
748
749                // address
750                ("address(0)", Address),
751                ("address(this)", Address),
752                // ("super", Type::Custom("super".to_string))
753                // (selfdestruct(address payable), None)
754                ("address(0).balance", Uint(256)),
755                ("address(0).code", Bytes),
756                ("address(0).codehash", FixedBytes(32)),
757                ("payable(address(0)).send(1)", Bool),
758                // (address.transfer(uint256), None)
759                //
760
761                // type
762                ("type(C).name", String),
763                ("type(C).creationCode", Bytes),
764                ("type(C).runtimeCode", Bytes),
765                ("type(I).interfaceId", FixedBytes(4)),
766                ("type(uint256).min", Uint(256)),
767                ("type(int128).min", Int(128)),
768                ("type(int256).min", Int(256)),
769                ("type(uint256).max", Uint(256)),
770                ("type(int128).max", Int(128)),
771                ("type(int256).max", Int(256)),
772                ("type(Enum1).min", Uint(8)),
773                ("type(Enum1).max", Uint(8)),
774                // function
775                ("this.run.address", Address),
776                ("this.run.selector", FixedBytes(4)),
777            ]
778        };
779
780        generic_type_test(&mut source(), global_variables);
781    }
782
783    #[track_caller]
784    fn source() -> TestSessionSource {
785        // synchronize solc install
786        static PRE_INSTALL_SOLC_LOCK: Mutex<bool> = Mutex::new(false);
787
788        // on some CI targets installing results in weird malformed solc files, we try installing it
789        // multiple times
790        let version = "0.8.20";
791        for _ in 0..3 {
792            let mut is_preinstalled = PRE_INSTALL_SOLC_LOCK.lock().unwrap();
793            if !*is_preinstalled {
794                let solc = Solc::find_or_install(&version.parse().unwrap())
795                    .map(|solc| (solc.version.clone(), solc));
796                match solc {
797                    Ok((v, solc)) => {
798                        // successfully installed
799                        let _ = sh_println!("found installed Solc v{v} @ {}", solc.solc.display());
800                        break;
801                    }
802                    Err(e) => {
803                        // try reinstalling
804                        let _ = sh_err!("error while trying to re-install Solc v{version}: {e}");
805                        let solc = Solc::blocking_install(&version.parse().unwrap());
806                        if solc.map_err(SolcError::from).is_ok() {
807                            *is_preinstalled = true;
808                            break;
809                        }
810                    }
811                }
812            }
813        }
814
815        SessionSource::new(Default::default()).unwrap()
816    }
817
818    fn array(ty: DynSolType) -> DynSolType {
819        DynSolType::Array(Box::new(ty))
820    }
821
822    fn fixed_array(ty: DynSolType, len: usize) -> DynSolType {
823        DynSolType::FixedArray(Box::new(ty), len)
824    }
825
826    /// Lowers the given snippet appended to the REPL contract via solar's HIR pipeline (without
827    /// invoking solc) and returns the resulting `DynSolType` of the last expression statement in
828    /// the run() body.
829    ///
830    /// Tests bypass `SessionSource::build` (which routes through foundry-compilers + solc) so the
831    /// Solar type table can be exercised directly without compiling each snippet through solc.
832    fn get_type_ethabi(s: &mut TestSessionSource, input: &str, clear: bool) -> Option<DynSolType> {
833        if clear {
834            s.clear();
835        }
836
837        // Always declare sample types so `type(...)` tests have concrete definitions.
838        *s = s.clone_with_new_line("enum Enum1 { A }".into()).unwrap().0;
839        *s = s.clone_with_new_line("contract C {}".into()).unwrap().0;
840        *s = s.clone_with_new_line("interface I {}".into()).unwrap().0;
841
842        let input = format!("{};", input.trim_end().trim_end_matches(';'));
843        let (new_source, _) = s.clone_with_new_line(input).unwrap();
844        *s = new_source.clone();
845
846        let src = new_source.to_repl_source();
847        let mut opts = solar::interface::config::CompileOpts::default();
848        opts.unstable.typeck = true;
849        let sess = solar::interface::Session::builder()
850            .opts(opts)
851            .with_buffer_emitter(Default::default())
852            .build();
853        let mut compiler = Compiler::new(sess);
854
855        compiler.enter_mut(|c| -> Option<DynSolType> {
856            // Stage 1: parse, lower, and analyze (mutable access required).
857            let analyzed = {
858                let mut pcx = c.parse();
859                let file = c
860                    .sess()
861                    .source_map()
862                    .new_source_file(
863                        std::path::PathBuf::from(new_source.file_name.clone()),
864                        src.clone(),
865                    )
866                    .ok()?;
867                pcx.add_file(file);
868                pcx.parse();
869                matches!(c.lower_asts(), Ok(ControlFlow::Continue(())))
870                    && matches!(c.analysis(), Ok(ControlFlow::Continue(())))
871            };
872            if !analyzed {
873                return None;
874            }
875
876            // Stage 2: walk HIR (immutable access).
877            let gcx = c.gcx();
878            let hir = &gcx.hir;
879            let repl = hir.contracts().find(|c| c.name.as_str() == "REPL")?;
880            let run_fid = repl
881                .functions()
882                .find(|&f| hir.function(f).name.as_ref().map(|n| n.as_str()) == Some("run"))?;
883            let body = hir.function(run_fid).body?;
884            let last = body.last()?;
885            let expr = match last.kind {
886                StmtKind::Expr(e) => e,
887                _ => return None,
888            };
889            expr_to_dyn(gcx, expr)
890        })
891    }
892
893    fn generic_type_test<'a, T, I>(s: &mut TestSessionSource, input: I)
894    where
895        T: AsRef<str> + std::fmt::Display + 'a,
896        I: IntoIterator<Item = &'a (T, DynSolType)> + 'a,
897    {
898        let mut failures = Vec::new();
899        for (input, expected) in input {
900            let input = input.as_ref();
901            let ty = get_type_ethabi(s, input, true);
902            if ty.as_ref() != Some(expected) {
903                failures.push(format!("{input}: got {ty:?}, expected {expected:?}"));
904            }
905        }
906        assert!(failures.is_empty(), "\n{}", failures.join("\n"));
907    }
908
909    fn init_tracing() {
910        let _ = tracing_subscriber::FmtSubscriber::builder()
911            .with_env_filter(tracing_subscriber::EnvFilter::from_default_env())
912            .try_init();
913    }
914}