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 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#[derive(Debug)]
36pub struct InspectResult {
37 pub control_flow: ControlFlow<()>,
39 pub formatted_output: Option<String>,
41 pub last_result: Option<String>,
43 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
95impl<FEN: FoundryEvmNetwork> SessionSource<FEN> {
97 pub async fn execute(&mut self) -> Result<ChiselResult> {
99 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 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 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 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 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 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 let generated_output = source.build()?;
234
235 let res_ty = generated_output.enter(|out| -> Option<(bool, DynSolType)> {
238 let gcx = out.gcx();
239
240 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 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
356fn 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
461fn 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
512fn 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#[inline]
519fn should_continue(expr: &Expr<'_>) -> bool {
520 match &expr.kind {
521 ExprKind::Assign(_, _, _) => true,
523 ExprKind::Delete(_) => true,
525 ExprKind::Unary(op, _) => matches!(
527 op.kind,
528 UnOpKind::PreInc | UnOpKind::PreDec | UnOpKind::PostInc | UnOpKind::PostDec
529 ),
530 ExprKind::Call(callee, _, _) => match &callee.kind {
532 ExprKind::Member(_, ident) => ident.as_str() == "pop",
533 _ => false,
534 },
535 _ => false,
536 }
537}
538
539const 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 ElementaryType::Fixed(_, _) | ElementaryType::UFixed(_, _) => return None,
551 })
552}
553
554fn solar_expr_ty_to_dyn<'gcx>(gcx: Gcx<'gcx>, ty: Ty<'gcx>, expr: &Expr<'_>) -> Option<DynSolType> {
556 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 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 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 ("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 ("-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 ("true ? 1 : 0", Uint(8)),
714 ("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(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 ("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 ("true && true", Bool),
761 ("true || true", Bool),
762 ("true == true", Bool),
763 ("true != true", Bool),
764 ("!true", Bool),
765 ]
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", Bool),
789 ("true", Bool),
790 ("false", Bool),
791 ("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 ("address", Address),
807 ("address(0)", Address),
808 ("0x690B9A9E9aa1C9dB991C7721a92d351Db4FaC990", Address),
809 ("payable(0)", Address),
810 ("payable(address(0))", Address),
811 ("string", String),
815 ("string(\"hello world\")", String),
816 ("\"hello world\"", String),
817 ("unicode\"hello world 😀\"", String),
818 ("bytes", Bytes),
822 ("bytes(\"hello world\")", Bytes),
823 ("bytes(unicode\"hello world 😀\")", Bytes),
824 ("hex\"68656c6c6f20776f726c64\"", Bytes),
825 ]
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 let global_variables = {
853 use DynSolType::*;
854 &[
855 ("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 ("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 ("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 ("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 ("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 ("address(0)", Address),
913 ("address(this)", Address),
914 ("address(0).balance", Uint(256)),
917 ("address(0).code", Bytes),
918 ("address(0).codehash", FixedBytes(32)),
919 ("payable(address(0)).send(1)", Bool),
920 ("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 ("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 static PRE_INSTALL_SOLC_LOCK: Mutex<bool> = Mutex::new(false);
949
950 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 let _ = sh_println!("found installed Solc v{v} @ {}", solc.solc.display());
962 break;
963 }
964 Err(e) => {
965 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 fn get_type_ethabi(s: &mut TestSessionSource, input: &str, clear: bool) -> Option<DynSolType> {
995 if clear {
996 s.clear();
997 }
998
999 *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 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 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}