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