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