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