Skip to main content

chisel/
executor.rs

1//! Executor
2//!
3//! This module contains the execution logic for the [SessionSource].
4
5use crate::{
6    prelude::{ChiselDispatcher, ChiselResult, ChiselRunner, SessionSource, SolidityHelper},
7    source::CachedBackend,
8};
9use alloy_dyn_abi::{DynSolType, DynSolValue};
10use alloy_json_abi::EventParam;
11use alloy_primitives::{Address, B256, U256, hex};
12use eyre::{Result, WrapErr};
13use foundry_compilers::Artifact;
14use foundry_evm::{
15    backend::Backend,
16    core::evm::{BlockEnvFor, FoundryEvmNetwork, SpecFor, TxEnvFor},
17    decode::decode_console_logs,
18    inspectors::CheatsConfig,
19    opts::{ExecutionSpecContext, resolve_execution_spec},
20    traces::TraceRequirements,
21};
22use solar::{
23    ast::{ElementaryType, LitKind, StmtKind as AstStmtKind, StrKind, UnOpKind, yul},
24    interface::Session,
25    sema::{
26        hir::{Event, Expr, ExprKind, StmtKind},
27        ty::{Gcx, Ty, TyKind},
28    },
29};
30use std::ops::ControlFlow;
31use yansi::Paint;
32
33/// Result of inspecting a Solidity snippet.
34#[derive(Debug)]
35pub struct InspectResult {
36    /// Whether the input was fully handled by inspection.
37    pub control_flow: ControlFlow<()>,
38    /// The formatted value to display, if any.
39    pub formatted_output: Option<String>,
40    /// An expression that recreates the inspected value, if any.
41    pub last_result: Option<String>,
42    /// Input to execute and persist after inspection, if it differs from the original input.
43    pub replay_input: Option<String>,
44}
45
46impl InspectResult {
47    const fn empty(control_flow: ControlFlow<()>) -> Self {
48        Self { control_flow, formatted_output: None, last_result: None, replay_input: None }
49    }
50}
51
52struct YulInspection {
53    inspector_input: String,
54    replay_input: String,
55}
56
57fn yul_inspection(input: &str, session_source: &str) -> Option<YulInspection> {
58    let sess = Session::builder().with_buffer_emitter(Default::default()).build();
59    sess.enter_sequential(|| {
60        let arena = solar::ast::Arena::new();
61        let mut parser = solar::parse::Parser::from_source_code(
62            &sess,
63            &arena,
64            "ChiselInput.sol".to_string().into(),
65            input,
66        )
67        .ok()?;
68        let stmt = parser.parse_stmt().map_err(|err| err.emit()).ok()?;
69        if !parser.token.is_eof() {
70            return None;
71        }
72        let AstStmtKind::Assembly(assembly) = &stmt.kind else { return None };
73        let last = assembly.block.stmts.last()?;
74        let yul::StmtKind::Expr(expr) = &last.kind else { return None };
75
76        let expr_range = sess.source_map().span_to_source(expr.span).ok()?.data;
77        let expression = input.get(expr_range.clone())?;
78        let result_var = std::iter::once("__chisel_yul_result".to_string())
79            .chain((1..).map(|suffix| format!("__chisel_yul_result_{suffix}")))
80            .find(|name| !input.contains(name) && !session_source.contains(name))?;
81
82        let mut assembly = input.to_string();
83        assembly.replace_range(expr_range.clone(), &format!("{result_var} := {expression}"));
84        let inspector_input = format!(
85            "uint256 {result_var}; {assembly}\nbytes memory inspectoor = abi.encode({result_var});"
86        );
87
88        let mut replay_input = input.to_string();
89        replay_input.replace_range(expr_range, &format!("pop({expression})"));
90        Some(YulInspection { inspector_input, replay_input })
91    })
92}
93
94/// Executor implementation for [SessionSource]
95impl<FEN: FoundryEvmNetwork> SessionSource<FEN> {
96    /// Runs the source with the [ChiselRunner]
97    pub async fn execute(&mut self) -> Result<ChiselResult> {
98        // Recompile the project and ensure no errors occurred.
99        let output = self.build()?;
100
101        let (bytecode, final_pc) = output.enter(|output| -> Result<_> {
102            let contract = output
103                .repl_contract()
104                .ok_or_else(|| eyre::eyre!("failed to find REPL contract"))?;
105            trace!(?contract, "REPL contract");
106            let bytecode = contract
107                .get_bytecode_bytes()
108                .ok_or_else(|| eyre::eyre!("No bytecode found for `REPL` contract"))?;
109            Ok((bytecode.into_owned(), output.final_pc(contract)?))
110        })?;
111        let final_pc = final_pc.unwrap_or_default();
112        let mut runner = self.build_runner(final_pc).await?;
113        runner.run(bytecode)
114    }
115
116    /// Inspect a contract element inside of the current session
117    ///
118    /// ### Takes
119    ///
120    /// A solidity snippet
121    ///
122    /// ### Returns
123    ///
124    /// If the input is valid, returns its [`InspectResult`].
125    pub async fn inspect(&self, input: &str) -> Result<InspectResult> {
126        let line = format!("bytes memory inspectoor = abi.encode({input});");
127        let (mut source, replay_input) = match self.clone_with_new_line(line) {
128            Ok((source, _)) => (source, None),
129            Err(err) => {
130                debug!(%err, "failed to build new source for inspection");
131                let Some(inspection) = yul_inspection(input, &self.to_repl_source()) else {
132                    return Ok(InspectResult::empty(ControlFlow::Continue(())));
133                };
134                if self
135                    .clone_with_new_line(input.to_string())
136                    .is_ok_and(|(source, _)| source.build().is_ok())
137                {
138                    return Ok(InspectResult::empty(ControlFlow::Continue(())));
139                }
140                let Ok((source, _)) = self.clone_with_new_line(inspection.inspector_input) else {
141                    return Ok(InspectResult::empty(ControlFlow::Continue(())));
142                };
143                (source, Some(inspection.replay_input))
144            }
145        };
146
147        let mut source_without_inspector = self.clone();
148
149        // Events and tuples fails compilation due to it not being able to be encoded in
150        // `inspectoor`. If that happens, try executing without the inspector.
151        let (mut res, err) = match source.execute().await {
152            Ok(res) => (res, None),
153            Err(err) => {
154                debug!(?err, %input, "execution failed");
155                let should_execute = self
156                    .clone_with_new_line(input.to_string())
157                    .ok()
158                    .and_then(|(source, do_execute)| {
159                        if !do_execute {
160                            return None;
161                        }
162                        source.build().ok().map(|output| {
163                            output.enter(|output| {
164                                let body = output.run_func_body();
165                                let Some(last) = body.last() else { return false };
166                                let StmtKind::Expr(expr) = last.kind else { return false };
167                                should_continue(expr)
168                            })
169                        })
170                    })
171                    .unwrap_or(false);
172                if should_execute {
173                    return Ok(InspectResult::empty(ControlFlow::Continue(())));
174                }
175                match source_without_inspector.execute().await {
176                    Ok(res) => (res, Some(err)),
177                    Err(_) => {
178                        if self.config.foundry_config.verbosity >= 3 {
179                            sh_err!("Could not inspect: {err}")?;
180                        }
181                        return Ok(InspectResult::empty(ControlFlow::Continue(())));
182                    }
183                }
184            }
185        };
186
187        // If abi-encoding the input failed, check whether it is an event
188        if let Some(err) = err {
189            let output = source_without_inspector.build()?;
190
191            let formatted_event = output.enter(|output| {
192                let gcx = output.gcx();
193                output.get_event(input).map(|eid| format_event_definition(gcx, gcx.hir.event(eid)))
194            });
195            if let Some(formatted_event) = formatted_event {
196                return Ok(InspectResult {
197                    control_flow: ControlFlow::Break(()),
198                    formatted_output: Some(formatted_event?),
199                    last_result: None,
200                    replay_input: None,
201                });
202            }
203
204            // we were unable to check the event
205            if self.config.foundry_config.verbosity >= 3 {
206                sh_err!("Failed eval: {err}")?;
207            }
208
209            debug!(%err, %input, "failed abi encode input");
210            return Ok(InspectResult::empty(ControlFlow::Break(())));
211        }
212        drop(source_without_inspector);
213
214        let Some((stack, memory)) = &res.state else {
215            // Show traces and logs, if there are any, and return an error
216            if let Ok(decoder) = ChiselDispatcher::decode_traces(&source.config, &mut res).await {
217                ChiselDispatcher::<FEN>::show_traces(&decoder, &mut res).await?;
218            }
219            let decoded_logs = decode_console_logs(&res.logs);
220            if !decoded_logs.is_empty() {
221                sh_println!("{}", "Logs:".green())?;
222                for log in decoded_logs {
223                    sh_println!("  {log}")?;
224                }
225            }
226
227            return Err(eyre::eyre!("Failed to inspect expression"));
228        };
229
230        // Either the expression referred to by `input`, or the last expression,
231        // which was wrapped in `abi.encode`.
232        let generated_output = source.build()?;
233
234        // Inside the compiler closure, infer the DynSolType of the inspected expression and
235        // determine whether the REPL should continue.
236        let res_ty = generated_output.enter(|out| -> Option<(bool, DynSolType)> {
237            let gcx = out.gcx();
238
239            // Find the appended `bytes memory inspectoor = abi.encode(<input>);` and pull out the
240            // first call argument.
241            let block = out.run_func_body();
242            let last = block.last()?;
243            let StmtKind::DeclSingle(vid) = last.kind else { return None };
244            let var = gcx.hir.variable(vid);
245            let init = var.initializer?;
246            let ExprKind::Call(_callee, args, _) = &init.kind else { return None };
247            let inner_expr = args.exprs().next()?;
248
249            let ty = expr_to_dyn(gcx, inner_expr)?;
250            Some((should_continue(inner_expr), ty))
251        });
252
253        let Some((cont, ty)) = res_ty else {
254            return Ok(InspectResult::empty(ControlFlow::Continue(())));
255        };
256
257        // the file compiled correctly, thus the last stack item must be the memory offset of
258        // the `bytes memory inspectoor` value
259        let data = (|| -> Option<_> {
260            let mut offset: usize = stack.last()?.try_into().ok()?;
261            debug!("inspect memory @ {offset}: {}", hex::encode(memory));
262            let mem_offset = memory.get(offset..offset + 32)?;
263            let len: usize = U256::try_from_be_slice(mem_offset)?.try_into().ok()?;
264            offset += 32;
265            memory.get(offset..offset + len)
266        })();
267        let Some(data) = data else {
268            eyre::bail!("Failed to inspect last expression: could not retrieve data from memory");
269        };
270        let last_result = format!("abi.decode(hex\"{}\", ({ty}))", hex::encode(data));
271        let token = ty.abi_decode(data).wrap_err("Could not decode inspected values")?;
272        let c = if cont || replay_input.is_some() {
273            ControlFlow::Continue(())
274        } else {
275            ControlFlow::Break(())
276        };
277        Ok(InspectResult {
278            control_flow: c,
279            formatted_output: Some(format_token(token)),
280            last_result: Some(last_result),
281            replay_input,
282        })
283    }
284
285    async fn build_runner(&mut self, final_pc: usize) -> Result<ChiselRunner<FEN>> {
286        let (mut evm_env, tx_env, backend, resolved_fork) = match self.config.cached_backend.clone()
287        {
288            Some(CachedBackend { backend, resolved_fork }) => {
289                let (evm_env, tx_env) = self
290                    .config
291                    .evm_opts
292                    .env_with_resolved_fork::<SpecFor<FEN>, BlockEnvFor<FEN>, TxEnvFor<FEN>>(
293                        resolved_fork.as_ref(),
294                    )
295                    .await?;
296                (evm_env, tx_env, backend, resolved_fork)
297            }
298            None => {
299                let (evm_env, tx_env, resolved_fork) = self
300                    .config
301                    .evm_opts
302                    .env_resolved::<SpecFor<FEN>, BlockEnvFor<FEN>, TxEnvFor<FEN>>()
303                    .await?;
304                let fork = self.config.evm_opts.get_fork_resolved(
305                    &self.config.foundry_config,
306                    evm_env.cfg_env.chain_id,
307                    resolved_fork.as_ref(),
308                );
309                let backend = Backend::spawn(fork)?;
310                self.config.cached_backend = Some(CachedBackend {
311                    backend: backend.clone(),
312                    resolved_fork: resolved_fork.clone(),
313                });
314                (evm_env, tx_env, backend, resolved_fork)
315            }
316        };
317        let fork_context = resolved_fork.as_ref().map(|fork| fork.context());
318        let fork_chain_id = fork_context.map(|context| context.source_chain_id);
319        let fork_hardfork = fork_context.and_then(|context| context.hardfork);
320        self.config.source_chain_id = fork_chain_id;
321        self.config.resolved_hardfork = resolve_execution_spec(
322            self.config.foundry_config.evm_version,
323            self.config.foundry_config.hardfork,
324            &mut evm_env,
325            ExecutionSpecContext::local_or_fork(fork_chain_id, fork_hardfork),
326            None,
327        );
328
329        let executor = self
330            .config
331            .executor_builder
332            .clone()
333            .inspectors(|stack| {
334                stack
335                    .logs(self.config.foundry_config.live_logs)
336                    .chisel_state(final_pc)
337                    .trace_requirements(TraceRequirements::none().with_calls(true))
338                    .cheatcodes(
339                        CheatsConfig::new(
340                            &self.config.foundry_config,
341                            self.config.evm_opts.clone(),
342                            None,
343                            None,
344                            false,
345                        )
346                        .into(),
347                    )
348            })
349            .gas_limit(self.config.evm_opts.gas_limit())
350            .legacy_assertions(self.config.foundry_config.legacy_assertions)
351            .build(evm_env, tx_env, backend, self.config.evm_opts.networks);
352
353        Ok(ChiselRunner::new(executor, U256::MAX, Address::ZERO, self.config.calldata.clone()))
354    }
355}
356
357/// Formats a value into an inspection message
358// TODO: Verbosity option
359fn format_token(token: DynSolValue) -> String {
360    match token {
361        DynSolValue::Address(a) => {
362            format!("Type: {}\n└ Data: {}", "address".red(), a.cyan())
363        }
364        DynSolValue::FixedBytes(b, byte_len) => {
365            format!(
366                "Type: {}\n└ Data: {}",
367                format!("bytes{byte_len}").red(),
368                hex::encode_prefixed(b).cyan()
369            )
370        }
371        DynSolValue::Int(i, bit_len) => {
372            format!(
373                "Type: {}\n├ Hex: {}\n├ Hex (full word): {}\n└ Decimal: {}",
374                format!("int{bit_len}").red(),
375                format!(
376                    "0x{}",
377                    format!("{i:x}")
378                        .chars()
379                        .skip(if i.is_negative() { 64 - bit_len / 4 } else { 0 })
380                        .collect::<String>()
381                )
382                .cyan(),
383                hex::encode_prefixed(B256::from(i)).cyan(),
384                i.cyan()
385            )
386        }
387        DynSolValue::Uint(i, bit_len) => {
388            format!(
389                "Type: {}\n├ Hex: {}\n├ Hex (full word): {}\n└ Decimal: {}",
390                format!("uint{bit_len}").red(),
391                format!("0x{i:x}").cyan(),
392                hex::encode_prefixed(B256::from(i)).cyan(),
393                i.cyan()
394            )
395        }
396        DynSolValue::Bool(b) => {
397            format!("Type: {}\n└ Value: {}", "bool".red(), b.cyan())
398        }
399        DynSolValue::Bytes(bytes) => {
400            format!(
401                "Type: {}\n└ Data: {}",
402                "dynamic bytes".red(),
403                hex::encode_prefixed(bytes).cyan()
404            )
405        }
406        token @ DynSolValue::String(_) => {
407            let hex = hex::encode(token.abi_encode());
408            let s = token.as_str().expect("matched string value");
409            format!(
410                "Type: {}\n├ UTF-8: {}\n├ Hex (Memory):\n├─ Length ({}): {}\n├─ Contents ({}): {}\n├ Hex (Tuple Encoded):\n├─ Pointer ({}): {}\n├─ Length ({}): {}\n└─ Contents ({}): {}",
411                "string".red(),
412                s.cyan(),
413                "[0x00:0x20]".yellow(),
414                format!("0x{}", &hex[64..128]).cyan(),
415                "[0x20:..]".yellow(),
416                format!("0x{}", &hex[128..]).cyan(),
417                "[0x00:0x20]".yellow(),
418                format!("0x{}", &hex[..64]).cyan(),
419                "[0x20:0x40]".yellow(),
420                format!("0x{}", &hex[64..128]).cyan(),
421                "[0x40:..]".yellow(),
422                format!("0x{}", &hex[128..]).cyan(),
423            )
424        }
425        DynSolValue::FixedArray(tokens) | DynSolValue::Array(tokens) => {
426            let mut out = format!(
427                "{}({}) = {}",
428                "array".red(),
429                format!("{}", tokens.len()).yellow(),
430                '['.red()
431            );
432            for token in tokens {
433                out.push_str("\n  ├ ");
434                out.push_str(&format_token(token).replace('\n', "\n  "));
435                out.push('\n');
436            }
437            out.push_str(&']'.red().to_string());
438            out
439        }
440        DynSolValue::Tuple(tokens) => {
441            let displayed_types = tokens
442                .iter()
443                .map(|t| t.sol_type_name().unwrap_or_default())
444                .collect::<Vec<_>>()
445                .join(", ");
446            let mut out =
447                format!("{}({}) = {}", "tuple".red(), displayed_types.yellow(), '('.red());
448            for token in tokens {
449                out.push_str("\n  ├ ");
450                out.push_str(&format_token(token).replace('\n', "\n  "));
451                out.push('\n');
452            }
453            out.push_str(&')'.red().to_string());
454            out
455        }
456        _ => {
457            unimplemented!()
458        }
459    }
460}
461
462/// Formats an [`Event`] into an inspection message.
463// TODO: Verbosity option
464fn format_event_definition(gcx: Gcx<'_>, event: &Event<'_>) -> Result<String> {
465    let event_name = event.name.as_str().to_string();
466    let inputs = event
467        .parameters
468        .iter()
469        .map(|&pid| {
470            let var = gcx.hir.variable(pid);
471            let name =
472                var.name.map(|n| n.as_str().to_string()).unwrap_or_else(|| "<anonymous>".into());
473            let kind = solar_ty_to_dyn(gcx, gcx.type_of_item(pid.into()))
474                .ok_or_else(|| eyre::eyre!("Invalid type in event {event_name}"))?;
475            Ok(EventParam {
476                name,
477                ty: kind.to_string(),
478                components: vec![],
479                indexed: var.indexed,
480                internal_type: None,
481            })
482        })
483        .collect::<Result<Vec<_>>>()?;
484    let event = alloy_json_abi::Event { name: event_name, inputs, anonymous: event.anonymous };
485
486    Ok(format!(
487        "Type: {}\n├ Name: {}\n├ Signature: {:?}\n└ Selector: {:?}",
488        "event".red(),
489        SolidityHelper::new().highlight(&format!(
490            "{}({})",
491            event.name,
492            event
493                .inputs
494                .iter()
495                .map(|param| format!(
496                    "{}{}{}",
497                    param.ty,
498                    if param.indexed { " indexed" } else { "" },
499                    if param.name.is_empty() {
500                        String::default()
501                    } else {
502                        format!(" {}", param.name)
503                    },
504                ))
505                .collect::<Vec<_>>()
506                .join(", ")
507        )),
508        event.signature().cyan(),
509        event.selector().cyan(),
510    ))
511}
512
513/// Converts an [`Expr`] directly to a [`DynSolType`] for ABI inspection.
514fn expr_to_dyn(gcx: Gcx<'_>, expr: &Expr<'_>) -> Option<DynSolType> {
515    gcx.type_of_expr(expr.id).and_then(|ty| solar_expr_ty_to_dyn(gcx, ty, expr))
516}
517
518/// Whether execution should continue after inspecting this expression.
519#[inline]
520fn should_continue(expr: &Expr<'_>) -> bool {
521    match &expr.kind {
522        // assignments and compound assignments
523        ExprKind::Assign(_, _, _) => true,
524        // Delete expressions.
525        ExprKind::Delete(_) => true,
526        // ++/-- pre/post operations
527        ExprKind::Unary(op, _) => matches!(
528            op.kind,
529            UnOpKind::PreInc | UnOpKind::PreDec | UnOpKind::PostInc | UnOpKind::PostDec
530        ),
531        // Array.pop()
532        ExprKind::Call(callee, _, _) => match &callee.kind {
533            ExprKind::Member(_, ident) => ident.as_str() == "pop",
534            _ => false,
535        },
536        _ => false,
537    }
538}
539
540/// Maps a solar [`ElementaryType`] to a [`DynSolType`].
541const fn elementary_to_dyn(et: ElementaryType) -> Option<DynSolType> {
542    Some(match et {
543        ElementaryType::Address(_) => DynSolType::Address,
544        ElementaryType::Bool => DynSolType::Bool,
545        ElementaryType::String => DynSolType::String,
546        ElementaryType::Bytes => DynSolType::Bytes,
547        ElementaryType::Int(size) => DynSolType::Int(size.bits() as usize),
548        ElementaryType::UInt(size) => DynSolType::Uint(size.bits() as usize),
549        ElementaryType::FixedBytes(size) => DynSolType::FixedBytes(size.bytes() as usize),
550        // Fixed-point numbers are not yet representable as DynSolType.
551        ElementaryType::Fixed(_, _) | ElementaryType::UFixed(_, _) => return None,
552    })
553}
554
555/// Maps a solar [`Ty`] to a [`DynSolType`].
556fn solar_expr_ty_to_dyn<'gcx>(gcx: Gcx<'gcx>, ty: Ty<'gcx>, expr: &Expr<'_>) -> Option<DynSolType> {
557    // `expr` is the inspected expression inside Chisel's generated `abi.encode(...)` call. Solar
558    // currently reports hex string literals as `StringLiteral`, but solc ABI-encodes
559    // `hex"..."` literals as dynamic bytes in that context.
560    let expr = expr.peel_parens();
561    if matches!(expr.kind, ExprKind::Lit(lit) if matches!(lit.kind, LitKind::Str(StrKind::Hex, ..)))
562    {
563        return Some(DynSolType::Bytes);
564    }
565
566    solar_ty_to_dyn(gcx, ty)
567}
568
569fn solar_ty_to_dyn<'gcx>(gcx: Gcx<'gcx>, ty: Ty<'gcx>) -> Option<DynSolType> {
570    match ty.kind {
571        TyKind::Elementary(et) => elementary_to_dyn(et),
572        TyKind::Ref(inner, _) => solar_ty_to_dyn(gcx, inner),
573        TyKind::Array(elem, n) => {
574            let inner = solar_ty_to_dyn(gcx, elem)?;
575            let size: usize = n.try_into().ok()?;
576            Some(DynSolType::FixedArray(Box::new(inner), size))
577        }
578        TyKind::DynArray(elem) => {
579            let inner = solar_ty_to_dyn(gcx, elem)?;
580            Some(DynSolType::Array(Box::new(inner)))
581        }
582        TyKind::Slice(array) => solar_ty_to_dyn(gcx, array),
583        TyKind::Tuple(tys) => {
584            Some(DynSolType::Tuple(tys.iter().filter_map(|t| solar_ty_to_dyn(gcx, *t)).collect()))
585        }
586        TyKind::Mapping(_, _) => None,
587        TyKind::Struct(sid) => Some(DynSolType::Tuple(
588            gcx.struct_field_types(sid).iter().filter_map(|t| solar_ty_to_dyn(gcx, *t)).collect(),
589        )),
590        TyKind::Enum(_) => Some(DynSolType::Uint(8)),
591        TyKind::Udvt(inner, _) => solar_ty_to_dyn(gcx, inner),
592        TyKind::Contract(_) => Some(DynSolType::Address),
593        // For a function-pointer type we return the ABI type of what the call *produces*, not a
594        // representation of the pointer itself. This is intentional: chisel inspects values, so
595        // the interesting type is the returned value.  A zero-return function pointer has no
596        // inspectable value, so we return `None`.
597        TyKind::Fn(f) => match f.returns.len() {
598            0 => None,
599            1 => solar_ty_to_dyn(gcx, f.returns[0]),
600            _ => Some(DynSolType::Tuple(
601                f.returns.iter().filter_map(|t| solar_ty_to_dyn(gcx, *t)).collect(),
602            )),
603        },
604        TyKind::Type(inner) => solar_ty_to_dyn(gcx, inner),
605        TyKind::Meta(inner) => solar_ty_to_dyn(gcx, inner),
606        TyKind::IntLiteral(neg, size, _) => {
607            let bits = (size.bits() as usize).max(8);
608            // Round up to the nearest multiple of 8 bits, capped at 256.
609            let bits = bits.div_ceil(8) * 8;
610            let bits = bits.min(256);
611            if neg {
612                Some(DynSolType::Int(bits.max(8)))
613            } else {
614                Some(DynSolType::Uint(bits.max(8)))
615            }
616        }
617        TyKind::StringLiteral(valid_utf8, _) => {
618            if valid_utf8 {
619                Some(DynSolType::String)
620            } else {
621                Some(DynSolType::Bytes)
622            }
623        }
624        TyKind::Module(_)
625        | TyKind::BuiltinModule(_)
626        | TyKind::Error(_, _)
627        | TyKind::Event(_, _)
628        | TyKind::Err(_) => None,
629        _ => None,
630    }
631}
632
633#[cfg(test)]
634mod tests {
635    use super::*;
636    use crate::source::SessionSourceConfig;
637    use foundry_compilers::{error::SolcError, solc::Solc};
638    use foundry_config::Config;
639    use foundry_evm::{core::evm::EthEvmNetwork, executors::ExecutorBuilder, opts::EvmOpts};
640    use foundry_evm_networks::{NetworkConfigs, celo::transfer::CELO_TRANSFER_ADDRESS};
641    use solar::sema::Compiler;
642    use std::sync::Mutex;
643
644    #[cfg(feature = "monad")]
645    use foundry_evm::core::{constants::MONAD_CHEATCODE_ADDRESS, evm::MonadEvmNetwork};
646
647    type TestSessionSource = SessionSource<EthEvmNetwork>;
648
649    async fn assert_celo_transfer_precompile(config: SessionSourceConfig<EthEvmNetwork>) {
650        let mut source = SessionSource::<EthEvmNetwork>::new(config).unwrap();
651        let mut runner = source.build_runner(0).await.unwrap();
652        let from = Address::with_last_byte(1);
653        let to = Address::with_last_byte(2);
654        let amount = U256::from(4);
655        runner.executor.set_balance(from, U256::from(10)).unwrap();
656        runner.executor.set_balance(to, U256::from(1)).unwrap();
657
658        let mut input = vec![0u8; 96];
659        input[12..32].copy_from_slice(from.as_slice());
660        input[44..64].copy_from_slice(to.as_slice());
661        input[64..96].copy_from_slice(&amount.to_be_bytes::<32>());
662        let result = runner
663            .executor
664            .transact_raw(Address::ZERO, CELO_TRANSFER_ADDRESS, input.into(), U256::ZERO)
665            .unwrap();
666
667        assert!(!result.reverted);
668        assert_eq!(runner.executor.get_balance(from).unwrap(), U256::from(6));
669        assert_eq!(runner.executor.get_balance(to).unwrap(), U256::from(5));
670    }
671
672    #[tokio::test(flavor = "multi_thread")]
673    async fn celo_network_reaches_fresh_and_restored_runners() {
674        let networks = NetworkConfigs::with_celo();
675        let mut evm_opts = EvmOpts { networks, ..Default::default() };
676        evm_opts.env.gas_limit = 30_000_000u64.into();
677        let config = SessionSourceConfig::<EthEvmNetwork> {
678            foundry_config: Config { networks, ..Default::default() },
679            evm_opts,
680            ..Default::default()
681        };
682
683        assert_celo_transfer_precompile(config.clone()).await;
684
685        let encoded = serde_json::to_string(&config).unwrap();
686        let mut restored =
687            serde_json::from_str::<SessionSourceConfig<EthEvmNetwork>>(&encoded).unwrap();
688        restored.initialize_local_context();
689        assert_celo_transfer_precompile(restored).await;
690    }
691
692    #[cfg(feature = "monad")]
693    #[tokio::test(flavor = "multi_thread")]
694    async fn chisel_runner_uses_dispatched_monad_tooling() {
695        let networks = NetworkConfigs::with_monad();
696        let mut source = SessionSource::<MonadEvmNetwork>::new(SessionSourceConfig {
697            foundry_config: Config { networks, ..Default::default() },
698            evm_opts: EvmOpts { networks, ..Default::default() },
699            executor_builder: ExecutorBuilder::<MonadEvmNetwork>::new(),
700            ..Default::default()
701        })
702        .unwrap();
703
704        let runner = source.build_runner(0).await.unwrap();
705        assert_eq!(
706            runner.executor.inspector().extra_cheatcode_addresses(),
707            &[MONAD_CHEATCODE_ADDRESS]
708        );
709    }
710
711    #[test]
712    fn test_expressions() {
713        static EXPRESSIONS: &[(&str, DynSolType)] = {
714            use DynSolType::*;
715            &[
716                // units
717                // uint
718                ("1 seconds", Uint(8)),
719                ("1 minutes", Uint(8)),
720                ("1 hours", Uint(16)),
721                ("1 days", Uint(24)),
722                ("1 weeks", Uint(24)),
723                ("1 wei", Uint(8)),
724                ("1 gwei", Uint(32)),
725                ("1 ether", Uint(64)),
726                // int
727                ("-1 seconds", Int(8)),
728                ("-1 minutes", Int(8)),
729                ("-1 hours", Int(16)),
730                ("-1 days", Int(24)),
731                ("-1 weeks", Int(24)),
732                ("-1 wei", Int(8)),
733                ("-1 gwei", Int(32)),
734                ("-1 ether", Int(64)),
735                //
736                ("true ? 1 : 0", Uint(8)),
737                // misc
738                //
739
740                // ops
741                // uint
742                ("1 + 1", Uint(8)),
743                ("1 - 1", Uint(8)),
744                ("1 * 1", Uint(8)),
745                ("1 / 1", Uint(8)),
746                ("1 % 1", Uint(8)),
747                ("1 ** 1", Uint(8)),
748                ("1 | 1", Uint(8)),
749                ("1 & 1", Uint(8)),
750                ("1 ^ 1", Uint(8)),
751                ("1 >> 1", Uint(8)),
752                ("1 << 1", Uint(8)),
753                // int
754                ("int(1) + 1", Int(256)),
755                ("int(1) - 1", Int(256)),
756                ("int(1) * 1", Int(256)),
757                ("int(1) / 1", Int(256)),
758                ("1 + int(1)", Int(256)),
759                ("1 - int(1)", Int(256)),
760                ("1 * int(1)", Int(256)),
761                ("1 / int(1)", Int(256)),
762                //
763
764                // assign
765                ("uint256 a; a--", Uint(256)),
766                ("uint256 a; --a", Uint(256)),
767                ("uint256 a; a++", Uint(256)),
768                ("uint256 a; ++a", Uint(256)),
769                ("uint256 a; a   = 1", Uint(256)),
770                ("uint256 a; a  += 1", Uint(256)),
771                ("uint256 a; a  -= 1", Uint(256)),
772                ("uint256 a; a  *= 1", Uint(256)),
773                ("uint256 a; a  /= 1", Uint(256)),
774                ("uint256 a; a  %= 1", Uint(256)),
775                ("uint256 a; a  &= 1", Uint(256)),
776                ("uint256 a; a  |= 1", Uint(256)),
777                ("uint256 a; a  ^= 1", Uint(256)),
778                ("uint256 a; a <<= 1", Uint(256)),
779                ("uint256 a; a >>= 1", Uint(256)),
780                //
781
782                // bool
783                ("true && true", Bool),
784                ("true || true", Bool),
785                ("true == true", Bool),
786                ("true != true", Bool),
787                ("!true", Bool),
788                //
789            ]
790        };
791
792        let source = &mut source();
793
794        let array_expressions: &[(&str, DynSolType)] = &[
795            ("[1, 2, 3]", fixed_array(DynSolType::Uint(8), 3)),
796            ("[uint8(1), 2, 3]", fixed_array(DynSolType::Uint(8), 3)),
797            ("[int8(1), 2, 3]", fixed_array(DynSolType::Int(8), 3)),
798            ("new uint256[](3)", array(DynSolType::Uint(256))),
799            ("uint256[] memory a = new uint256[](3);\na[0]", DynSolType::Uint(256)),
800        ];
801        generic_type_test(source, array_expressions);
802        generic_type_test(source, EXPRESSIONS);
803    }
804
805    #[test]
806    fn test_types() {
807        static TYPES: &[(&str, DynSolType)] = {
808            use DynSolType::*;
809            &[
810                // bool
811                ("bool", Bool),
812                ("true", Bool),
813                ("false", Bool),
814                //
815
816                // int and uint
817                ("uint", Uint(256)),
818                ("uint(1)", Uint(256)),
819                ("1", Uint(8)),
820                ("0x01", Uint(8)),
821                ("int", Int(256)),
822                ("int(1)", Int(256)),
823                ("int(-1)", Int(256)),
824                ("-1", Int(8)),
825                ("-0x01", Int(8)),
826                //
827
828                // address
829                ("address", Address),
830                ("address(0)", Address),
831                ("0x690B9A9E9aa1C9dB991C7721a92d351Db4FaC990", Address),
832                ("payable(0)", Address),
833                ("payable(address(0))", Address),
834                //
835
836                // string
837                ("string", String),
838                ("string(\"hello world\")", String),
839                ("\"hello world\"", String),
840                ("unicode\"hello world 😀\"", String),
841                //
842
843                // bytes
844                ("bytes", Bytes),
845                ("bytes(\"hello world\")", Bytes),
846                ("bytes(unicode\"hello world 😀\")", Bytes),
847                ("hex\"68656c6c6f20776f726c64\"", Bytes),
848                //
849            ]
850        };
851
852        let mut types: Vec<(String, DynSolType)> = Vec::with_capacity(96 + 32 + 100);
853        for (n, b) in (8..=256).step_by(8).zip(1..=32) {
854            types.push((format!("uint{n}(0)"), DynSolType::Uint(n)));
855            types.push((format!("int{n}(0)"), DynSolType::Int(n)));
856            types.push((format!("bytes{b}(0x00)"), DynSolType::FixedBytes(b)));
857        }
858
859        for n in 1..=32 {
860            types.push((
861                format!("uint256[{n}]"),
862                DynSolType::FixedArray(Box::new(DynSolType::Uint(256)), n),
863            ));
864        }
865
866        generic_type_test(&mut source(), TYPES);
867        generic_type_test(&mut source(), &types);
868    }
869
870    #[test]
871    fn test_global_vars() {
872        init_tracing();
873
874        // https://docs.soliditylang.org/en/latest/cheatsheet.html#global-variables
875        let global_variables = {
876            use DynSolType::*;
877            &[
878                // abi
879                ("abi.decode(bytes(\"\"), (uint8[13]))", FixedArray(Box::new(Uint(8)), 13)),
880                ("abi.decode(bytes(\"\"), (address, bytes))", Tuple(vec![Address, Bytes])),
881                ("abi.decode(bytes(\"\"), (uint112, uint48))", Tuple(vec![Uint(112), Uint(48)])),
882                ("abi.encode(1, 2)", Bytes),
883                ("abi.encodePacked(uint256(1), uint256(2))", Bytes),
884                ("abi.encodeWithSelector(bytes4(0), 1, 2)", Bytes),
885                ("abi.encodeWithSignature(\"f(uint256)\", 1)", Bytes),
886                //
887
888                //
889                ("bytes.concat()", Bytes),
890                ("bytes.concat(bytes(\"\"))", Bytes),
891                ("bytes.concat(bytes(\"\"), bytes(\"\"))", Bytes),
892                ("string.concat()", String),
893                ("string.concat(\"\")", String),
894                ("string.concat(\"\", \"\")", String),
895                //
896
897                // block
898                ("block.basefee", Uint(256)),
899                ("block.chainid", Uint(256)),
900                ("block.coinbase", Address),
901                ("block.difficulty", Uint(256)),
902                ("block.gaslimit", Uint(256)),
903                ("block.number", Uint(256)),
904                ("block.timestamp", Uint(256)),
905                //
906
907                // tx
908                ("gasleft()", Uint(256)),
909                ("msg.data", Bytes),
910                ("msg.sender", Address),
911                ("msg.sig", FixedBytes(4)),
912                ("msg.value", Uint(256)),
913                ("tx.gasprice", Uint(256)),
914                ("tx.origin", Address),
915                //
916
917                // assertions
918                // assert(bool)
919                // require(bool)
920                // revert()
921                // revert(string)
922                //
923
924                //
925                ("blockhash(0)", FixedBytes(32)),
926                ("keccak256(bytes(\"\"))", FixedBytes(32)),
927                ("sha256(bytes(\"\"))", FixedBytes(32)),
928                ("ripemd160(bytes(\"\"))", FixedBytes(20)),
929                ("ecrecover(bytes32(0), 0, bytes32(0), bytes32(0))", Address),
930                ("addmod(1, 2, 3)", Uint(256)),
931                ("mulmod(1, 2, 3)", Uint(256)),
932                //
933
934                // address
935                ("address(0)", Address),
936                ("address(this)", Address),
937                // ("super", Type::Custom("super".to_string))
938                // (selfdestruct(address payable), None)
939                ("address(0).balance", Uint(256)),
940                ("address(0).code", Bytes),
941                ("address(0).codehash", FixedBytes(32)),
942                ("payable(address(0)).send(1)", Bool),
943                // (address.transfer(uint256), None)
944                //
945
946                // type
947                ("type(C).name", String),
948                ("type(C).creationCode", Bytes),
949                ("type(C).runtimeCode", Bytes),
950                ("type(I).interfaceId", FixedBytes(4)),
951                ("type(uint256).min", Uint(256)),
952                ("type(int128).min", Int(128)),
953                ("type(int256).min", Int(256)),
954                ("type(uint256).max", Uint(256)),
955                ("type(int128).max", Int(128)),
956                ("type(int256).max", Int(256)),
957                ("type(Enum1).min", Uint(8)),
958                ("type(Enum1).max", Uint(8)),
959                // function
960                ("this.run.address", Address),
961                ("this.run.selector", FixedBytes(4)),
962            ]
963        };
964
965        generic_type_test(&mut source(), global_variables);
966    }
967
968    #[track_caller]
969    fn source() -> TestSessionSource {
970        // synchronize solc install
971        static PRE_INSTALL_SOLC_LOCK: Mutex<bool> = Mutex::new(false);
972
973        // on some CI targets installing results in weird malformed solc files, we try installing it
974        // multiple times
975        let version = "0.8.20";
976        for _ in 0..3 {
977            let mut is_preinstalled = PRE_INSTALL_SOLC_LOCK.lock().unwrap();
978            if !*is_preinstalled {
979                let solc = Solc::find_or_install(&version.parse().unwrap())
980                    .map(|solc| (solc.version.clone(), solc));
981                match solc {
982                    Ok((v, solc)) => {
983                        // successfully installed
984                        let _ = sh_println!("found installed Solc v{v} @ {}", solc.solc.display());
985                        break;
986                    }
987                    Err(e) => {
988                        // try reinstalling
989                        let _ = sh_err!("error while trying to re-install Solc v{version}: {e}");
990                        let solc = Solc::blocking_install(&version.parse().unwrap());
991                        if solc.map_err(SolcError::from).is_ok() {
992                            *is_preinstalled = true;
993                            break;
994                        }
995                    }
996                }
997            }
998        }
999
1000        SessionSource::new(Default::default()).unwrap()
1001    }
1002
1003    fn array(ty: DynSolType) -> DynSolType {
1004        DynSolType::Array(Box::new(ty))
1005    }
1006
1007    fn fixed_array(ty: DynSolType, len: usize) -> DynSolType {
1008        DynSolType::FixedArray(Box::new(ty), len)
1009    }
1010
1011    /// Lowers the given snippet appended to the REPL contract via solar's HIR pipeline (without
1012    /// invoking solc) and returns the resulting `DynSolType` of the last expression statement in
1013    /// the run() body.
1014    ///
1015    /// Tests bypass `SessionSource::build` (which routes through foundry-compilers + solc) so the
1016    /// Solar type table can be exercised directly without compiling each snippet through solc.
1017    fn get_type_ethabi(s: &mut TestSessionSource, input: &str, clear: bool) -> Option<DynSolType> {
1018        if clear {
1019            s.clear();
1020        }
1021
1022        // Always declare sample types so `type(...)` tests have concrete definitions.
1023        *s = s.clone_with_new_line("enum Enum1 { A }".into()).unwrap().0;
1024        *s = s.clone_with_new_line("contract C {}".into()).unwrap().0;
1025        *s = s.clone_with_new_line("interface I {}".into()).unwrap().0;
1026
1027        let input = format!("{};", input.trim_end().trim_end_matches(';'));
1028        let (new_source, _) = s.clone_with_new_line(input).unwrap();
1029        *s = new_source.clone();
1030
1031        let src = new_source.to_repl_source();
1032        let opts = solar::interface::config::CompileOpts::default();
1033        let sess = solar::interface::Session::builder()
1034            .opts(opts)
1035            .with_buffer_emitter(Default::default())
1036            .build();
1037        let mut compiler = Compiler::new(sess);
1038
1039        compiler.enter_mut(|c| -> Option<DynSolType> {
1040            // Stage 1: parse, lower, and analyze (mutable access required).
1041            let analyzed = {
1042                let mut pcx = c.parse();
1043                let file = c
1044                    .sess()
1045                    .source_map()
1046                    .new_source_file(
1047                        std::path::PathBuf::from(new_source.file_name.clone()),
1048                        src.clone(),
1049                    )
1050                    .ok()?;
1051                pcx.add_file(file);
1052                pcx.parse();
1053                matches!(c.lower_asts(), Ok(ControlFlow::Continue(())))
1054                    && matches!(c.analysis(), Ok(ControlFlow::Continue(())))
1055            };
1056            if !analyzed {
1057                return None;
1058            }
1059
1060            // Stage 2: walk HIR (immutable access).
1061            let gcx = c.gcx();
1062            let hir = &gcx.hir;
1063            let repl = hir.contracts().find(|c| c.name.as_str() == "REPL")?;
1064            let run_fid = repl
1065                .functions()
1066                .find(|&f| hir.function(f).name.as_ref().map(|n| n.as_str()) == Some("run"))?;
1067            let body = hir.function(run_fid).body?;
1068            let last = body.last()?;
1069            let expr = match last.kind {
1070                StmtKind::Expr(e) => e,
1071                _ => return None,
1072            };
1073            expr_to_dyn(gcx, expr)
1074        })
1075    }
1076
1077    fn generic_type_test<'a, T, I>(s: &mut TestSessionSource, input: I)
1078    where
1079        T: AsRef<str> + std::fmt::Display + 'a,
1080        I: IntoIterator<Item = &'a (T, DynSolType)> + 'a,
1081    {
1082        let mut failures = Vec::new();
1083        for (input, expected) in input {
1084            let input = input.as_ref();
1085            let ty = get_type_ethabi(s, input, true);
1086            if ty.as_ref() != Some(expected) {
1087                failures.push(format!("{input}: got {ty:?}, expected {expected:?}"));
1088            }
1089        }
1090        assert!(failures.is_empty(), "\n{}", failures.join("\n"));
1091    }
1092
1093    fn init_tracing() {
1094        let _ = tracing_subscriber::FmtSubscriber::builder()
1095            .with_env_filter(tracing_subscriber::EnvFilter::from_default_env())
1096            .try_init();
1097    }
1098}