1use 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#[derive(Debug)]
35pub struct InspectResult {
36 pub control_flow: ControlFlow<()>,
38 pub formatted_output: Option<String>,
40 pub last_result: Option<String>,
42 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
94impl<FEN: FoundryEvmNetwork> SessionSource<FEN> {
96 pub async fn execute(&mut self) -> Result<ChiselResult> {
98 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 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 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 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 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 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 let generated_output = source.build()?;
233
234 let res_ty = generated_output.enter(|out| -> Option<(bool, DynSolType)> {
237 let gcx = out.gcx();
238
239 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 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
357fn 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
462fn 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
513fn 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#[inline]
520fn should_continue(expr: &Expr<'_>) -> bool {
521 match &expr.kind {
522 ExprKind::Assign(_, _, _) => true,
524 ExprKind::Delete(_) => true,
526 ExprKind::Unary(op, _) => matches!(
528 op.kind,
529 UnOpKind::PreInc | UnOpKind::PreDec | UnOpKind::PostInc | UnOpKind::PostDec
530 ),
531 ExprKind::Call(callee, _, _) => match &callee.kind {
533 ExprKind::Member(_, ident) => ident.as_str() == "pop",
534 _ => false,
535 },
536 _ => false,
537 }
538}
539
540const 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 ElementaryType::Fixed(_, _) | ElementaryType::UFixed(_, _) => return None,
552 })
553}
554
555fn solar_expr_ty_to_dyn<'gcx>(gcx: Gcx<'gcx>, ty: Ty<'gcx>, expr: &Expr<'_>) -> Option<DynSolType> {
557 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 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 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 ("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 ("-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 ("true ? 1 : 0", Uint(8)),
737 ("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(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 ("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 ("true && true", Bool),
784 ("true || true", Bool),
785 ("true == true", Bool),
786 ("true != true", Bool),
787 ("!true", Bool),
788 ]
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", Bool),
812 ("true", Bool),
813 ("false", Bool),
814 ("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 ("address", Address),
830 ("address(0)", Address),
831 ("0x690B9A9E9aa1C9dB991C7721a92d351Db4FaC990", Address),
832 ("payable(0)", Address),
833 ("payable(address(0))", Address),
834 ("string", String),
838 ("string(\"hello world\")", String),
839 ("\"hello world\"", String),
840 ("unicode\"hello world 😀\"", String),
841 ("bytes", Bytes),
845 ("bytes(\"hello world\")", Bytes),
846 ("bytes(unicode\"hello world 😀\")", Bytes),
847 ("hex\"68656c6c6f20776f726c64\"", Bytes),
848 ]
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 let global_variables = {
876 use DynSolType::*;
877 &[
878 ("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 ("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 ("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 ("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 ("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 ("address(0)", Address),
936 ("address(this)", Address),
937 ("address(0).balance", Uint(256)),
940 ("address(0).code", Bytes),
941 ("address(0).codehash", FixedBytes(32)),
942 ("payable(address(0)).send(1)", Bool),
943 ("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 ("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 static PRE_INSTALL_SOLC_LOCK: Mutex<bool> = Mutex::new(false);
972
973 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 let _ = sh_println!("found installed Solc v{v} @ {}", solc.solc.display());
985 break;
986 }
987 Err(e) => {
988 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 fn get_type_ethabi(s: &mut TestSessionSource, input: &str, clear: bool) -> Option<DynSolType> {
1018 if clear {
1019 s.clear();
1020 }
1021
1022 *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 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 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}