From 88d32ecfecdce38604014f1e05fc589b46db1af0 Mon Sep 17 00:00:00 2001 From: 0xrusowsky <0xrusowsky@proton.me> Date: Wed, 18 Jun 2025 15:42:17 +0200 Subject: [PATCH 01/31] wip: try-catch --- crates/fmt-2/src/state.rs | 52 +++++++++++++++++++++++++++++++++------ 1 file changed, 44 insertions(+), 8 deletions(-) diff --git a/crates/fmt-2/src/state.rs b/crates/fmt-2/src/state.rs index b493c6f4db140..dd107749776a6 100644 --- a/crates/fmt-2/src/state.rs +++ b/crates/fmt-2/src/state.rs @@ -1289,18 +1289,54 @@ impl<'ast> State<'_, 'ast> { } ast::StmtKind::Revert(path, args) => self.print_emit_revert("revert", path, args), ast::StmtKind::Try(ast::StmtTry { expr, clauses }) => { - self.word("try "); - self.print_expr(expr); - for (pos, ast::TryCatchClause { name, args, block }) in clauses.iter().delimited() { - self.word(if pos.is_first { " returns" } else { " catch" }); - if let Some(name) = name { + self.cbox(0); + if let Some((first, other)) = clauses.split_first() { + // Handle 'try' clause + let ast::TryCatchClause { args, block, .. } = first; + self.ibox(0); + self.word("try "); + self.print_expr(expr); + self.nbsp(); + if !args.is_empty() { + self.word("returns "); + self.print_parameter_list(args); self.nbsp(); - self.print_ident(name); } - self.print_parameter_list(args); - self.nbsp(); self.print_block(block, span); + self.end(); + + // Handle 'catch' clauses + let mut should_indent = false; + for (pos, ast::TryCatchClause { name, args, block }) in other.iter().delimited() + { + // Add extra indent if all prev 'catch' stmts are empty + if pos.is_first && block.is_empty() { + should_indent = true; + } else if should_indent && !block.is_empty() { + should_indent = false; + } + if should_indent { + self.space(); + self.s.offset(self.ind); + } else { + self.nbsp(); + } + + self.word("catch "); + if !args.is_empty() { + if let Some(name) = name { + // self.ibox(0); + self.print_ident(name); + self.print_parameter_list(args); + self.nbsp(); + // self.end(); + } + } + + self.print_block(block, span); + } } + self.end(); } ast::StmtKind::UncheckedBlock(block) => { self.word("unchecked "); From aa1a0a5e31ff2a9b63402c47470ba76b18d80c17 Mon Sep 17 00:00:00 2001 From: 0xrusowsky <0xrusowsky@proton.me> Date: Wed, 18 Jun 2025 16:30:03 +0200 Subject: [PATCH 02/31] wip: try-catch --- crates/fmt-2/src/state.rs | 49 +++++++++++++++++++++++++-------------- 1 file changed, 31 insertions(+), 18 deletions(-) diff --git a/crates/fmt-2/src/state.rs b/crates/fmt-2/src/state.rs index dd107749776a6..d90b4df5d718b 100644 --- a/crates/fmt-2/src/state.rs +++ b/crates/fmt-2/src/state.rs @@ -3,7 +3,11 @@ use super::{ comments::Comments, pp::{self, Token}, }; -use crate::{iter::IterDelimited, pp::BreakToken, FormatterConfig, InlineConfig}; +use crate::{ + iter::{IterDelimited, IteratorPosition}, + pp::BreakToken, + FormatterConfig, InlineConfig, +}; use foundry_config::fmt as config; use itertools::{Either, Itertools}; use solar_parse::{ @@ -1306,33 +1310,18 @@ impl<'ast> State<'_, 'ast> { self.end(); // Handle 'catch' clauses - let mut should_indent = false; + let mut should_break = false; for (pos, ast::TryCatchClause { name, args, block }) in other.iter().delimited() { - // Add extra indent if all prev 'catch' stmts are empty - if pos.is_first && block.is_empty() { - should_indent = true; - } else if should_indent && !block.is_empty() { - should_indent = false; - } - if should_indent { - self.space(); - self.s.offset(self.ind); - } else { - self.nbsp(); - } - + self.handle_try_catch_indent(&mut should_break, block.is_empty(), pos); self.word("catch "); if !args.is_empty() { if let Some(name) = name { - // self.ibox(0); self.print_ident(name); self.print_parameter_list(args); self.nbsp(); - // self.end(); } } - self.print_block(block, span); } } @@ -1582,6 +1571,30 @@ impl<'ast> State<'_, 'ast> { self.print_ident(name); self.print_tuple(arguments, Self::print_yul_expr, get_span!()); } + + fn handle_try_catch_indent( + &mut self, + should_break: &mut bool, + empty_block: bool, + pos: IteratorPosition, + ) { + // Add extra indent if all prev 'catch' stmts are empty + if *should_break { + self.nbsp(); + } else { + if empty_block { + self.space(); + self.s.offset(self.ind); + } else { + if pos.is_first { + self.nbsp(); + } else { + self.space(); + } + *should_break = true; + } + } + } } fn stmt_needs_semi<'ast>(stmt: &'ast ast::StmtKind<'ast>) -> bool { From e945d914a2c82f53fcdc77a9089ce60b705cfb86 Mon Sep 17 00:00:00 2001 From: 0xrusowsky <0xrusowsky@proton.me> Date: Wed, 18 Jun 2025 17:07:42 +0200 Subject: [PATCH 03/31] feat: print compact tuple --- crates/fmt-2/src/state.rs | 75 ++++++++++++++++++++++++--------------- 1 file changed, 47 insertions(+), 28 deletions(-) diff --git a/crates/fmt-2/src/state.rs b/crates/fmt-2/src/state.rs index d90b4df5d718b..3639f70bdb86a 100644 --- a/crates/fmt-2/src/state.rs +++ b/crates/fmt-2/src/state.rs @@ -213,13 +213,13 @@ impl<'sess> State<'sess, '_> { } } - fn print_tuple<'a, T, P, S>(&mut self, values: &'a [T], print: P, get_span: S) + fn print_tuple<'a, T, P, S>(&mut self, values: &'a [T], print: P, get_span: S, compact: bool) where P: FnMut(&mut Self, &'a T), S: FnMut(&T) -> Option, { self.word("("); - self.commasep(values, print, get_span); + self.commasep(values, print, get_span, compact); self.word(")"); } @@ -229,12 +229,17 @@ impl<'sess> State<'sess, '_> { S: FnMut(&T) -> Option, { self.word("["); - self.commasep(values, print, get_span); + self.commasep(values, print, get_span, false); self.word("]"); } - fn commasep<'a, T, P, S>(&mut self, values: &'a [T], mut print: P, mut get_span: S) - where + fn commasep<'a, T, P, S>( + &mut self, + values: &'a [T], + mut print: P, + mut get_span: S, + compact: bool, + ) where P: FnMut(&mut Self, &'a T), S: FnMut(&T) -> Option, { @@ -242,8 +247,14 @@ impl<'sess> State<'sess, '_> { return; } - self.s.cbox(self.ind); - self.zerobreak(); + if compact { + self.s.cbox(self.ind); + self.zerobreak(); + self.s.cbox(0); + } else { + self.s.cbox(self.ind); + self.zerobreak(); + } for (i, value) in values.iter().enumerate() { let span = get_span(value); if let Some(span) = span { @@ -259,13 +270,15 @@ impl<'sess> State<'sess, '_> { self.print_trailing_comment(span, next_pos); } if !self.is_beginning_of_line() { - if is_last { - self.zerobreak(); - } else { + if !is_last { self.space(); } } } + if compact { + self.end(); + } + self.zerobreak(); self.s.offset(-self.ind); self.end(); } @@ -572,7 +585,7 @@ impl<'ast> State<'_, 'ast> { self.nbsp(); self.print_ident(&name); } - self.print_parameter_list(parameters); + self.print_parameter_list(parameters, false); self.end(); // Attributes. @@ -600,7 +613,7 @@ impl<'ast> State<'_, 'ast> { if !returns.is_empty() { self.space(); self.word("returns "); - self.print_parameter_list(returns); + self.print_parameter_list(returns, false); } if let Some(body) = body { @@ -643,7 +656,7 @@ impl<'ast> State<'_, 'ast> { let ast::ItemError { name, parameters } = err; self.word("error "); self.print_ident(name); - self.print_parameter_list(parameters); + self.print_parameter_list(parameters, false); self.word(";"); } @@ -651,7 +664,7 @@ impl<'ast> State<'_, 'ast> { let ast::ItemEvent { name, parameters, anonymous } = event; self.word("event "); self.print_ident(name); - self.print_parameter_list(parameters); + self.print_parameter_list(parameters, false); if *anonymous { self.word(" anonymous"); } @@ -712,8 +725,12 @@ impl<'ast> State<'_, 'ast> { } } - fn print_parameter_list(&mut self, parameters: &'ast [ast::VariableDefinition<'ast>]) { - self.print_tuple(parameters, Self::print_var, get_span!()); + fn print_parameter_list( + &mut self, + parameters: &'ast [ast::VariableDefinition<'ast>], + compact: bool, + ) { + self.print_tuple(parameters, Self::print_var, get_span!(), compact); } fn print_docs(&mut self, docs: &'ast ast::DocComments<'ast>) { @@ -1000,7 +1017,7 @@ impl<'ast> State<'_, 'ast> { if self.config.override_spacing { self.nbsp(); } - self.print_tuple(paths, |this, path| this.print_path(path), get_span!(())); + self.print_tuple(paths, |this, path| this.print_path(path), get_span!(()), false); } } @@ -1114,10 +1131,11 @@ impl<'ast> State<'_, 'ast> { } }, |e| e.as_deref().map(|e| e.span), + false, ), ast::ExprKind::TypeCall(ty) => { self.word("type"); - self.print_tuple(std::slice::from_ref(ty), Self::print_ty, get_span!()); + self.print_tuple(std::slice::from_ref(ty), Self::print_ty, get_span!(), false); } ast::ExprKind::Type(ty) => self.print_ty(ty), ast::ExprKind::Unary(un_op, expr) => { @@ -1156,7 +1174,7 @@ impl<'ast> State<'_, 'ast> { match kind { ast::CallArgsKind::Unnamed(exprs) => { - self.print_tuple(exprs, |this, e| this.print_expr(e), get_span!()); + self.print_tuple(exprs, |this, e| this.print_expr(e), get_span!(), false); } ast::CallArgsKind::Named(named_args) => { self.word("("); @@ -1202,7 +1220,7 @@ impl<'ast> State<'_, 'ast> { self.nbsp(); } if !flags.is_empty() { - self.print_tuple(flags, Self::print_ast_str_lit, get_span!()); + self.print_tuple(flags, Self::print_ast_str_lit, get_span!(), false); } self.print_yul_block(block, span, false); } @@ -1216,6 +1234,7 @@ impl<'ast> State<'_, 'ast> { } }, |v| v.as_ref().map(|v| v.span), + false, ); self.word(" = "); self.neverbreak(); @@ -1303,7 +1322,7 @@ impl<'ast> State<'_, 'ast> { self.nbsp(); if !args.is_empty() { self.word("returns "); - self.print_parameter_list(args); + self.print_parameter_list(args, true); self.nbsp(); } self.print_block(block, span); @@ -1318,7 +1337,7 @@ impl<'ast> State<'_, 'ast> { if !args.is_empty() { if let Some(name) = name { self.print_ident(name); - self.print_parameter_list(args); + self.print_parameter_list(args, true); self.nbsp(); } } @@ -1356,7 +1375,7 @@ impl<'ast> State<'_, 'ast> { fn print_if_cond(&mut self, kw: &'static str, cond: &'ast ast::Expr<'ast>) { self.word_nbsp(kw); - self.print_tuple(std::slice::from_ref(cond), Self::print_expr, get_span!()); + self.print_tuple(std::slice::from_ref(cond), Self::print_expr, get_span!(), false); } fn print_emit_revert( @@ -1463,7 +1482,7 @@ impl<'ast> State<'_, 'ast> { self.print_yul_expr(expr); } yul::StmtKind::AssignMulti(paths, expr_call) => { - self.commasep(paths, |this, path| this.print_path(path), get_span!(())); + self.commasep(paths, |this, path| this.print_path(path), get_span!(()), false); self.word(" := "); self.neverbreak(); self.print_yul_expr_call(expr_call); @@ -1523,11 +1542,11 @@ impl<'ast> State<'_, 'ast> { self.ibox(0); self.word("function "); self.print_ident(name); - self.print_tuple(parameters, Self::print_ident, get_span!()); + self.print_tuple(parameters, Self::print_ident, get_span!(), false); self.nbsp(); if !returns.is_empty() { self.word("-> "); - self.commasep(returns, Self::print_ident, get_span!()); + self.commasep(returns, Self::print_ident, get_span!(), false); self.nbsp(); } self.end(); @@ -1537,7 +1556,7 @@ impl<'ast> State<'_, 'ast> { yul::StmtKind::VarDecl(idents, expr) => { self.ibox(0); self.word("let "); - self.commasep(idents, Self::print_ident, get_span!()); + self.commasep(idents, Self::print_ident, get_span!(), false); if let Some(expr) = expr { self.word(" := "); self.neverbreak(); @@ -1569,7 +1588,7 @@ impl<'ast> State<'_, 'ast> { fn print_yul_expr_call(&mut self, expr: &'ast yul::ExprCall<'ast>) { let yul::ExprCall { name, arguments } = expr; self.print_ident(name); - self.print_tuple(arguments, Self::print_yul_expr, get_span!()); + self.print_tuple(arguments, Self::print_yul_expr, get_span!(), false); } fn handle_try_catch_indent( From 01894906e59a9901c18b2d2dba9a33b7cb120f2b Mon Sep 17 00:00:00 2001 From: 0xrusowsky <0xrusowsky@proton.me> Date: Wed, 18 Jun 2025 18:41:40 +0200 Subject: [PATCH 04/31] wip: inline comments --- crates/fmt-2/src/state.rs | 34 +++++++++++++++++++++++++++------- 1 file changed, 27 insertions(+), 7 deletions(-) diff --git a/crates/fmt-2/src/state.rs b/crates/fmt-2/src/state.rs index 3639f70bdb86a..ec9ff5dc08a31 100644 --- a/crates/fmt-2/src/state.rs +++ b/crates/fmt-2/src/state.rs @@ -1319,6 +1319,7 @@ impl<'ast> State<'_, 'ast> { self.ibox(0); self.word("try "); self.print_expr(expr); + self.print_trailing_comment(expr.span, first.args.first().map(|p| p.span.lo())); self.nbsp(); if !args.is_empty() { self.word("returns "); @@ -1336,9 +1337,13 @@ impl<'ast> State<'_, 'ast> { self.word("catch "); if !args.is_empty() { if let Some(name) = name { + self.ibox(0); self.print_ident(name); - self.print_parameter_list(args, true); - self.nbsp(); + } + self.print_parameter_list(args, true); + self.nbsp(); + if name.is_some() { + self.end(); } } self.print_block(block, span); @@ -1390,7 +1395,7 @@ impl<'ast> State<'_, 'ast> { } fn print_block(&mut self, block: &'ast [ast::Stmt<'ast>], span: Span) { - self.print_block_inner(block, Self::print_stmt, span, false, false); + self.print_block_inner(block, Self::print_stmt, |b| b.span, span, false, false); } // Body of a if/loop. @@ -1400,7 +1405,14 @@ impl<'ast> State<'_, 'ast> { } else { std::slice::from_ref(stmt) }; - self.print_block_inner(stmts, Self::print_stmt, stmt.span, attempt_single_line, true) + self.print_block_inner( + stmts, + Self::print_stmt, + |b| b.span, + stmt.span, + attempt_single_line, + true, + ) } fn print_yul_block( @@ -1409,13 +1421,21 @@ impl<'ast> State<'_, 'ast> { span: Span, attempt_single_line: bool, ) { - self.print_block_inner(block, Self::print_yul_stmt, span, attempt_single_line, false); + self.print_block_inner( + block, + Self::print_yul_stmt, + |b| b.span, + span, + attempt_single_line, + false, + ); } fn print_block_inner( &mut self, block: &'ast [T], mut print: impl FnMut(&mut Self, &'ast T), + mut get_block_span: impl FnMut(&'ast T) -> Span, span: Span, attempt_single_line: bool, attempt_omit_braces: bool, @@ -1430,7 +1450,7 @@ impl<'ast> State<'_, 'ast> { self.space(); } print(self, &block[0]); - self.print_comments(span.hi()); + self.print_comments_skip_ws(get_block_span(&block[0]).hi()); if attempt_omit_braces { self.s.scan_break(BreakToken { post_break: Some('}'), ..Default::default() }); self.s.offset(-self.ind); @@ -1448,7 +1468,7 @@ impl<'ast> State<'_, 'ast> { print(self, stmt); self.hardbreak_if_not_bol(); } - self.print_comments_skip_ws(span.hi()); + self.print_comments_skip_ws(block.last().map_or(span.hi(), |b| get_block_span(b).hi())); self.s.offset(-self.ind); self.end(); self.word("}"); From 3bde150291f311b560c570ec6946c46cf6bed478 Mon Sep 17 00:00:00 2001 From: 0xrusowsky <0xrusowsky@proton.me> Date: Thu, 19 Jun 2025 09:39:35 +0200 Subject: [PATCH 05/31] wip: try-cactch --- crates/fmt-2/src/state.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/crates/fmt-2/src/state.rs b/crates/fmt-2/src/state.rs index ec9ff5dc08a31..6d6479044c65f 100644 --- a/crates/fmt-2/src/state.rs +++ b/crates/fmt-2/src/state.rs @@ -1320,7 +1320,9 @@ impl<'ast> State<'_, 'ast> { self.word("try "); self.print_expr(expr); self.print_trailing_comment(expr.span, first.args.first().map(|p| p.span.lo())); - self.nbsp(); + if !self.is_beginning_of_line() { + self.nbsp(); + } if !args.is_empty() { self.word("returns "); self.print_parameter_list(args, true); @@ -1337,7 +1339,7 @@ impl<'ast> State<'_, 'ast> { self.word("catch "); if !args.is_empty() { if let Some(name) = name { - self.ibox(0); + self.cbox(0); self.print_ident(name); } self.print_parameter_list(args, true); From bc59dc56f2eae0b8557a1de252950652ccdc0a22 Mon Sep 17 00:00:00 2001 From: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> Date: Sun, 22 Jun 2025 08:18:21 +0200 Subject: [PATCH 06/31] bump solar to have try-catch spans (#10832) --- .config/nextest.toml | 2 +- .devcontainer/Dockerfile.dev | 84 + .devcontainer/devcontainer.json | 49 + .gitattributes | 1 + .github/workflows/nextest.yml | 5 +- .github/workflows/nix.yml | 44 + .github/workflows/release.yml | 6 +- .github/workflows/test.yml | 2 +- .gitignore | 2 + CHANGELOG.md | 37 - CONTRIBUTING.md | 29 +- Cargo.lock | 3431 +++++++++-------- Cargo.toml | 167 +- FUNDING.json | 5 +- Makefile | 73 +- README.md | 39 +- clippy.toml | 2 +- crates/anvil/Cargo.toml | 36 +- crates/anvil/bin/main.rs | 3 +- crates/anvil/core/Cargo.toml | 2 +- crates/anvil/core/src/eth/block.rs | 13 +- crates/anvil/core/src/eth/mod.rs | 44 +- crates/anvil/core/src/eth/proof.rs | 24 - crates/anvil/core/src/eth/subscription.rs | 4 +- crates/anvil/core/src/eth/transaction/mod.rs | 255 +- .../core/src/eth/transaction/optimism.rs | 195 - crates/anvil/core/src/eth/trie.rs | 30 - crates/anvil/core/src/types.rs | 28 +- crates/anvil/server/src/ws.rs | 2 +- crates/anvil/src/cmd.rs | 16 +- crates/anvil/src/config.rs | 123 +- crates/anvil/src/eth/api.rs | 475 ++- crates/anvil/src/eth/backend/db.rs | 52 +- crates/anvil/src/eth/backend/env.rs | 30 + crates/anvil/src/eth/backend/executor.rs | 201 +- crates/anvil/src/eth/backend/fork.rs | 2 +- crates/anvil/src/eth/backend/genesis.rs | 6 +- crates/anvil/src/eth/backend/mem/fork_db.rs | 25 +- .../anvil/src/eth/backend/mem/in_memory_db.rs | 25 +- crates/anvil/src/eth/backend/mem/inspector.rs | 104 +- crates/anvil/src/eth/backend/mem/mod.rs | 777 ++-- crates/anvil/src/eth/backend/mem/state.rs | 79 +- crates/anvil/src/eth/backend/mem/storage.rs | 56 +- crates/anvil/src/eth/backend/mod.rs | 1 + crates/anvil/src/eth/backend/validate.rs | 11 +- crates/anvil/src/eth/error.rs | 83 +- crates/anvil/src/eth/fees.rs | 64 +- crates/anvil/src/eth/otterscan/api.rs | 4 +- crates/anvil/src/eth/pool/mod.rs | 4 +- crates/anvil/src/eth/pool/transactions.rs | 7 +- crates/anvil/src/eth/sign.rs | 38 +- crates/anvil/src/eth/util.rs | 4 +- crates/anvil/src/evm.rs | 315 +- crates/anvil/src/hardfork.rs | 278 +- crates/anvil/src/lib.rs | 13 +- crates/anvil/src/pubsub.rs | 10 + crates/anvil/src/server/handler.rs | 41 +- .../test-data/state-dump-legacy-stress.json | 2 +- crates/anvil/test-data/state-dump-legacy.json | 2 +- crates/anvil/test-data/state-dump.json | 2 +- crates/anvil/tests/it/anvil.rs | 3 +- crates/anvil/tests/it/anvil_api.rs | 30 +- crates/anvil/tests/it/api.rs | 22 +- crates/anvil/tests/it/eip4844.rs | 7 +- crates/anvil/tests/it/eip7702.rs | 81 +- crates/anvil/tests/it/fork.rs | 95 +- crates/anvil/tests/it/gas.rs | 45 +- crates/anvil/tests/it/optimism.rs | 33 +- crates/anvil/tests/it/otterscan.rs | 13 +- crates/anvil/tests/it/pubsub.rs | 4 +- crates/anvil/tests/it/state.rs | 41 +- crates/anvil/tests/it/traces.rs | 17 +- crates/anvil/tests/it/transaction.rs | 139 +- crates/anvil/tests/it/txpool.rs | 4 +- crates/cast/Cargo.toml | 27 +- crates/cast/bin/main.rs | 3 +- crates/cast/src/args.rs | 45 +- crates/cast/src/base.rs | 13 +- crates/cast/src/cmd/access_list.rs | 2 +- crates/cast/src/cmd/artifact.rs | 10 +- crates/cast/src/cmd/call.rs | 70 +- crates/cast/src/cmd/constructor_args.rs | 7 +- crates/cast/src/cmd/create2.rs | 2 +- crates/cast/src/cmd/creation_code.rs | 13 +- crates/cast/src/cmd/da_estimate.rs | 48 + crates/cast/src/cmd/estimate.rs | 19 +- crates/cast/src/cmd/interface.rs | 3 +- crates/cast/src/cmd/logs.rs | 2 +- crates/cast/src/cmd/mktx.rs | 24 +- crates/cast/src/cmd/mod.rs | 1 + crates/cast/src/cmd/run.rs | 48 +- crates/cast/src/cmd/send.rs | 17 +- crates/cast/src/cmd/storage.rs | 5 +- crates/cast/src/cmd/wallet/mod.rs | 67 +- crates/cast/src/cmd/wallet/vanity.rs | 2 +- crates/cast/src/lib.rs | 105 +- crates/cast/src/opts.rs | 43 +- crates/cast/src/tx.rs | 14 +- crates/cast/tests/cli/main.rs | 279 +- crates/cheatcodes/Cargo.toml | 2 + crates/cheatcodes/assets/cheatcodes.json | 182 +- crates/cheatcodes/spec/src/vm.rs | 66 +- crates/cheatcodes/src/config.rs | 4 + crates/cheatcodes/src/crypto.rs | 8 +- crates/cheatcodes/src/error.rs | 3 +- crates/cheatcodes/src/evm.rs | 151 +- crates/cheatcodes/src/evm/fork.rs | 105 +- crates/cheatcodes/src/evm/mapping.rs | 16 +- crates/cheatcodes/src/evm/mock.rs | 28 +- crates/cheatcodes/src/evm/prank.rs | 14 +- .../cheatcodes/src/evm/record_debug_step.rs | 2 +- crates/cheatcodes/src/fs.rs | 17 +- crates/cheatcodes/src/inspector.rs | 1075 +++--- crates/cheatcodes/src/inspector/utils.rs | 50 +- crates/cheatcodes/src/lib.rs | 12 +- crates/cheatcodes/src/script.rs | 169 +- crates/cheatcodes/src/test.rs | 25 +- crates/cheatcodes/src/test/assert.rs | 7 +- crates/cheatcodes/src/test/assume.rs | 10 +- crates/cheatcodes/src/test/expect.rs | 42 +- crates/cheatcodes/src/test/revert_handlers.rs | 4 +- crates/cheatcodes/src/utils.rs | 171 +- crates/chisel/Cargo.toml | 7 +- crates/chisel/bin/main.rs | 3 +- crates/chisel/src/dispatcher.rs | 5 +- crates/chisel/src/lib.rs | 1 + crates/cli/Cargo.toml | 13 + crates/cli/src/opts/build/core.rs | 9 - crates/cli/src/opts/build/mod.rs | 3 + crates/cli/src/opts/build/utils.rs | 105 + crates/cli/src/opts/rpc.rs | 16 + crates/cli/src/utils/abi.rs | 10 +- crates/cli/src/utils/allocator.rs | 41 + crates/cli/src/utils/cmd.rs | 6 +- crates/cli/src/utils/mod.rs | 3 + crates/common/Cargo.toml | 2 - crates/common/fmt/Cargo.toml | 3 +- crates/common/fmt/src/eof.rs | 79 - crates/common/fmt/src/lib.rs | 5 +- crates/common/fmt/src/ui.rs | 2 +- crates/common/src/abi.rs | 18 +- crates/common/src/compile.rs | 1 - crates/common/src/constants.rs | 24 +- crates/common/src/contracts.rs | 27 +- crates/common/src/ens.rs | 254 -- crates/common/src/fs.rs | 2 +- crates/common/src/lib.rs | 1 - crates/common/src/preprocessor/data.rs | 1 + crates/common/src/preprocessor/deps.rs | 139 +- crates/common/src/provider/mod.rs | 4 +- .../common/src/provider/runtime_transport.rs | 13 +- crates/common/src/traits.rs | 15 +- crates/common/src/transactions.rs | 34 +- crates/common/src/version.rs | 1 - crates/config/Cargo.toml | 7 +- crates/config/README.md | 2 +- crates/config/src/etherscan.rs | 112 +- crates/config/src/invariant.rs | 4 +- crates/config/src/lib.rs | 181 +- crates/config/src/lint.rs | 108 + crates/config/src/utils.rs | 2 +- crates/config/src/vyper.rs | 12 +- crates/debugger/src/dump.rs | 2 +- crates/debugger/src/op.rs | 78 +- crates/debugger/src/tui/context.rs | 20 +- crates/debugger/src/tui/draw.rs | 11 +- crates/doc/src/parser/comment.rs | 6 +- crates/evm/core/Cargo.toml | 5 +- crates/evm/core/src/backend/cow.rs | 73 +- crates/evm/core/src/backend/error.rs | 3 +- crates/evm/core/src/backend/in_memory_db.rs | 6 +- crates/evm/core/src/backend/mod.rs | 203 +- crates/evm/core/src/backend/snapshot.rs | 10 +- crates/evm/core/src/buffer.rs | 9 +- crates/evm/core/src/decode.rs | 10 +- crates/evm/core/src/either_evm.rs | 276 ++ crates/evm/core/src/env.rs | 103 + crates/evm/core/src/evm.rs | 387 ++ crates/evm/core/src/fork/database.rs | 22 +- crates/evm/core/src/fork/init.rs | 61 +- crates/evm/core/src/fork/mod.rs | 4 +- crates/evm/core/src/fork/multi.rs | 18 +- crates/evm/core/src/ic.rs | 77 +- crates/evm/core/src/lib.rs | 17 +- crates/evm/core/src/opcodes.rs | 25 - crates/evm/core/src/opts.rs | 65 +- crates/evm/core/src/utils.rs | 270 +- crates/evm/coverage/src/anchors.rs | 4 +- crates/evm/coverage/src/inspector.rs | 39 +- crates/evm/evm/Cargo.toml | 1 + crates/evm/evm/src/executors/builder.rs | 17 +- crates/evm/evm/src/executors/fuzz/mod.rs | 2 +- crates/evm/evm/src/executors/invariant/mod.rs | 49 +- .../evm/evm/src/executors/invariant/replay.rs | 3 +- crates/evm/evm/src/executors/mod.rs | 125 +- crates/evm/evm/src/executors/trace.rs | 9 +- crates/evm/evm/src/inspectors/chisel_state.rs | 24 +- .../evm/evm/src/inspectors/custom_printer.rs | 114 + crates/evm/evm/src/inspectors/logs.rs | 39 +- crates/evm/evm/src/inspectors/mod.rs | 6 + .../evm/src/inspectors/revert_diagnostic.rs | 228 ++ crates/evm/evm/src/inspectors/script.rs | 28 +- crates/evm/evm/src/inspectors/stack.rs | 393 +- crates/evm/evm/src/lib.rs | 4 +- crates/evm/fuzz/src/inspector.rs | 30 +- crates/evm/fuzz/src/lib.rs | 2 +- crates/evm/fuzz/src/strategies/int.rs | 18 +- crates/evm/fuzz/src/strategies/invariants.rs | 2 +- crates/evm/fuzz/src/strategies/param.rs | 9 +- crates/evm/fuzz/src/strategies/state.rs | 12 +- crates/evm/fuzz/src/strategies/uint.rs | 16 +- crates/evm/traces/src/debug/mod.rs | 2 +- crates/evm/traces/src/debug/sources.rs | 2 +- crates/evm/traces/src/decoder/mod.rs | 66 +- crates/evm/traces/src/decoder/precompiles.rs | 30 +- crates/evm/traces/src/identifier/local.rs | 2 +- crates/evm/traces/src/lib.rs | 2 +- crates/fmt-2/src/pp/convenience.rs | 40 + crates/fmt-2/src/state.rs | 218 +- crates/fmt/src/buffer.rs | 2 +- crates/fmt/src/formatter.rs | 55 +- crates/fmt/src/solang_ext/ast_eq.rs | 29 +- crates/fmt/src/solang_ext/loc.rs | 12 + crates/fmt/src/solang_ext/mod.rs | 11 +- crates/fmt/src/visit.rs | 15 +- crates/fmt/testdata/NonKeywords/fmt.sol | 43 + crates/fmt/testdata/NonKeywords/original.sol | 43 + crates/fmt/testdata/TryStatement/original.sol | 8 +- .../fmt/testdata/VariableDefinition/fmt.sol | 4 +- .../override-spacing.fmt.sol | 4 +- crates/fmt/tests/formatter.rs | 11 +- crates/forge/Cargo.toml | 37 +- crates/forge/assets/workflowTemplate.yml | 3 - crates/forge/bin/main.rs | 3 +- crates/forge/src/args.rs | 1 + crates/forge/src/cmd/bind_json.rs | 205 +- crates/forge/src/cmd/build.rs | 58 +- crates/forge/src/cmd/clone.rs | 12 +- crates/forge/src/cmd/compiler.rs | 17 +- crates/forge/src/cmd/coverage.rs | 12 +- crates/forge/src/cmd/create.rs | 9 +- crates/forge/src/cmd/eip712.rs | 371 +- crates/forge/src/cmd/fmt.rs | 3 +- crates/forge/src/cmd/inspect.rs | 158 +- crates/forge/src/cmd/lint.rs | 120 + crates/forge/src/cmd/mod.rs | 1 + crates/forge/src/cmd/selectors.rs | 3 +- crates/forge/src/cmd/test/mod.rs | 9 + crates/forge/src/cmd/watch.rs | 34 +- crates/forge/src/lib.rs | 1 + crates/forge/src/multi_runner.rs | 11 +- crates/forge/src/opts.rs | 6 +- crates/forge/src/runner.rs | 126 +- crates/forge/tests/cli/cmd.rs | 121 +- crates/forge/tests/cli/compiler.rs | 16 +- crates/forge/tests/cli/config.rs | 35 +- crates/forge/tests/cli/eip712.rs | 709 +++- crates/forge/tests/cli/eof.rs | 66 - crates/forge/tests/cli/lint.rs | 392 ++ crates/forge/tests/cli/main.rs | 2 +- crates/forge/tests/cli/script.rs | 295 +- crates/forge/tests/cli/svm.rs | 2 +- crates/forge/tests/cli/test_cmd.rs | 299 +- crates/forge/tests/cli/test_optimizer.rs | 181 +- crates/forge/tests/cli/verify_bytecode.rs | 8 +- .../forge/tests/fixtures/colored_traces.svg | 14 +- crates/forge/tests/it/config.rs | 2 +- crates/forge/tests/it/fuzz.rs | 2 +- crates/forge/tests/it/invariant.rs | 39 +- crates/forge/tests/it/main.rs | 1 + crates/forge/tests/it/repros.rs | 14 +- crates/forge/tests/it/spec.rs | 2 +- crates/forge/tests/it/table.rs | 116 + crates/forge/tests/it/test_helpers.rs | 11 +- crates/forge/tests/ui.rs | 13 + crates/lint/Cargo.toml | 27 + crates/lint/README.md | 67 + crates/lint/src/lib.rs | 6 + crates/lint/src/linter.rs | 129 + crates/lint/src/sol/gas/keccak.rs | 35 + crates/lint/src/sol/gas/mod.rs | 9 + crates/lint/src/sol/high/incorrect_shift.rs | 42 + crates/lint/src/sol/high/mod.rs | 9 + crates/lint/src/sol/info/mixed_case.rs | 66 + crates/lint/src/sol/info/mod.rs | 20 + crates/lint/src/sol/info/pascal_case.rs | 32 + .../lint/src/sol/info/screaming_snake_case.rs | 51 + crates/lint/src/sol/macros.rs | 71 + crates/lint/src/sol/med/div_mul.rs | 38 + crates/lint/src/sol/med/mod.rs | 9 + crates/lint/src/sol/mod.rs | 217 ++ crates/lint/testdata/DivideBeforeMultiply.sol | 18 + .../lint/testdata/DivideBeforeMultiply.stderr | 48 + crates/lint/testdata/IncorrectShift.sol | 38 + crates/lint/testdata/IncorrectShift.stderr | 40 + crates/lint/testdata/Keccak256.sol | 24 + crates/lint/testdata/Keccak256.stderr | 24 + crates/lint/testdata/MixedCase.sol | 62 + crates/lint/testdata/MixedCase.stderr | 96 + crates/lint/testdata/ScreamingSnakeCase.sol | 23 + .../lint/testdata/ScreamingSnakeCase.stderr | 64 + crates/lint/testdata/StructPascalCase.sol | 36 + crates/lint/testdata/StructPascalCase.stderr | 48 + crates/macros/src/console_fmt.rs | 30 +- crates/script-sequence/src/lib.rs | 1 + crates/script-sequence/src/sequence.rs | 4 +- crates/script/Cargo.toml | 1 + crates/script/src/broadcast.rs | 9 +- crates/script/src/execute.rs | 15 +- crates/script/src/lib.rs | 33 +- crates/script/src/multi_sequence.rs | 6 +- crates/script/src/receipts.rs | 9 +- crates/script/src/simulate.rs | 6 +- crates/script/src/transaction.rs | 4 +- crates/sol-macro-gen/Cargo.toml | 2 + crates/sol-macro-gen/src/lib.rs | 1 + crates/sol-macro-gen/src/sol_macro_gen.rs | 15 +- crates/test-utils/Cargo.toml | 4 +- crates/test-utils/src/lib.rs | 3 + crates/test-utils/src/rpc.rs | 69 +- crates/test-utils/src/ui_runner.rs | 152 + crates/test-utils/src/util.rs | 10 +- crates/verify/Cargo.toml | 7 +- crates/verify/src/bytecode.rs | 86 +- crates/verify/src/etherscan/mod.rs | 159 +- crates/verify/src/utils.rs | 31 +- crates/verify/src/verify.rs | 28 +- crates/wallets/Cargo.toml | 2 +- crates/wallets/src/wallet.rs | 10 +- crates/wallets/src/wallet_signer.rs | 16 +- deny.toml | 18 +- docs/dev/cheatcodes.md | 2 +- docs/dev/lintrules.md | 78 + flake.lock | 105 +- flake.nix | 81 +- foundryup/README.md | 18 +- testdata/cheats/Vm.sol | 9 + .../default/cheats/AttachDelegation.t.sol | 73 +- .../cheats/BroadcastRawTransaction.t.sol | 2 +- testdata/default/cheats/ChainId.t.sol | 2 +- testdata/default/cheats/Etch.t.sol | 2 +- testdata/default/cheats/Fee.t.sol | 2 +- .../default/cheats/GetBlockTimestamp.t.sol | 2 +- testdata/default/cheats/GetChain.t.sol | 17 +- testdata/default/cheats/Prank.t.sol | 20 + testdata/default/cheats/Roll.t.sol | 2 +- testdata/default/cheats/Seed.t.sol | 92 + testdata/default/cheats/Shuffle.t.sol | 61 + testdata/default/cheats/Warp.t.sol | 2 +- testdata/default/cheats/getBlockNumber.t.sol | 2 +- testdata/default/repros/Issue10477.t.sol | 49 + testdata/default/repros/Issue10527.t.sol | 45 + testdata/default/repros/Issue10552.t.sol | 62 + testdata/default/repros/Issue10586.t.sol | 78 + 354 files changed, 15147 insertions(+), 7118 deletions(-) create mode 100644 .devcontainer/Dockerfile.dev create mode 100644 .devcontainer/devcontainer.json create mode 100644 .github/workflows/nix.yml delete mode 100644 CHANGELOG.md delete mode 100644 crates/anvil/core/src/eth/proof.rs delete mode 100644 crates/anvil/core/src/eth/transaction/optimism.rs delete mode 100644 crates/anvil/core/src/eth/trie.rs create mode 100644 crates/anvil/src/eth/backend/env.rs create mode 100644 crates/cast/src/cmd/da_estimate.rs create mode 100644 crates/cli/src/opts/build/utils.rs create mode 100644 crates/cli/src/utils/allocator.rs delete mode 100644 crates/common/fmt/src/eof.rs delete mode 100644 crates/common/src/ens.rs create mode 100644 crates/config/src/lint.rs create mode 100644 crates/evm/core/src/either_evm.rs create mode 100644 crates/evm/core/src/env.rs create mode 100644 crates/evm/core/src/evm.rs delete mode 100644 crates/evm/core/src/opcodes.rs create mode 100644 crates/evm/evm/src/inspectors/custom_printer.rs create mode 100644 crates/evm/evm/src/inspectors/revert_diagnostic.rs create mode 100644 crates/fmt/testdata/NonKeywords/fmt.sol create mode 100644 crates/fmt/testdata/NonKeywords/original.sol create mode 100644 crates/forge/src/cmd/lint.rs delete mode 100644 crates/forge/tests/cli/eof.rs create mode 100644 crates/forge/tests/cli/lint.rs create mode 100644 crates/forge/tests/it/table.rs create mode 100644 crates/forge/tests/ui.rs create mode 100644 crates/lint/Cargo.toml create mode 100644 crates/lint/README.md create mode 100644 crates/lint/src/lib.rs create mode 100644 crates/lint/src/linter.rs create mode 100644 crates/lint/src/sol/gas/keccak.rs create mode 100644 crates/lint/src/sol/gas/mod.rs create mode 100644 crates/lint/src/sol/high/incorrect_shift.rs create mode 100644 crates/lint/src/sol/high/mod.rs create mode 100644 crates/lint/src/sol/info/mixed_case.rs create mode 100644 crates/lint/src/sol/info/mod.rs create mode 100644 crates/lint/src/sol/info/pascal_case.rs create mode 100644 crates/lint/src/sol/info/screaming_snake_case.rs create mode 100644 crates/lint/src/sol/macros.rs create mode 100644 crates/lint/src/sol/med/div_mul.rs create mode 100644 crates/lint/src/sol/med/mod.rs create mode 100644 crates/lint/src/sol/mod.rs create mode 100644 crates/lint/testdata/DivideBeforeMultiply.sol create mode 100644 crates/lint/testdata/DivideBeforeMultiply.stderr create mode 100644 crates/lint/testdata/IncorrectShift.sol create mode 100644 crates/lint/testdata/IncorrectShift.stderr create mode 100644 crates/lint/testdata/Keccak256.sol create mode 100644 crates/lint/testdata/Keccak256.stderr create mode 100644 crates/lint/testdata/MixedCase.sol create mode 100644 crates/lint/testdata/MixedCase.stderr create mode 100644 crates/lint/testdata/ScreamingSnakeCase.sol create mode 100644 crates/lint/testdata/ScreamingSnakeCase.stderr create mode 100644 crates/lint/testdata/StructPascalCase.sol create mode 100644 crates/lint/testdata/StructPascalCase.stderr create mode 100644 crates/test-utils/src/ui_runner.rs create mode 100644 docs/dev/lintrules.md create mode 100644 testdata/default/cheats/Seed.t.sol create mode 100644 testdata/default/cheats/Shuffle.t.sol create mode 100644 testdata/default/repros/Issue10477.t.sol create mode 100644 testdata/default/repros/Issue10527.t.sol create mode 100644 testdata/default/repros/Issue10552.t.sol create mode 100644 testdata/default/repros/Issue10586.t.sol diff --git a/.config/nextest.toml b/.config/nextest.toml index 1de244ff846d7..86960c3abb1cd 100644 --- a/.config/nextest.toml +++ b/.config/nextest.toml @@ -2,7 +2,7 @@ chisel-serial = { max-threads = 1 } [profile.default] -retries = { backoff = "exponential", count = 2, delay = "3s", jitter = true } +retries = { backoff = "exponential", count = 2, delay = "5s", jitter = true } slow-timeout = { period = "1m", terminate-after = 3 } [[profile.default.overrides]] diff --git a/.devcontainer/Dockerfile.dev b/.devcontainer/Dockerfile.dev new file mode 100644 index 0000000000000..9017115744e89 --- /dev/null +++ b/.devcontainer/Dockerfile.dev @@ -0,0 +1,84 @@ +FROM ubuntu:22.04 + +ARG USERNAME=foundry +ARG USER_UID=1000 +ARG USER_GID=$USER_UID +ARG PYTHON_VERSION=3.11 +ARG NODE_MAJOR=20 +ARG VYPER_VERSION=0.4.0 + +ENV DEBIAN_FRONTEND=noninteractive +ENV CARGO_TERM_COLOR=always \ + RUST_BACKTRACE=full + +WORKDIR /workspace + +RUN apt-get update && apt-get install -y --no-install-recommends \ + # Build tools + build-essential \ + clang \ + lld \ + pkg-config \ + # Network/SSL + curl \ + ca-certificates \ + gnupg \ + libssl-dev \ + # Version control & utils + git \ + sudo \ + unzip \ + # Python + python${PYTHON_VERSION} \ + python3-pip \ + python${PYTHON_VERSION}-venv \ + # Add Node.js repo + && mkdir -p /etc/apt/keyrings \ + && curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg \ + && echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_${NODE_MAJOR}.x nodistro main" | tee /etc/apt/sources.list.d/nodesource.list \ + # Update again after adding repo and install Node.js + && apt-get update && apt-get install -y --no-install-recommends \ + nodejs \ + # Clean up apt cache + && apt-get clean && rm -rf /var/lib/apt/lists/* + +# Ensure python points to the installed python version +RUN ln -sf /usr/bin/python${PYTHON_VERSION} /usr/bin/python && \ + ln -sf /usr/bin/python${PYTHON_VERSION} /usr/bin/python3 + +# Create non-root user with sudo privileges +RUN groupadd --gid $USER_GID $USERNAME \ + && useradd --uid $USER_UID --gid $USER_GID -m $USERNAME -s /bin/bash \ + # Setup sudo without password prompt + && echo "$USERNAME ALL=(ALL) NOPASSWD:ALL" > /etc/sudoers.d/$USERNAME \ + && chmod 0440 /etc/sudoers.d/$USERNAME \ + # Add user to the sudo group (standard practice) + && usermod -aG sudo $USERNAME + +# Switch to the non-root user +USER $USERNAME +WORKDIR /home/$USERNAME + +# --- User-specific installations --- + +# Install Bun +ENV BUN_INSTALL="/home/$USERNAME/.bun" +ENV PATH="$BUN_INSTALL/bin:$PATH" +RUN curl -fsSL https://bun.sh/install | bash + +# Install Rust & cargo-nextest +ENV CARGO_HOME="/home/$USERNAME/.cargo" +ENV RUSTUP_HOME="/home/$USERNAME/.rustup" +ENV PATH="$CARGO_HOME/bin:$PATH" +RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y \ + && cargo install cargo-nextest --locked + +# Install Vyper using pip +# Ensure pip user install directory is in PATH +ENV PYTHONUSERBASE="/home/$USERNAME/.local" +ENV PATH="$PYTHONUSERBASE/bin:$PATH" +RUN pip3 install --user vyper==${VYPER_VERSION} + +# Switch back to the main workspace directory +WORKDIR /workspace + diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 0000000000000..1c2eb26f5a9f6 --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,49 @@ +// For format details, see https://aka.ms/devcontainer.json. +{ + "name": "Foundry Development", + "build": { + "context": "..", + "dockerfile": "Dockerfile.dev" + }, + + "features": { + "ghcr.io/devcontainers/features/common-utils:2": { + "installZsh": true, + "configureZshAsDefaultShell": true, + "installOhMyZsh": true, + "upgradePackages": true + } + }, + + "forwardPorts": [], + + "postCreateCommand": "rustup default stable && rustup update", + + "customizations": { + "vscode": { + "extensions": [ + "rust-lang.rust-analyzer", + "serayuzgur.crates", + "tamasfe.even-better-toml", + "ms-python.python", + "dbaeumer.vscode-eslint", + "oven.bun-vscode" + ], + "settings": { + "rust-analyzer.checkOnSave": true, + "rust-analyzer.cargo.features": "all" + } + } + }, + + "remoteUser": "foundry", + + "workspaceMount": "source=${localWorkspaceFolder},target=/workspace,type=bind,consistency=cached", + + "workspaceFolder": "/workspace", + + "mounts": [ + "source=${localEnv:HOME}/.cargo/registry,target=/home/foundry/.cargo/registry,type=bind,consistency=cached", + "source=${localEnv:HOME}/.cargo/git,target=/home/foundry/.cargo/git,type=bind,consistency=cached" + ] +} diff --git a/.gitattributes b/.gitattributes index 0e0276a958df8..a1451e6b491a5 100644 --- a/.gitattributes +++ b/.gitattributes @@ -3,3 +3,4 @@ testdata/cheats/Vm.sol linguist-generated # See *.rs diff=rust +crates/lint/testdata/* text eol=lf diff --git a/.github/workflows/nextest.yml b/.github/workflows/nextest.yml index d50953871eaf5..f0502b9a71915 100644 --- a/.github/workflows/nextest.yml +++ b/.github/workflows/nextest.yml @@ -54,7 +54,9 @@ jobs: - uses: dtolnay/rust-toolchain@stable with: target: ${{ matrix.target }} - - uses: taiki-e/install-action@nextest + - uses: taiki-e/install-action@v2 + with: + tool: nextest@0.9.98 # External tests dependencies - name: Setup Node.js @@ -72,6 +74,7 @@ jobs: with: python-version: 3.11 - name: Install Vyper + # Also update vyper version in .devcontainer/Dockerfile.dev run: pip --version && pip install vyper==0.4.0 - name: Forge RPC cache diff --git a/.github/workflows/nix.yml b/.github/workflows/nix.yml new file mode 100644 index 0000000000000..e1091ec923ef7 --- /dev/null +++ b/.github/workflows/nix.yml @@ -0,0 +1,44 @@ +name: nix + +on: + schedule: + # Run weekly + - cron: "0 0 * * SUN" + workflow_dispatch: + # Needed so we can run it manually + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + # Opens a PR with an updated flake.lock file + update: + runs-on: ubuntu-latest + steps: + - uses: DeterminateSystems/determinate-nix-action@v3 + - uses: actions/checkout@v4 + - uses: DeterminateSystems/update-flake-lock@main + with: + pr-title: "Update flake.lock" + pr-labels: | + L-ignore + A-dependencies + + build: + strategy: + matrix: + runs-on: [ubuntu-latest, macos-latest] + runs-on: ${{ matrix.runs-on }} + steps: + - uses: DeterminateSystems/determinate-nix-action@v3 + - uses: actions/checkout@v4 + + - name: Update flake.lock + run: nix flake update + + - name: Activate nix env + run: nix develop -c echo Ok + + - name: Check that we can compile all crates + run: nix develop -c cargo check --all-targets diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 8d96287ebb22c..b94b219cdc312 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -15,7 +15,7 @@ env: CARGO_TERM_COLOR: always IS_NIGHTLY: ${{ github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' }} PROFILE: maxperf - STABLE_VERSION: "v1.0.0" + STABLE_VERSION: "v1.1.0" jobs: prepare: @@ -231,7 +231,7 @@ jobs: # Creates the release for this specific version - name: Create release - uses: softprops/action-gh-release@v2 + uses: softprops/action-gh-release@v2.2.2 with: name: ${{ needs.prepare.outputs.release_name }} tag_name: ${{ needs.prepare.outputs.tag_name }} @@ -254,7 +254,7 @@ jobs: # tagged `nightly` for compatibility with `foundryup` - name: Update nightly release if: ${{ env.IS_NIGHTLY == 'true' }} - uses: softprops/action-gh-release@v2 + uses: softprops/action-gh-release@v2.2.2 with: name: "Nightly" tag_name: "nightly" diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 0486f2237f10b..d5e21a495e6b0 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -109,7 +109,7 @@ jobs: - uses: Swatinem/rust-cache@v2 with: cache-on-failure: true - - run: cargo hack check --each-feature --exclude-features isolate-by-default + - run: cargo hack check deny: uses: ithacaxyz/ci/.github/workflows/deny.yml@main diff --git a/.gitignore b/.gitignore index fe8e13360dff4..c064886863e6f 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,5 @@ snapshots/ out.json .idea .vscode +.claude +CLAUDE.md diff --git a/CHANGELOG.md b/CHANGELOG.md deleted file mode 100644 index 2d9a4e1a92665..0000000000000 --- a/CHANGELOG.md +++ /dev/null @@ -1,37 +0,0 @@ -# Changelog - -## Pre 1.0 - -### Important note for users - -Multiple breaking changes will occur so Semver can be followed as soon as Foundry 1.0 is released. They will be listed here, along with the updates needed for your projects. - -If you need a stable Foundry version, we recommend using the latest pinned nightly of May 2nd, locally and on your CI. - -To use the latest pinned nightly locally, use the following command: - -``` -foundryup --version nightly-e15e33a07c0920189fc336391f538c3dad53da73 -```` - -To use the latest pinned nightly on your CI, modify your Foundry installation step to use an specific version: - -``` -- name: Install Foundry - uses: foundry-rs/foundry-toolchain@v1 - with: - version: nightly-e15e33a07c0920189fc336391f538c3dad53da73 -``` - -### Breaking changes - -- [expectEmit](https://github.com/foundry-rs/foundry/pull/4920) will now only work for the next call. -- expectCall will now only work if the call(s) are made exactly after the cheatcode is invoked. -- [expectRevert will now work if the next call does revert](https://github.com/foundry-rs/foundry/pull/4945), instead of expecting a revert during the whole test. - - This will very likely break your tests. Please make sure that all the calls you expect to revert are external, and if not, abstract them into a separate contract so that they can be called externally and the cheatcode can be used. -- `-m`, the deprecated alias for `--mt` or `--match-test`, has now been removed. -- [startPrank will now override the existing prank instead of erroring](https://github.com/foundry-rs/foundry/pull/4826). -- [precompiles will not be compatible with all cheatcodes](https://github.com/foundry-rs/foundry/pull/4905). -- The difficulty and prevrandao cheatcodes now [fail if not used with the correct EVM version](https://github.com/foundry-rs/foundry/pull/4904). -- The default EVM version will be Shanghai. If you're using an EVM chain which is not compatible with [EIP-3855](https://eips.ethereum.org/EIPS/eip-3855) you need to change your EVM version. See [Matt Solomon's thread](https://twitter.com/msolomon44/status/1656411871635972096) for more information. -- Non-existent JSON keys are now processed correctly, and `parseJson` returns non-decodable empty bytes if they do not exist. https://github.com/foundry-rs/foundry/pull/5511 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 98b36e8539a05..4b85713d56cdf 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -40,8 +40,8 @@ elsewhere. If you have reviewed existing documentation and still have questions, or you are having problems, you can get help in the following ways: -- **Asking in the support Telegram:** The [Foundry Support Telegram][support-tg] is a fast and easy way to ask questions. -- **Opening a discussion:** This repository comes with a discussions board where you can also ask for help. Click the "Discussions" tab at the top. +- **Asking in the support Telegram:** The [Foundry Support Telegram][support-tg] is a fast and easy way to ask questions. +- **Opening a discussion:** This repository comes with a discussions board where you can also ask for help. Click the "Discussions" tab at the top. As Foundry is still in heavy development, the documentation can be a bit scattered. The [Foundry Book][foundry-book] is our current best-effort attempt at keeping up-to-date information. @@ -54,10 +54,10 @@ If you believe that you have uncovered a bug, please fill out the form to the be The most important pieces of information we need in a bug report are: -- The Foundry version you are on (and that it is up to date) -- The platform you are on (Windows, macOS, an M1 Mac or Linux) -- Code snippets if this is happening in relation to testing or building code -- Concrete steps to reproduce the bug +- The Foundry version you are on (and that it is up to date) +- The platform you are on (Windows, macOS or Linux) +- Code snippets if this is happening in relation to testing or building code +- Concrete steps to reproduce the bug In order to rule out the possibility of the bug being in your project, the code snippets should be as minimal as possible. It is better if you can reproduce the bug with a small snippet as opposed to an entire project! @@ -86,7 +86,14 @@ Please also make sure that the following commands pass if you have changed the c cargo check --all cargo test --all --all-features cargo +nightly fmt -- --check -cargo +nightly clippy --all --all-targets --all-features -- -D warnings +cargo +nightly clippy --all --all-targets --all-features -- -D warning +``` + +or alternatively: + +```sh +make build +make pr ``` If you are working in VSCode, we recommend you install the [rust-analyzer](https://rust-analyzer.github.io/) extension, and use the following VSCode user settings: @@ -103,7 +110,7 @@ If you are working on a larger feature, we encourage you to open up a draft pull If you would like to test the binaries built from your change, see [foundryup](https://github.com/foundry-rs/foundry/tree/master/foundryup). -If you would like to use a debugger with breakpoints to debug a patch you might be working on, keep in mind we currently strip debug info for faster builds, which is *not* the default. Therefore, to use a debugger, you need to enable it on the workspace [`Cargo.toml`'s `dev` profile](https://github.com/foundry-rs/foundry/tree/master/Cargo.toml#L15-L18). +If you would like to use a debugger with breakpoints to debug a patch you might be working on, keep in mind we currently strip debug info for faster builds, which is _not_ the default. Therefore, to use a debugger, you need to enable it on the workspace [`Cargo.toml`'s `dev` profile](https://github.com/foundry-rs/foundry/tree/master/Cargo.toml#L15-L18). #### Adding tests @@ -113,9 +120,9 @@ in the future. Types of tests include: -- **Unit tests**: Functions which have very specific tasks should be unit tested. -- **Integration tests**: For general purpose, far reaching functionality, integration tests should be added. - The best way to add a new integration test is to look at existing ones and follow the style. +- **Unit tests**: Functions which have very specific tasks should be unit tested. +- **Integration tests**: For general purpose, far reaching functionality, integration tests should be added. + The best way to add a new integration test is to look at existing ones and follow the style. Tests that use forking must contain "fork" in their name. diff --git a/Cargo.lock b/Cargo.lock index 00dccbb789c71..cce654320611b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,24 +4,18 @@ version = 4 [[package]] name = "addr2line" -version = "0.21.0" +version = "0.24.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" +checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" dependencies = [ "gimli", ] -[[package]] -name = "adler" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" - [[package]] name = "adler2" -version = "2.0.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" [[package]] name = "aes" @@ -36,15 +30,15 @@ dependencies = [ [[package]] name = "ahash" -version = "0.8.11" +version = "0.8.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" +checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" dependencies = [ "cfg-if", - "getrandom 0.2.16", + "getrandom 0.3.3", "once_cell", "version_check", - "zerocopy 0.7.35", + "zerocopy", ] [[package]] @@ -64,11 +58,11 @@ checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" [[package]] name = "alloy-chains" -version = "0.1.69" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28e2652684758b0d9b389d248b209ed9fd9989ef489a550265fe4bb8454fe7eb" +checksum = "19a9cc9d81ace3da457883b0bdf76776e55f1b84219a9e9d55c27ad308548d3f" dependencies = [ - "alloy-primitives 0.8.25", + "alloy-primitives", "num_enum", "serde", "strum 0.27.1", @@ -76,15 +70,16 @@ dependencies = [ [[package]] name = "alloy-consensus" -version = "0.12.6" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fbf458101ed6c389e9bb70a34ebc56039868ad10472540614816cdedc8f5265" +checksum = "2bcb57295c4b632b6b3941a089ee82d00ff31ff9eb3eac801bf605ffddc81041" dependencies = [ "alloy-eips", - "alloy-primitives 0.8.25", + "alloy-primitives", "alloy-rlp", "alloy-serde", "alloy-trie", + "alloy-tx-macros", "auto_impl", "c-kzg", "derive_more 2.0.1", @@ -92,6 +87,7 @@ dependencies = [ "k256", "once_cell", "rand 0.8.5", + "secp256k1", "serde", "serde_with", "thiserror 2.0.12", @@ -99,13 +95,13 @@ dependencies = [ [[package]] name = "alloy-consensus-any" -version = "0.12.6" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc982af629e511292310fe85b433427fd38cb3105147632b574abc997db44c91" +checksum = "3ab669be40024565acb719daf1b2a050e6dc065fc0bec6050d97a81cdb860bd7" dependencies = [ "alloy-consensus", "alloy-eips", - "alloy-primitives 0.8.25", + "alloy-primitives", "alloy-rlp", "alloy-serde", "serde", @@ -113,16 +109,16 @@ dependencies = [ [[package]] name = "alloy-contract" -version = "0.12.6" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd0a0c1ddee20ecc14308aae21c2438c994df7b39010c26d70f86e1d8fdb8db0" +checksum = "8ba5d28e15c14226f243d6e329611840135e1b0fa31feaea57c461e0b03b4c7b" dependencies = [ "alloy-consensus", "alloy-dyn-abi", - "alloy-json-abi 0.8.25", + "alloy-json-abi", "alloy-network", "alloy-network-primitives", - "alloy-primitives 0.8.25", + "alloy-primitives", "alloy-provider", "alloy-pubsub", "alloy-rpc-types-eth", @@ -130,37 +126,37 @@ dependencies = [ "alloy-transport", "futures", "futures-util", + "serde_json", "thiserror 2.0.12", ] [[package]] name = "alloy-dyn-abi" -version = "0.8.25" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb8e762aefd39a397ff485bc86df673465c4ad3ec8819cc60833a8a3ba5cdc87" +checksum = "f9135eb501feccf7f4cb8a183afd406a65483fdad7bbd7332d0470e5d725c92f" dependencies = [ - "alloy-json-abi 0.8.25", - "alloy-primitives 0.8.25", - "alloy-sol-type-parser 0.8.25", + "alloy-json-abi", + "alloy-primitives", + "alloy-sol-type-parser", "alloy-sol-types", "arbitrary", - "const-hex", "derive_arbitrary", "derive_more 2.0.1", "itoa", "proptest", "serde", "serde_json", - "winnow 0.7.7", + "winnow", ] [[package]] name = "alloy-eip2124" -version = "0.1.0" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "675264c957689f0fd75f5993a73123c2cc3b5c235a38f5b9037fe6c826bfb2c0" +checksum = "741bdd7499908b3aa0b159bba11e71c8cddd009a2c2eb7a06e825f1ec87900a5" dependencies = [ - "alloy-primitives 0.8.25", + "alloy-primitives", "alloy-rlp", "crc", "serde", @@ -169,98 +165,128 @@ dependencies = [ [[package]] name = "alloy-eip2930" -version = "0.1.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0069cf0642457f87a01a014f6dc29d5d893cd4fd8fddf0c3cdfad1bb3ebafc41" +checksum = "7b82752a889170df67bbb36d42ca63c531eb16274f0d7299ae2a680facba17bd" dependencies = [ - "alloy-primitives 0.8.25", + "alloy-primitives", "alloy-rlp", - "arbitrary", - "rand 0.8.5", "serde", ] [[package]] name = "alloy-eip7702" -version = "0.5.1" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b15b13d38b366d01e818fe8e710d4d702ef7499eacd44926a06171dd9585d0c" +checksum = "9d4769c6ffddca380b0070d71c8b7f30bed375543fe76bb2f74ec0acf4b7cd16" dependencies = [ - "alloy-primitives 0.8.25", + "alloy-primitives", "alloy-rlp", - "arbitrary", "k256", - "rand 0.8.5", "serde", "thiserror 2.0.12", ] [[package]] name = "alloy-eips" -version = "0.12.6" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e86967eb559920e4b9102e4cb825fe30f2e9467988353ce4809f0d3f2c90cd4" +checksum = "4f853de9ca1819f54de80de5d03bfc1bb7c9fafcf092b480a654447141bc354d" dependencies = [ "alloy-eip2124", "alloy-eip2930", "alloy-eip7702", - "alloy-primitives 0.8.25", + "alloy-primitives", "alloy-rlp", "alloy-serde", "auto_impl", "c-kzg", "derive_more 2.0.1", "either", - "once_cell", "serde", - "sha2", + "sha2 0.10.9", +] + +[[package]] +name = "alloy-ens" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cba413e1baa2a5a765f48d30cb0244d0c6b3cf911c5e66105361744b277da8d" +dependencies = [ + "alloy-contract", + "alloy-primitives", + "alloy-provider", + "alloy-sol-types", + "async-trait", + "thiserror 2.0.12", +] + +[[package]] +name = "alloy-evm" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "394b09cf3a32773eedf11828987f9c72dfa74545040be0422e3f5f09a2a3fab9" +dependencies = [ + "alloy-consensus", + "alloy-eips", + "alloy-hardforks", + "alloy-primitives", + "alloy-sol-types", + "auto_impl", + "derive_more 2.0.1", + "op-alloy-consensus", + "op-revm", + "revm", + "thiserror 2.0.12", ] [[package]] name = "alloy-genesis" -version = "0.12.6" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a40de6f5b53ecf5fd7756072942f41335426d9a3704cd961f77d854739933bcf" +checksum = "8500bcc1037901953771c25cb77e0d4ec0bffd938d93a04715390230d21a612d" dependencies = [ "alloy-eips", - "alloy-primitives 0.8.25", + "alloy-primitives", "alloy-serde", "alloy-trie", "serde", ] [[package]] -name = "alloy-json-abi" -version = "0.8.25" +name = "alloy-hardforks" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe6beff64ad0aa6ad1019a3db26fef565aefeb011736150ab73ed3366c3cfd1b" +checksum = "977d2492ce210e34baf7b36afaacea272c96fbe6774c47e23f97d14033c0e94f" dependencies = [ - "alloy-primitives 0.8.25", - "alloy-sol-type-parser 0.8.25", - "serde", - "serde_json", + "alloy-chains", + "alloy-eip2124", + "alloy-primitives", + "auto_impl", + "dyn-clone", ] [[package]] name = "alloy-json-abi" -version = "1.0.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5189fa9a8797e92396bc4b4454c5f2073a4945f7c2b366af9af60f9536558f7a" +checksum = "8b26fdd571915bafe857fccba4ee1a4f352965800e46a53e4a5f50187b7776fa" dependencies = [ - "alloy-primitives 1.0.0", - "alloy-sol-type-parser 1.0.0", + "alloy-primitives", + "alloy-sol-type-parser", "serde", "serde_json", ] [[package]] name = "alloy-json-rpc" -version = "0.12.6" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27434beae2514d4a2aa90f53832cbdf6f23e4b5e2656d95eaf15f9276e2418b6" +checksum = "f4997a9873c8639d079490f218e50e5fa07e70f957e9fc187c0a0535977f482f" dependencies = [ - "alloy-primitives 0.8.25", + "alloy-primitives", "alloy-sol-types", + "http 1.3.1", "serde", "serde_json", "thiserror 2.0.12", @@ -269,16 +295,16 @@ dependencies = [ [[package]] name = "alloy-network" -version = "0.12.6" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26a33a38c7486b1945f8d093ff027add2f3a8f83c7300dbad6165cc49150085e" +checksum = "a0306e8d148b7b94d988615d367443c1b9d6d2e9fecd2e1f187ac5153dce56f5" dependencies = [ "alloy-consensus", "alloy-consensus-any", "alloy-eips", "alloy-json-rpc", "alloy-network-primitives", - "alloy-primitives 0.8.25", + "alloy-primitives", "alloy-rpc-types-any", "alloy-rpc-types-eth", "alloy-serde", @@ -295,67 +321,67 @@ dependencies = [ [[package]] name = "alloy-network-primitives" -version = "0.12.6" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db973a7a23cbe96f2958e5687c51ce2d304b5c6d0dc5ccb3de8667ad8476f50b" +checksum = "3eef189583f4c53d231dd1297b28a675ff842b551fb34715f562868a1937431a" dependencies = [ "alloy-consensus", "alloy-eips", - "alloy-primitives 0.8.25", + "alloy-primitives", "alloy-serde", "serde", ] [[package]] -name = "alloy-primitives" -version = "0.8.25" +name = "alloy-op-evm" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c77490fe91a0ce933a1f219029521f20fc28c2c0ca95d53fa4da9c00b8d9d4e" +checksum = "9f32538cc243ec5d4603da9845cc2f5254c6a3a78e82475beb1a2a1de6c0d36c" dependencies = [ - "alloy-rlp", - "arbitrary", - "bytes", - "cfg-if", - "const-hex", - "derive_arbitrary", - "derive_more 2.0.1", - "foldhash", - "getrandom 0.2.16", - "hashbrown 0.15.2", - "indexmap 2.9.0", - "itoa", - "k256", - "keccak-asm", - "paste", - "proptest", - "proptest-derive", - "rand 0.8.5", - "ruint", - "rustc-hash 2.1.1", - "serde", - "sha3", - "tiny-keccak", + "alloy-consensus", + "alloy-eips", + "alloy-evm", + "alloy-op-hardforks", + "alloy-primitives", + "auto_impl", + "op-alloy-consensus", + "op-revm", + "revm", +] + +[[package]] +name = "alloy-op-hardforks" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08b147547aff595aa3d4c2fc2c8146263e18d3372909def423619ed631ecbcfa" +dependencies = [ + "alloy-hardforks", + "auto_impl", ] [[package]] name = "alloy-primitives" -version = "1.0.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70b98b99c1dcfbe74d7f0b31433ff215e7d1555e367d90e62db904f3c9d4ff53" +checksum = "a326d47106039f38b811057215a92139f46eef7983a4b77b10930a0ea5685b1e" dependencies = [ "alloy-rlp", + "arbitrary", "bytes", "cfg-if", "const-hex", + "derive_arbitrary", "derive_more 2.0.1", "foldhash", - "hashbrown 0.15.2", + "getrandom 0.3.3", + "hashbrown 0.15.4", "indexmap 2.9.0", "itoa", "k256", "keccak-asm", "paste", "proptest", + "proptest-derive", "rand 0.9.1", "ruint", "rustc-hash 2.1.1", @@ -366,9 +392,9 @@ dependencies = [ [[package]] name = "alloy-provider" -version = "0.12.6" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b03bde77ad73feae14aa593bcabb932c8098c0f0750ead973331cfc0003a4e1" +checksum = "ea624ddcdad357c33652b86aa7df9bd21afd2080973389d3facf1a221c573948" dependencies = [ "alloy-chains", "alloy-consensus", @@ -376,13 +402,14 @@ dependencies = [ "alloy-json-rpc", "alloy-network", "alloy-network-primitives", - "alloy-primitives 0.8.25", + "alloy-primitives", "alloy-pubsub", "alloy-rpc-client", "alloy-rpc-types-debug", "alloy-rpc-types-eth", "alloy-rpc-types-trace", "alloy-rpc-types-txpool", + "alloy-signer", "alloy-sol-types", "alloy-transport", "alloy-transport-http", @@ -392,8 +419,10 @@ dependencies = [ "async-trait", "auto_impl", "dashmap", + "either", "futures", "futures-utils-wasm", + "http 1.3.1", "lru 0.13.0", "parking_lot", "pin-project 1.1.10", @@ -409,28 +438,30 @@ dependencies = [ [[package]] name = "alloy-pubsub" -version = "0.12.6" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "721aca709a9231815ad5903a2d284042cc77e7d9d382696451b30c9ee0950001" +checksum = "1ea3227fa5f627d22b7781e88bc2fe79ba1792d5535b4161bc8fc99cdcd8bedd" dependencies = [ "alloy-json-rpc", - "alloy-primitives 0.8.25", + "alloy-primitives", "alloy-transport", "bimap", "futures", + "parking_lot", "serde", "serde_json", "tokio", "tokio-stream", - "tower 0.5.2", + "tower", "tracing", + "wasmtimer", ] [[package]] name = "alloy-rlp" -version = "0.3.11" +version = "0.3.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d6c1d995bff8d011f7cd6c81820d51825e6e06d6db73914c1630ecf544d83d6" +checksum = "5f70d83b765fdc080dbcd4f4db70d8d23fe4761f2f02ebfa9146b833900634b4" dependencies = [ "alloy-rlp-derive", "arrayvec", @@ -439,23 +470,23 @@ dependencies = [ [[package]] name = "alloy-rlp-derive" -version = "0.3.11" +version = "0.3.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a40e1ef334153322fd878d07e86af7a529bcb86b2439525920a88eba87bcf943" +checksum = "64b728d511962dda67c1bc7ea7c03736ec275ed2cf4c35d9585298ac9ccf3b73" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] name = "alloy-rpc-client" -version = "0.12.6" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "445a3298c14fae7afb5b9f2f735dead989f3dd83020c2ab8e48ed95d7b6d1acb" +checksum = "e43d00b4de38432304c4e4b01ae6a3601490fd9824c852329d158763ec18663c" dependencies = [ "alloy-json-rpc", - "alloy-primitives 0.8.25", + "alloy-primitives", "alloy-pubsub", "alloy-transport", "alloy-transport-http", @@ -469,7 +500,7 @@ dependencies = [ "serde_json", "tokio", "tokio-stream", - "tower 0.5.2", + "tower", "tracing", "tracing-futures", "url", @@ -478,11 +509,11 @@ dependencies = [ [[package]] name = "alloy-rpc-types" -version = "0.12.6" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9157deaec6ba2ad7854f16146e4cd60280e76593eed79fdcb06e0fa8b6c60f77" +checksum = "3bf22ddb69a436f28bbdda7daf34fe011ee9926fa13bfce89fa023aca9ce2b2f" dependencies = [ - "alloy-primitives 0.8.25", + "alloy-primitives", "alloy-rpc-types-anvil", "alloy-rpc-types-engine", "alloy-rpc-types-eth", @@ -494,11 +525,11 @@ dependencies = [ [[package]] name = "alloy-rpc-types-anvil" -version = "0.12.6" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a80ee83ef97e7ffd667a81ebdb6154558dfd5e8f20d8249a10a12a1671a04b3" +checksum = "1ecd1c60085d8cbc3562e16e264a3cd68f42e54dc16b0d40645e5e42841bc042" dependencies = [ - "alloy-primitives 0.8.25", + "alloy-primitives", "alloy-rpc-types-eth", "alloy-serde", "serde", @@ -506,9 +537,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-any" -version = "0.12.6" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "604dea1f00fd646debe8033abe8e767c732868bf8a5ae9df6321909ccbc99c56" +checksum = "5958f2310d69f4806e6f6b90ceb4f2b781cc5a843517a7afe2e7cfec6de3cfb9" dependencies = [ "alloy-consensus-any", "alloy-rpc-types-eth", @@ -517,23 +548,23 @@ dependencies = [ [[package]] name = "alloy-rpc-types-debug" -version = "0.12.6" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08b113a0087d226291b9768ed331818fa0b0744cc1207ae7c150687cf3fde1bd" +checksum = "c293df0c58d15330e65599f776e30945ea078c93b1594e5c4ba0efaad3f0a739" dependencies = [ - "alloy-primitives 0.8.25", + "alloy-primitives", "serde", ] [[package]] name = "alloy-rpc-types-engine" -version = "0.12.6" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "874ac9d1249ece0453e262d9ba72da9dbb3b7a2866220ded5940c2e47f1aa04d" +checksum = "2e5b09d86d0c015cb8400c5d1d0483425670bef4fc1260336aea9ef6d4b9540c" dependencies = [ "alloy-consensus", "alloy-eips", - "alloy-primitives 0.8.25", + "alloy-primitives", "alloy-rlp", "alloy-serde", "derive_more 2.0.1", @@ -545,15 +576,15 @@ dependencies = [ [[package]] name = "alloy-rpc-types-eth" -version = "0.12.6" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e13d71eac04513a71af4b3df580f52f2b4dcbff9d971cc9a52519acf55514cb" +checksum = "1826285e4ffc2372a8c061d5cc145858e67a0be3309b768c5b77ddb6b9e6cbc7" dependencies = [ "alloy-consensus", "alloy-consensus-any", "alloy-eips", "alloy-network-primitives", - "alloy-primitives 0.8.25", + "alloy-primitives", "alloy-rlp", "alloy-serde", "alloy-sol-types", @@ -565,11 +596,11 @@ dependencies = [ [[package]] name = "alloy-rpc-types-trace" -version = "0.12.6" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4747763aee39c1b0f5face79bde9be8932be05b2db7d8bdcebb93490f32c889c" +checksum = "d1e8f7fa774a1d6f7b3654686f68955631e55f687e03da39c0bd77a9418d57a1" dependencies = [ - "alloy-primitives 0.8.25", + "alloy-primitives", "alloy-rpc-types-eth", "alloy-serde", "serde", @@ -579,11 +610,11 @@ dependencies = [ [[package]] name = "alloy-rpc-types-txpool" -version = "0.12.6" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70132ebdbea1eaa68c4d6f7a62c2fadf0bdce83b904f895ab90ca4ec96f63468" +checksum = "d39d9218a0fd802dbccd7e0ce601a6bdefb61190386e97a437d97a31661cd358" dependencies = [ - "alloy-primitives 0.8.25", + "alloy-primitives", "alloy-rpc-types-eth", "alloy-serde", "serde", @@ -591,23 +622,23 @@ dependencies = [ [[package]] name = "alloy-serde" -version = "0.12.6" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a1cd73fc054de6353c7f22ff9b846b0f0f145cd0112da07d4119e41e9959207" +checksum = "906ce0190afeded19cb2e963cb8507c975a7862216b9e74f39bf91ddee6ae74b" dependencies = [ - "alloy-primitives 0.8.25", + "alloy-primitives", "serde", "serde_json", ] [[package]] name = "alloy-signer" -version = "0.12.6" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c96fbde54bee943cd94ebacc8a62c50b38c7dfd2552dcd79ff61aea778b1bfcc" +checksum = "c89baab06195c4be9c5d66f15c55e948013d1aff3ec1cfb0ed469e1423313fce" dependencies = [ "alloy-dyn-abi", - "alloy-primitives 0.8.25", + "alloy-primitives", "alloy-sol-types", "async-trait", "auto_impl", @@ -619,13 +650,13 @@ dependencies = [ [[package]] name = "alloy-signer-aws" -version = "0.12.6" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e73835ed6689740b76cab0f59afbdce374a03d3f856ea33ba1fc054630a1b28" +checksum = "3d6a8589f1186ffb01e0086d515c657bf1bfb3bb456cfc7c6f51dbb29e154a5f" dependencies = [ "alloy-consensus", "alloy-network", - "alloy-primitives 0.8.25", + "alloy-primitives", "alloy-signer", "async-trait", "aws-sdk-kms", @@ -637,13 +668,13 @@ dependencies = [ [[package]] name = "alloy-signer-gcp" -version = "0.12.6" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a16b468ae86bb876d9c7a3b49b1e8d614a581a1a9673e4e0d2393b411080fe64" +checksum = "4080f77195f44eeb806aa1dcdb55c1c14ac16a4f85e8f0ca769b14709cf3fc3c" dependencies = [ "alloy-consensus", "alloy-network", - "alloy-primitives 0.8.25", + "alloy-primitives", "alloy-signer", "async-trait", "gcloud-sdk", @@ -655,14 +686,14 @@ dependencies = [ [[package]] name = "alloy-signer-ledger" -version = "0.12.6" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44cf8a7f45edcc43566218e44b70ed3c278b7556926158cfeb63c8d41fefef70" +checksum = "ccaa089aa033669ed92f05e365e3a12d7a29b0cc5ee415917f3bf381fa1f346d" dependencies = [ "alloy-consensus", "alloy-dyn-abi", "alloy-network", - "alloy-primitives 0.8.25", + "alloy-primitives", "alloy-signer", "alloy-sol-types", "async-trait", @@ -675,13 +706,13 @@ dependencies = [ [[package]] name = "alloy-signer-local" -version = "0.12.6" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc6e72002cc1801d8b41e9892165e3a6551b7bd382bd9d0414b21e90c0c62551" +checksum = "8a249a923e302ac6db932567c43945392f0b6832518aab3c4274858f58756774" dependencies = [ "alloy-consensus", "alloy-network", - "alloy-primitives 0.8.25", + "alloy-primitives", "alloy-signer", "async-trait", "coins-bip32", @@ -694,13 +725,13 @@ dependencies = [ [[package]] name = "alloy-signer-trezor" -version = "0.12.6" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d4fd403c53cf7924c3e16c61955742cfc3813188f0975622f4fa6f8a01760aa" +checksum = "9a087d62a5c2b109c2f7521da31d72ba914025141f2aa1c0be122ddc3f0ce31b" dependencies = [ "alloy-consensus", "alloy-network", - "alloy-primitives 0.8.25", + "alloy-primitives", "alloy-signer", "async-trait", "semver 1.0.26", @@ -711,25 +742,25 @@ dependencies = [ [[package]] name = "alloy-sol-macro" -version = "0.8.25" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e10ae8e9a91d328ae954c22542415303919aabe976fe7a92eb06db1b68fd59f2" +checksum = "d4be1ce1274ddd7fdfac86e5ece1b225e9bba1f2327e20fbb30ee6b9cc1423fe" dependencies = [ "alloy-sol-macro-expander", "alloy-sol-macro-input", "proc-macro-error2", "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] name = "alloy-sol-macro-expander" -version = "0.8.25" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83ad5da86c127751bc607c174d6c9fe9b85ef0889a9ca0c641735d77d4f98f26" +checksum = "01e92f3708ea4e0d9139001c86c051c538af0146944a2a9c7181753bd944bf57" dependencies = [ - "alloy-json-abi 0.8.25", + "alloy-json-abi", "alloy-sol-macro-input", "const-hex", "heck", @@ -737,18 +768,18 @@ dependencies = [ "proc-macro-error2", "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", "syn-solidity", "tiny-keccak", ] [[package]] name = "alloy-sol-macro-input" -version = "0.8.25" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba3d30f0d3f9ba3b7686f3ff1de9ee312647aac705604417a2f40c604f409a9e" +checksum = "9afe1bd348a41f8c9b4b54dfb314886786d6201235b0b3f47198b9d910c86bb2" dependencies = [ - "alloy-json-abi 0.8.25", + "alloy-json-abi", "const-hex", "dunce", "heck", @@ -756,50 +787,40 @@ dependencies = [ "proc-macro2", "quote", "serde_json", - "syn 2.0.101", + "syn 2.0.103", "syn-solidity", ] [[package]] name = "alloy-sol-type-parser" -version = "0.8.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d162f8524adfdfb0e4bd0505c734c985f3e2474eb022af32eef0d52a4f3935c" -dependencies = [ - "serde", - "winnow 0.7.7", -] - -[[package]] -name = "alloy-sol-type-parser" -version = "1.0.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2b5f5f9f561c29f78ea521ebe2e5ac1633f1b1442dae582f68ecd57c6350042" +checksum = "d6195df2acd42df92a380a8db6205a5c7b41282d0ce3f4c665ecf7911ac292f1" dependencies = [ "serde", - "winnow 0.7.7", + "winnow", ] [[package]] name = "alloy-sol-types" -version = "0.8.25" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d43d5e60466a440230c07761aa67671d4719d46f43be8ea6e7ed334d8db4a9ab" +checksum = "6185e98a79cf19010722f48a74b5a65d153631d2f038cabd250f4b9e9813b8ad" dependencies = [ - "alloy-json-abi 0.8.25", - "alloy-primitives 0.8.25", + "alloy-json-abi", + "alloy-primitives", "alloy-sol-macro", - "const-hex", "serde", ] [[package]] name = "alloy-transport" -version = "0.12.6" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9aec325c2af8562ef355c02aeb527c755a07e9d8cf6a1e65dda8d0bf23e29b2c" +checksum = "6d1ae10b1bc77fde38161e242749e41e65e34000d05da0a3d3f631e03bfcb19e" dependencies = [ "alloy-json-rpc", + "alloy-primitives", "base64 0.22.1", "derive_more 2.0.1", "futures", @@ -809,7 +830,7 @@ dependencies = [ "serde_json", "thiserror 2.0.12", "tokio", - "tower 0.5.2", + "tower", "tracing", "url", "wasmtimer", @@ -817,24 +838,24 @@ dependencies = [ [[package]] name = "alloy-transport-http" -version = "0.12.6" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a082c9473c6642cce8b02405a979496126a03b096997888e86229afad05db06c" +checksum = "b234272ee449e32c9f1afbbe4ee08ea7c4b52f14479518f95c844ab66163c545" dependencies = [ "alloy-json-rpc", "alloy-transport", "reqwest", "serde_json", - "tower 0.5.2", + "tower", "tracing", "url", ] [[package]] name = "alloy-transport-ipc" -version = "0.12.6" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45a78cfda2cac16fa83f6b5dd8b4643caec6161433b25b67e484ce05d2194513" +checksum = "061672d736144eb5aae13ca67cfec8e5e69a65bef818cb1a2ab2345d55c50ab4" dependencies = [ "alloy-json-rpc", "alloy-pubsub", @@ -852,9 +873,9 @@ dependencies = [ [[package]] name = "alloy-transport-ws" -version = "0.12.6" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae865917bdabaae21f418010fe7e8837c6daa6611fde25f8d78a1778d6ecb523" +checksum = "40b01f10382c2aea797d710279b24687a1e9e09a09ecd145f84f636f2a8a3fcc" dependencies = [ "alloy-pubsub", "alloy-transport", @@ -863,27 +884,40 @@ dependencies = [ "rustls", "serde_json", "tokio", - "tokio-tungstenite 0.26.2", + "tokio-tungstenite", "tracing", "ws_stream_wasm", ] [[package]] name = "alloy-trie" -version = "0.7.9" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d95a94854e420f07e962f7807485856cde359ab99ab6413883e15235ad996e8b" +checksum = "983d99aa81f586cef9dae38443245e585840fcf0fc58b09aee0b1f27aed1d500" dependencies = [ - "alloy-primitives 0.8.25", + "alloy-primitives", "alloy-rlp", "arrayvec", - "derive_more 1.0.0", + "derive_more 2.0.1", "nybbles", "serde", "smallvec", "tracing", ] +[[package]] +name = "alloy-tx-macros" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b75ef8609ea2b31c799b0a56c724dca4c73105c5ccc205d9dfeb1d038df6a1da" +dependencies = [ + "alloy-primitives", + "darling", + "proc-macro2", + "quote", + "syn 2.0.103", +] + [[package]] name = "ammonia" version = "4.1.0" @@ -925,9 +959,9 @@ dependencies = [ [[package]] name = "anstream" -version = "0.6.18" +version = "0.6.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" +checksum = "301af1932e46185686725e0fad2f8f2aa7da69dd70bf6ecc44d6b703844a3933" dependencies = [ "anstyle", "anstyle-parse", @@ -940,73 +974,77 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.10" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" +checksum = "862ed96ca487e809f1c8e5a8447f6ee2cf102f846893800b20cebdf541fc6bbd" [[package]] name = "anstyle-lossy" -version = "1.1.3" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "934ff8719effd2023a48cf63e69536c1c3ced9d3895068f6f5cc9a4ff845e59b" +checksum = "04d3a5dc826f84d0ea11882bb8054ff7f3d482602e11bb181101303a279ea01f" dependencies = [ "anstyle", ] [[package]] name = "anstyle-parse" -version = "0.2.6" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" +checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" -version = "1.1.2" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" +checksum = "6c8bdeb6047d8983be085bab0ba1472e6dc604e7041dbf6fcd5e71523014fae9" dependencies = [ "windows-sys 0.59.0", ] [[package]] name = "anstyle-svg" -version = "0.1.7" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3607949e9f6de49ea4bafe12f5e4fd73613ebf24795e48587302a8cc0e4bb35" +checksum = "0a43964079ef399480603125d5afae2b219aceffb77478956e25f17b9bc3435c" dependencies = [ - "anstream", "anstyle", "anstyle-lossy", + "anstyle-parse", "html-escape", "unicode-width 0.2.0", ] [[package]] name = "anstyle-wincon" -version = "3.0.7" +version = "3.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca3534e77181a9cc07539ad51f2141fe32f6c3ffd4df76db8ad92346b003ae4e" +checksum = "403f75924867bb1033c59fbf0797484329750cfbe3c4325cd33127941fabc882" dependencies = [ "anstyle", - "once_cell", + "once_cell_polyfill", "windows-sys 0.59.0", ] [[package]] name = "anvil" -version = "1.1.0" +version = "1.2.3" dependencies = [ "alloy-chains", "alloy-consensus", "alloy-contract", "alloy-dyn-abi", "alloy-eips", + "alloy-evm", "alloy-genesis", + "alloy-hardforks", "alloy-network", - "alloy-primitives 0.8.25", + "alloy-op-evm", + "alloy-op-hardforks", + "alloy-primitives", "alloy-provider", "alloy-pubsub", "alloy-rlp", @@ -1034,45 +1072,47 @@ dependencies = [ "foundry-common", "foundry-config", "foundry-evm", + "foundry-evm-core", "foundry-test-utils", "futures", "hyper", "itertools 0.14.0", "op-alloy-consensus", "op-alloy-rpc-types", + "op-revm", "parking_lot", "rand 0.8.5", + "rand 0.9.1", "revm", "revm-inspectors", "serde", "serde_json", "tempfile", "thiserror 2.0.12", - "tikv-jemallocator", "tokio", "tracing", - "tracing-subscriber", + "tracing-subscriber 0.3.19", "yansi", ] [[package]] name = "anvil-core" -version = "1.1.0" +version = "1.2.3" dependencies = [ "alloy-consensus", "alloy-dyn-abi", "alloy-eips", "alloy-network", - "alloy-primitives 0.8.25", + "alloy-primitives", "alloy-rlp", "alloy-rpc-types", "alloy-serde", - "alloy-trie", "bytes", "foundry-common", "foundry-evm", "op-alloy-consensus", - "rand 0.8.5", + "op-revm", + "rand 0.9.1", "revm", "serde", "serde_json", @@ -1081,7 +1121,7 @@ dependencies = [ [[package]] name = "anvil-rpc" -version = "1.1.0" +version = "1.2.3" dependencies = [ "serde", "serde_json", @@ -1089,7 +1129,7 @@ dependencies = [ [[package]] name = "anvil-server" -version = "1.1.0" +version = "1.2.3" dependencies = [ "anvil-rpc", "async-trait", @@ -1133,6 +1173,51 @@ dependencies = [ "yansi", ] +[[package]] +name = "ark-bls12-381" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3df4dcc01ff89867cd86b0da835f23c3f02738353aaee7dde7495af71363b8d5" +dependencies = [ + "ark-ec", + "ark-ff 0.5.0", + "ark-serialize 0.5.0", + "ark-std 0.5.0", +] + +[[package]] +name = "ark-bn254" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d69eab57e8d2663efa5c63135b2af4f396d66424f88954c21104125ab6b3e6bc" +dependencies = [ + "ark-ec", + "ark-ff 0.5.0", + "ark-r1cs-std", + "ark-std 0.5.0", +] + +[[package]] +name = "ark-ec" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43d68f2d516162846c1238e755a7c4d131b892b70cc70c471a8e3ca3ed818fce" +dependencies = [ + "ahash", + "ark-ff 0.5.0", + "ark-poly", + "ark-serialize 0.5.0", + "ark-std 0.5.0", + "educe", + "fnv", + "hashbrown 0.15.4", + "itertools 0.13.0", + "num-bigint", + "num-integer", + "num-traits", + "zeroize", +] + [[package]] name = "ark-ff" version = "0.3.0" @@ -1171,6 +1256,26 @@ dependencies = [ "zeroize", ] +[[package]] +name = "ark-ff" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a177aba0ed1e0fbb62aa9f6d0502e9b46dad8c2eab04c14258a1212d2557ea70" +dependencies = [ + "ark-ff-asm 0.5.0", + "ark-ff-macros 0.5.0", + "ark-serialize 0.5.0", + "ark-std 0.5.0", + "arrayvec", + "digest 0.10.7", + "educe", + "itertools 0.13.0", + "num-bigint", + "num-traits", + "paste", + "zeroize", +] + [[package]] name = "ark-ff-asm" version = "0.3.0" @@ -1191,6 +1296,16 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "ark-ff-asm" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62945a2f7e6de02a31fe400aa489f0e0f5b2502e69f95f853adb82a96c7a6b60" +dependencies = [ + "quote", + "syn 2.0.103", +] + [[package]] name = "ark-ff-macros" version = "0.3.0" @@ -1217,31 +1332,112 @@ dependencies = [ ] [[package]] -name = "ark-serialize" -version = "0.3.0" +name = "ark-ff-macros" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d6c2b318ee6e10f8c2853e73a83adc0ccb88995aa978d8a3408d492ab2ee671" +checksum = "09be120733ee33f7693ceaa202ca41accd5653b779563608f1234f78ae07c4b3" dependencies = [ - "ark-std 0.3.0", - "digest 0.9.0", + "num-bigint", + "num-traits", + "proc-macro2", + "quote", + "syn 2.0.103", ] [[package]] -name = "ark-serialize" -version = "0.4.2" +name = "ark-poly" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adb7b85a02b83d2f22f89bd5cac66c9c89474240cb6207cb1efc16d098e822a5" +checksum = "579305839da207f02b89cd1679e50e67b4331e2f9294a57693e5051b7703fe27" dependencies = [ - "ark-std 0.4.0", - "digest 0.10.7", - "num-bigint", + "ahash", + "ark-ff 0.5.0", + "ark-serialize 0.5.0", + "ark-std 0.5.0", + "educe", + "fnv", + "hashbrown 0.15.4", ] [[package]] -name = "ark-std" -version = "0.3.0" +name = "ark-r1cs-std" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1df2c09229cbc5a028b1d70e00fdb2acee28b1055dfb5ca73eea49c5a25c4e7c" +checksum = "941551ef1df4c7a401de7068758db6503598e6f01850bdb2cfdb614a1f9dbea1" +dependencies = [ + "ark-ec", + "ark-ff 0.5.0", + "ark-relations", + "ark-std 0.5.0", + "educe", + "num-bigint", + "num-integer", + "num-traits", + "tracing", +] + +[[package]] +name = "ark-relations" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec46ddc93e7af44bcab5230937635b06fb5744464dd6a7e7b083e80ebd274384" +dependencies = [ + "ark-ff 0.5.0", + "ark-std 0.5.0", + "tracing", + "tracing-subscriber 0.2.25", +] + +[[package]] +name = "ark-serialize" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d6c2b318ee6e10f8c2853e73a83adc0ccb88995aa978d8a3408d492ab2ee671" +dependencies = [ + "ark-std 0.3.0", + "digest 0.9.0", +] + +[[package]] +name = "ark-serialize" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adb7b85a02b83d2f22f89bd5cac66c9c89474240cb6207cb1efc16d098e822a5" +dependencies = [ + "ark-std 0.4.0", + "digest 0.10.7", + "num-bigint", +] + +[[package]] +name = "ark-serialize" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f4d068aaf107ebcd7dfb52bc748f8030e0fc930ac8e360146ca54c1203088f7" +dependencies = [ + "ark-serialize-derive", + "ark-std 0.5.0", + "arrayvec", + "digest 0.10.7", + "num-bigint", +] + +[[package]] +name = "ark-serialize-derive" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "213888f660fddcca0d257e88e54ac05bca01885f258ccdf695bafd77031bb69d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.103", +] + +[[package]] +name = "ark-std" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1df2c09229cbc5a028b1d70e00fdb2acee28b1055dfb5ca73eea49c5a25c4e7c" dependencies = [ "num-traits", "rand 0.8.5", @@ -1257,6 +1453,22 @@ dependencies = [ "rand 0.8.5", ] +[[package]] +name = "ark-std" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "246a225cc6131e9ee4f24619af0f19d67761fff15d7ccc22e42b80846e69449a" +dependencies = [ + "num-traits", + "rand 0.8.5", +] + +[[package]] +name = "arrayref" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" + [[package]] name = "arrayvec" version = "0.7.6" @@ -1268,18 +1480,18 @@ dependencies = [ [[package]] name = "ascii-canvas" -version = "3.0.0" +version = "4.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8824ecca2e851cec16968d54a01dd372ef8f95b244fb84b84e70128be347c3c6" +checksum = "ef1e3e699d84ab1b0911a1010c5c106aa34ae89aeac103be5ce0c3859db1e891" dependencies = [ "term", ] [[package]] name = "async-compression" -version = "0.4.23" +version = "0.4.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b37fc50485c4f3f736a4fb14199f6d5f5ba008d7f28fe710306c92780f004c07" +checksum = "40f6024f3f856663b45fd0c9b6f2024034a702f453549449e0d84a305900dad4" dependencies = [ "flate2", "futures-core", @@ -1297,17 +1509,6 @@ dependencies = [ "event-listener", ] -[[package]] -name = "async-recursion" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.101", -] - [[package]] name = "async-stream" version = "0.3.6" @@ -1327,7 +1528,7 @@ checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -1338,7 +1539,7 @@ checksum = "e539d3fca749fcee5236ab05e93a52867dd549cc157c8cb7f99595f3cedffdb5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -1391,20 +1592,20 @@ checksum = "ffdcb70bdbc4d478427380519163274ac86e52916e10f0a8889adf0f96d3fee7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] name = "autocfg" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "aws-config" -version = "1.6.2" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6fcc63c9860579e4cb396239570e979376e70aab79e496621748a09913f8b36" +checksum = "455e9fb7743c6f6267eb2830ccc08686fbb3d13c9a689369562fd4d4ef9ea462" dependencies = [ "aws-credential-types", "aws-runtime", @@ -1444,9 +1645,9 @@ dependencies = [ [[package]] name = "aws-lc-rs" -version = "1.13.0" +version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19b756939cb2f8dc900aa6dcd505e6e2428e9cae7ff7b028c49e3946efa70878" +checksum = "93fcc8f365936c834db5514fc45aee5b1202d677e6b40e48468aaaa8183ca8c7" dependencies = [ "aws-lc-sys", "zeroize", @@ -1454,9 +1655,9 @@ dependencies = [ [[package]] name = "aws-lc-sys" -version = "0.28.2" +version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa9b6986f250236c27e5a204062434a773a13243d2ffc2955f37bdba4c5c6a1" +checksum = "61b1d86e7705efe1be1b569bab41d4fa1e14e220b60a160f78de2db687add079" dependencies = [ "bindgen", "cc", @@ -1467,9 +1668,9 @@ dependencies = [ [[package]] name = "aws-runtime" -version = "1.5.7" +version = "1.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c4063282c69991e57faab9e5cb21ae557e59f5b0fb285c196335243df8dc25c" +checksum = "4f6c68419d8ba16d9a7463671593c54f81ba58cab466e9b759418da606dcc2e2" dependencies = [ "aws-credential-types", "aws-sigv4", @@ -1486,14 +1687,14 @@ dependencies = [ "percent-encoding", "pin-project-lite", "tracing", - "uuid 1.16.0", + "uuid 1.17.0", ] [[package]] name = "aws-sdk-kms" -version = "1.66.0" +version = "1.76.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "655097cd83ab1f15575890943135192560f77097413c6dd1733fdbdc453e81ac" +checksum = "8565497721d9f18fa29a68bc5d8225b39e1cc7399d7fc6f1ad803ca934341804" dependencies = [ "aws-credential-types", "aws-runtime", @@ -1507,16 +1708,15 @@ dependencies = [ "bytes", "fastrand", "http 0.2.12", - "once_cell", "regex-lite", "tracing", ] [[package]] name = "aws-sdk-sso" -version = "1.65.0" +version = "1.73.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8efec445fb78df585327094fcef4cad895b154b58711e504db7a93c41aa27151" +checksum = "b2ac1674cba7872061a29baaf02209fefe499ff034dfd91bd4cc59e4d7741489" dependencies = [ "aws-credential-types", "aws-runtime", @@ -1530,16 +1730,15 @@ dependencies = [ "bytes", "fastrand", "http 0.2.12", - "once_cell", "regex-lite", "tracing", ] [[package]] name = "aws-sdk-ssooidc" -version = "1.66.0" +version = "1.74.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e49cca619c10e7b002dc8e66928ceed66ab7f56c1a3be86c5437bf2d8d89bba" +checksum = "3a6a22f077f5fd3e3c0270d4e1a110346cddf6769e9433eb9e6daceb4ca3b149" dependencies = [ "aws-credential-types", "aws-runtime", @@ -1553,16 +1752,15 @@ dependencies = [ "bytes", "fastrand", "http 0.2.12", - "once_cell", "regex-lite", "tracing", ] [[package]] name = "aws-sdk-sts" -version = "1.66.0" +version = "1.75.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7420479eac0a53f776cc8f0d493841ffe58ad9d9783f3947be7265784471b47a" +checksum = "e3258fa707f2f585ee3049d9550954b959002abd59176975150a01d5cf38ae3f" dependencies = [ "aws-credential-types", "aws-runtime", @@ -1577,16 +1775,15 @@ dependencies = [ "aws-types", "fastrand", "http 0.2.12", - "once_cell", "regex-lite", "tracing", ] [[package]] name = "aws-sigv4" -version = "1.3.1" +version = "1.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3503af839bd8751d0bdc5a46b9cac93a003a353e635b0c12cf2376b5b53e41ea" +checksum = "ddfb9021f581b71870a17eac25b52335b82211cdc092e02b6876b2bcefa61666" dependencies = [ "aws-credential-types", "aws-smithy-http", @@ -1599,7 +1796,7 @@ dependencies = [ "http 0.2.12", "http 1.3.1", "percent-encoding", - "sha2", + "sha2 0.10.9", "time", "tracing", ] @@ -1637,9 +1834,9 @@ dependencies = [ [[package]] name = "aws-smithy-http-client" -version = "1.0.1" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8aff1159006441d02e57204bf57a1b890ba68bedb6904ffd2873c1c4c11c546b" +checksum = "7f491388e741b7ca73b24130ff464c1478acc34d5b331b7dd0a2ee4643595a15" dependencies = [ "aws-smithy-async", "aws-smithy-runtime-api", @@ -1654,15 +1851,15 @@ dependencies = [ "rustls-native-certs", "rustls-pki-types", "tokio", - "tower 0.5.2", + "tower", "tracing", ] [[package]] name = "aws-smithy-json" -version = "0.61.3" +version = "0.61.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92144e45819cae7dc62af23eac5a038a58aa544432d2102609654376a900bd07" +checksum = "a16e040799d29c17412943bdbf488fd75db04112d0c0d4b9290bacf5ae0014b9" dependencies = [ "aws-smithy-types", ] @@ -1712,9 +1909,9 @@ dependencies = [ [[package]] name = "aws-smithy-runtime-api" -version = "1.8.0" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1e5d9e3a80a18afa109391fb5ad09c3daf887b516c6fd805a157c6ea7994a57" +checksum = "bd8531b6d8882fd8f48f82a9754e682e29dd44cff27154af51fa3eb730f59efb" dependencies = [ "aws-smithy-async", "aws-smithy-types", @@ -1729,9 +1926,9 @@ dependencies = [ [[package]] name = "aws-smithy-types" -version = "1.3.1" +version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40076bd09fadbc12d5e026ae080d0930defa606856186e31d83ccc6a255eeaf3" +checksum = "d498595448e43de7f4296b7b7a18a8a02c61ec9349128c80a368f7c3b4ab11a8" dependencies = [ "base64-simd", "bytes", @@ -1752,9 +1949,9 @@ dependencies = [ [[package]] name = "aws-smithy-xml" -version = "0.60.9" +version = "0.60.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab0b0166827aa700d3dc519f72f8b3a91c35d0b8d042dc5d643a91e6f80648fc" +checksum = "3db87b96cb1b16c024980f133968d52882ca0daaee3a086c6decc500f6c99728" dependencies = [ "xmlparser", ] @@ -1775,14 +1972,14 @@ dependencies = [ [[package]] name = "axum" -version = "0.7.9" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edca88bc138befd0323b20752846e6587272d3b03b0343c8ea28a6f819e6e71f" +checksum = "021e862c184ae977658b36c4500f7feac3221ca5da43e3f25bd04ab6c79a29b5" dependencies = [ - "async-trait", "axum-core", "base64 0.22.1", "bytes", + "form_urlencoded", "futures-util", "http 1.3.1", "http-body 1.0.1", @@ -1803,8 +2000,8 @@ dependencies = [ "sha1", "sync_wrapper", "tokio", - "tokio-tungstenite 0.24.0", - "tower 0.5.2", + "tokio-tungstenite", + "tower", "tower-layer", "tower-service", "tracing", @@ -1812,13 +2009,12 @@ dependencies = [ [[package]] name = "axum-core" -version = "0.4.5" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09f2bd6146b97ae3359fa0cc6d6b376d9539582c7b4220f041a33ec24c226199" +checksum = "68464cd0412f486726fb3373129ef5d2993f90c34bc2bc1c1e9943b2f4fc7ca6" dependencies = [ - "async-trait", "bytes", - "futures-util", + "futures-core", "http 1.3.1", "http-body 1.0.1", "http-body-util", @@ -1833,17 +2029,17 @@ dependencies = [ [[package]] name = "backtrace" -version = "0.3.71" +version = "0.3.75" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26b05800d2e817c8b3b4b54abd461726265fa9789ae34330622f2db9ee696f9d" +checksum = "6806a6321ec58106fea15becdad98371e28d92ccbc7c8f1b3b6dd724fe8f1002" dependencies = [ "addr2line", - "cc", "cfg-if", "libc", - "miniz_oxide 0.7.4", + "miniz_oxide", "object", "rustc-demangle", + "windows-targets 0.52.6", ] [[package]] @@ -1876,9 +2072,9 @@ dependencies = [ [[package]] name = "base64ct" -version = "1.7.3" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89e25b6adfb930f02d1981565a6e5d9c547ac15a96606256d3b59040e5cd4ca3" +checksum = "55248b47b0caf0546f7988906588779981c43bb1bc9d0c44087278f80cdb44ba" [[package]] name = "bech32" @@ -1898,10 +2094,10 @@ version = "0.69.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "271383c67ccabffb7381723dea0672a673f292304fcb45c01cc648c7a8d58088" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "cexpr", "clang-sys", - "itertools 0.11.0", + "itertools 0.12.1", "lazy_static", "lazycell", "log", @@ -1911,39 +2107,40 @@ dependencies = [ "regex", "rustc-hash 1.1.0", "shlex", - "syn 2.0.101", + "syn 2.0.103", "which 4.4.2", ] [[package]] name = "bit-set" -version = "0.5.3" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1" +checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3" dependencies = [ - "bit-vec 0.6.3", + "bit-vec", ] [[package]] -name = "bit-set" +name = "bit-vec" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3" -dependencies = [ - "bit-vec 0.8.0", -] +checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" [[package]] -name = "bit-vec" -version = "0.6.3" +name = "bitcoin-io" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" +checksum = "0b47c4ab7a93edb0c7198c5535ed9b52b63095f4e9b45279c6736cec4b856baf" [[package]] -name = "bit-vec" -version = "0.8.0" +name = "bitcoin_hashes" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" +checksum = "bb18c03d0db0247e147a21a6faafd5a7eb851c743db062de72018b6b7e8e4d16" +dependencies = [ + "bitcoin-io", + "hex-conservative", +] [[package]] name = "bitflags" @@ -1953,11 +2150,10 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.9.0" +version = "2.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" +checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" dependencies = [ - "arbitrary", "serde", ] @@ -1974,6 +2170,15 @@ dependencies = [ "wyz", ] +[[package]] +name = "block-buffer" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" +dependencies = [ + "generic-array", +] + [[package]] name = "block-buffer" version = "0.10.4" @@ -1985,9 +2190,9 @@ dependencies = [ [[package]] name = "blst" -version = "0.3.14" +version = "0.3.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47c79a94619fade3c0b887670333513a67ac28a6a7e653eb260bf0d4103db38d" +checksum = "4fd49896f12ac9b6dcd7a5998466b9b58263a695a3dd1ecc1aaca2e12a90b080" dependencies = [ "cc", "glob", @@ -1997,9 +2202,9 @@ dependencies = [ [[package]] name = "bon" -version = "3.6.3" +version = "3.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ced38439e7a86a4761f7f7d5ded5ff009135939ecb464a24452eaa4c1696af7d" +checksum = "f61138465baf186c63e8d9b6b613b508cd832cba4ce93cf37ce5f096f91ac1a6" dependencies = [ "bon-macros", "rustversion", @@ -2007,9 +2212,9 @@ dependencies = [ [[package]] name = "bon-macros" -version = "3.6.3" +version = "3.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ce61d2d3844c6b8d31b2353d9f66cf5e632b3e9549583fe3cac2f4f6136725e" +checksum = "40d1dad34aa19bf02295382f08d9bc40651585bd497266831d40ee6296fb49ca" dependencies = [ "darling", "ident_case", @@ -2017,16 +2222,22 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.101", + "syn 2.0.103", ] +[[package]] +name = "boxcar" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26c4925bc979b677330a8c7fe7a8c94af2dbb4a2d37b4a20a80d884400f46baa" + [[package]] name = "bs58" version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bf88ba1141d185c399bee5288d850d63b8369520c1eafc32a0430b5b6c287bf4" dependencies = [ - "sha2", + "sha2 0.10.9", "tinyvec", ] @@ -2041,17 +2252,11 @@ dependencies = [ "serde", ] -[[package]] -name = "build_const" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4ae4235e6dac0694637c763029ecea1a2ec9e4e06ec2729bd21ba4d9c863eb7" - [[package]] name = "bumpalo" -version = "3.17.0" +version = "3.18.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" +checksum = "793db76d6187cd04dff33004d8e6c9cc4e05cd330500379d2394209271b4aeee" [[package]] name = "byte-slice-cast" @@ -2061,9 +2266,9 @@ checksum = "7575182f7272186991736b70173b0ea045398f984bf5ebbb3804736ce1330c9d" [[package]] name = "bytemuck" -version = "1.22.0" +version = "1.23.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6b1fc10dbac614ebc03540c9dbd60e83887fda27794998c6528f1782047d540" +checksum = "5c76a5792e44e4abe34d3abf15636779261d45a7450612059293d1d2cfc63422" [[package]] name = "byteorder" @@ -2111,9 +2316,9 @@ dependencies = [ [[package]] name = "c-kzg" -version = "1.0.3" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0307f72feab3300336fb803a57134159f6e20139af1357f36c54cb90d8e8928" +checksum = "7318cfa722931cb5fe0838b98d3ce5621e75f6a6408abc21721d80de9223f2e4" dependencies = [ "blst", "cc", @@ -2124,6 +2329,38 @@ dependencies = [ "serde", ] +[[package]] +name = "camino" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0da45bc31171d8d6960122e222a67740df867c1dd53b4d51caa297084c185cab" +dependencies = [ + "serde", +] + +[[package]] +name = "cargo-platform" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e35af189006b9c0f00a064685c727031e3ed2d8020f7ba284d78cc2671bd36ea" +dependencies = [ + "serde", +] + +[[package]] +name = "cargo_metadata" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d886547e41f740c616ae73108f6eb70afe6d940c7bc697cb30f13daec073037" +dependencies = [ + "camino", + "cargo-platform", + "semver 1.0.26", + "serde", + "serde_json", + "thiserror 1.0.69", +] + [[package]] name = "cassowary" version = "0.3.0" @@ -2132,16 +2369,18 @@ checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53" [[package]] name = "cast" -version = "1.1.0" +version = "1.2.3" dependencies = [ "alloy-chains", "alloy-consensus", "alloy-contract", "alloy-dyn-abi", - "alloy-json-abi 0.8.25", + "alloy-ens", + "alloy-hardforks", + "alloy-json-abi", "alloy-json-rpc", "alloy-network", - "alloy-primitives 0.8.25", + "alloy-primitives", "alloy-provider", "alloy-rlp", "alloy-rpc-types", @@ -2151,7 +2390,6 @@ dependencies = [ "alloy-sol-types", "alloy-transport", "anvil", - "aws-sdk-kms", "chrono", "clap", "clap_complete", @@ -2166,20 +2404,23 @@ dependencies = [ "foundry-compilers", "foundry-config", "foundry-evm", + "foundry-evm-core", "foundry-test-utils", "foundry-wallets", "futures", - "gcloud-sdk", "itertools 0.14.0", + "op-alloy-consensus", + "op-alloy-flz", "rand 0.8.5", + "rand 0.9.1", "rayon", "regex", + "revm", "rpassword", "semver 1.0.26", "serde", "serde_json", "tempfile", - "tikv-jemallocator", "tokio", "tracing", "yansi", @@ -2196,9 +2437,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.20" +version = "1.2.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04da6a0d40b948dfc4fa8f5bbf402b0fc1a64a28dbf7d12ffd683550f2c1b63a" +checksum = "d487aa071b5f64da6f19a3e848e3578944b726ee5a4854b82172f02aa876bfdc" dependencies = [ "jobserver", "libc", @@ -2216,9 +2457,9 @@ dependencies = [ [[package]] name = "cfg-if" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" [[package]] name = "cfg_aliases" @@ -2228,11 +2469,11 @@ checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" [[package]] name = "chisel" -version = "1.1.0" +version = "1.2.3" dependencies = [ "alloy-dyn-abi", - "alloy-json-abi 0.8.25", - "alloy-primitives 0.8.25", + "alloy-json-abi", + "alloy-primitives", "clap", "dirs", "eyre", @@ -2242,6 +2483,7 @@ dependencies = [ "foundry-compilers", "foundry-config", "foundry-evm", + "foundry-solang-parser", "regex", "reqwest", "revm", @@ -2249,23 +2491,21 @@ dependencies = [ "semver 1.0.26", "serde", "serde_json", - "solang-parser", "solar-parse", "strum 0.27.1", - "tikv-jemallocator", "time", "tokio", "tracing", - "tracing-subscriber", + "tracing-subscriber 0.3.19", "walkdir", "yansi", ] [[package]] name = "chrono" -version = "0.4.40" +version = "0.4.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a7964611d71df112cb1730f2ee67324fcf4d0fc6606acbbe9bfe06df124637c" +checksum = "c469d952047f47f91b68d1cba3f10d63c11d73e4636f24f08daf0278abf01c4d" dependencies = [ "android-tzdata", "iana-time-zone", @@ -2324,9 +2564,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.37" +version = "4.5.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eccb054f56cbd38340b380d4a8e69ef1f02f1af43db2f0cc817a4774d80ae071" +checksum = "40b6887a1d8685cebccf115538db5c0efe625ccac9696ad45c409d96566e910f" dependencies = [ "clap_builder", "clap_derive", @@ -2334,9 +2574,9 @@ dependencies = [ [[package]] name = "clap-verbosity-flag" -version = "3.0.2" +version = "3.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2678fade3b77aa3a8ff3aae87e9c008d3fb00473a41c71fbf74e91c8c7b37e84" +checksum = "eeab6a5cdfc795a05538422012f20a5496f050223c91be4e5420bfd13c641fb1" dependencies = [ "clap", "log", @@ -2344,9 +2584,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.37" +version = "4.5.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efd9466fac8543255d3b1fcad4762c5e116ffe808c8a3043d4263cd4fd4862a2" +checksum = "e0c66c08ce9f0c698cbce5c0279d0bb6ac936d8674174fe48f736533b964f59e" dependencies = [ "anstream", "anstyle", @@ -2359,9 +2599,9 @@ dependencies = [ [[package]] name = "clap_complete" -version = "4.5.47" +version = "4.5.54" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c06f5378ea264ad4f82bbc826628b5aad714a75abf6ece087e923010eb937fb6" +checksum = "aad5b1b4de04fead402672b48897030eec1f3bfe1550776322f59f6d6e6a5677" dependencies = [ "clap", ] @@ -2378,21 +2618,21 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.32" +version = "4.5.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09176aae279615badda0765c0c0b3f6ed53f4709118af73cf4655d85d1530cd7" +checksum = "d2c7947ae4cc3d851207c1adb5b5e260ff0cca11446b1d6d1423788e442257ce" dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] name = "clap_lex" -version = "0.7.4" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" +checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675" [[package]] name = "clearscreen" @@ -2451,7 +2691,7 @@ dependencies = [ "hmac", "k256", "serde", - "sha2", + "sha2 0.10.9", "thiserror 1.0.69", ] @@ -2467,7 +2707,7 @@ dependencies = [ "once_cell", "pbkdf2 0.12.2", "rand 0.8.5", - "sha2", + "sha2 0.10.9", "thiserror 1.0.69", ] @@ -2485,7 +2725,7 @@ dependencies = [ "generic-array", "ripemd", "serde", - "sha2", + "sha2 0.10.9", "sha3", "thiserror 1.0.69", ] @@ -2515,36 +2755,46 @@ dependencies = [ [[package]] name = "color-eyre" -version = "0.6.3" +version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55146f5e46f237f7423d74111267d4597b59b0dad0ffaf7303bce9945d843ad5" +checksum = "e5920befb47832a6d61ee3a3a846565cfa39b331331e68a3b1d1116630f2f26d" dependencies = [ "backtrace", "color-spantrace", "eyre", "indenter", "once_cell", - "owo-colors", + "owo-colors 4.2.1", "tracing-error", ] [[package]] name = "color-spantrace" -version = "0.2.1" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd6be1b2a7e382e2b98b43b2adcca6bb0e465af0bdd38123873ae61eb17a72c2" +checksum = "b8b88ea9df13354b55bc7234ebcce36e6ef896aca2e42a15de9e10edce01b427" dependencies = [ "once_cell", - "owo-colors", + "owo-colors 4.2.1", "tracing-core", "tracing-error", ] [[package]] name = "colorchoice" -version = "1.0.3" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" + +[[package]] +name = "colored" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" +checksum = "117725a109d387c937a1533ce01b450cbde6b88abceea8473c4d7a85853cda3c" +dependencies = [ + "lazy_static", + "windows-sys 0.52.0", +] [[package]] name = "comfy-table" @@ -2557,6 +2807,12 @@ dependencies = [ "unicode-width 0.2.0", ] +[[package]] +name = "comma" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55b672471b4e9f9e95499ea597ff64941a309b2cdbffcc46f2cc5e2d971fd335" + [[package]] name = "compact_str" version = "0.8.1" @@ -2605,9 +2861,9 @@ dependencies = [ [[package]] name = "const-hex" -version = "1.14.0" +version = "1.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b0485bab839b018a8f1723fc5391819fea5f8f0f32288ef8a735fd096b6160c" +checksum = "83e22e0ed40b96a48d3db274f72fd365bd78f67af39b6bbd47e8a15e1c6207ff" dependencies = [ "cfg-if", "cpufeatures", @@ -2642,6 +2898,12 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "constant_time_eq" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6" + [[package]] name = "convert_case" version = "0.7.1" @@ -2663,9 +2925,9 @@ dependencies = [ [[package]] name = "core-foundation" -version = "0.10.0" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b55271e5c8c478ad3f38ad24ef34923091e0548492a266d19b3c0b4d82574c63" +checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" dependencies = [ "core-foundation-sys", "libc", @@ -2688,9 +2950,9 @@ dependencies = [ [[package]] name = "crc" -version = "3.2.1" +version = "3.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69e6e4d7b33a94f0991c26729976b10ebde1d34c3ee82408fb536164fa10d636" +checksum = "9710d3b3739c2e349eb44fe848ad0b7c8cb1e42bd87ee49371df2f7acaf3e675" dependencies = [ "crc-catalog", ] @@ -2710,6 +2972,15 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "crossbeam-channel" +version = "0.5.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" +dependencies = [ + "crossbeam-utils", +] + [[package]] name = "crossbeam-deque" version = "0.8.6" @@ -2741,7 +3012,7 @@ version = "0.28.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "829d955a0bb380ef178a640b91779e3987da38c9aea133b20614cfed8cdea9c6" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "crossterm_winapi", "mio", "parking_lot", @@ -2808,7 +3079,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13b588ba4ac1a99f7f2964d24b3d896ddc6bf847ee3855dbd4366f058cfcd331" dependencies = [ "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -2822,11 +3093,11 @@ dependencies = [ [[package]] name = "ctrlc" -version = "3.4.6" +version = "3.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "697b5419f348fd5ae2478e8018cb016c00a5881c7f46c717de98ffd135a5651c" +checksum = "46f93780a459b7d656ef7f071fe699c4d3d2cb201c4b24d085b6ddc505276e73" dependencies = [ - "nix 0.29.0", + "nix 0.30.1", "windows-sys 0.59.0", ] @@ -2851,7 +3122,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -2862,7 +3133,7 @@ checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" dependencies = [ "darling_core", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -2896,6 +3167,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "deflate64" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da692b8d1080ea3045efaab14434d40468c3d8657e42abddfffca87b428f4c1b" + [[package]] name = "der" version = "0.7.10" @@ -2928,6 +3205,17 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "derive-where" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "510c292c8cf384b1a340b816a9a6cf2599eb8f566a44949024af88418000c50b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.103", +] + [[package]] name = "derive_arbitrary" version = "1.4.1" @@ -2936,7 +3224,7 @@ checksum = "30542c1ad912e0e3d22a1935c290e12e8a29d704a420177a31faad4a601a0800" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -2957,7 +3245,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -2967,7 +3255,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab63b0e2bf4d5928aff72e83a7dace85d7bba5fe12dcc3c5a572d78caffd3f3c" dependencies = [ "derive_builder_core", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -2996,7 +3284,7 @@ checksum = "cb7330aeadfbe296029522e6c40f315320aba36fc43a5b3632f3795348f3bd22" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", "unicode-xid", ] @@ -3009,7 +3297,7 @@ dependencies = [ "convert_case", "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", "unicode-xid", ] @@ -3041,7 +3329,7 @@ version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ - "block-buffer", + "block-buffer 0.10.4", "const-oid", "crypto-common", "subtle", @@ -3056,16 +3344,6 @@ dependencies = [ "dirs-sys", ] -[[package]] -name = "dirs-next" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1" -dependencies = [ - "cfg-if", - "dirs-sys-next", -] - [[package]] name = "dirs-sys" version = "0.5.0" @@ -3074,19 +3352,19 @@ checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab" dependencies = [ "libc", "option-ext", - "redox_users 0.5.0", - "windows-sys 0.59.0", + "redox_users", + "windows-sys 0.60.2", ] [[package]] -name = "dirs-sys-next" -version = "0.1.2" +name = "displaydoc" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ - "libc", - "redox_users 0.4.6", - "winapi", + "proc-macro2", + "quote", + "syn 2.0.103", ] [[package]] @@ -3149,6 +3427,18 @@ dependencies = [ "spki", ] +[[package]] +name = "educe" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d7bc049e1bd8cdeb31b68bbd586a9464ecf9f3944af3958a7a9d0f8b9799417" +dependencies = [ + "enum-ordinalize", + "proc-macro2", + "quote", + "syn 2.0.103", +] + [[package]] name = "either" version = "1.15.0" @@ -3235,14 +3525,23 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c34f04666d835ff5d62e058c3995147c06f42fe86ff053337632bca83e42702d" [[package]] -name = "enumn" -version = "0.1.14" +name = "enum-ordinalize" +version = "4.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fea0dcfa4e54eeb516fe454635a95753ddd39acda650ce703031c6973e315dd5" +dependencies = [ + "enum-ordinalize-derive", +] + +[[package]] +name = "enum-ordinalize-derive" +version = "4.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f9ed6b3789237c8a0c1c505af1c7eb2c560df6186f01b098c3a1064ea532f38" +checksum = "0d28318a75d4aead5c4db25382e8ef717932d0346600cacae6357eb5941bc5ff" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -3292,9 +3591,9 @@ dependencies = [ [[package]] name = "errno" -version = "0.3.11" +version = "0.3.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "976dd42dc7e85965fe702eb8164f21f450704bdde31faefd6471dba214cb594e" +checksum = "cea14ef9355e3beab063703aa9dab15afd25f0667c341310c1e5274bb1d0da18" dependencies = [ "libc", "windows-sys 0.52.0", @@ -3302,9 +3601,9 @@ dependencies = [ [[package]] name = "error-code" -version = "3.3.1" +version = "3.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5d9305ccc6942a704f4335694ecd3de2ea531b114ac2d51f5f843750787a92f" +checksum = "dea2df4cf52843e0452895c455a1a2cfbb842a1e7329671acf418fdc53ed4c59" [[package]] name = "eth-keystore" @@ -3322,7 +3621,7 @@ dependencies = [ "scrypt", "serde", "serde_json", - "sha2", + "sha2 0.10.9", "sha3", "thiserror 1.0.69", "uuid 0.8.2", @@ -3351,13 +3650,12 @@ dependencies = [ [[package]] name = "evmole" -version = "0.7.2" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78329cbf3c326a3ce2694003976c019fe5f407682b1fdc76e89e463826ea511a" +checksum = "c29ecc930ee2ed03083436c2ddd7e5292c3c3bcda65f6a37369502d578a853f1" dependencies = [ - "ahash", "alloy-dyn-abi", - "alloy-primitives 0.8.25", + "alloy-primitives", "indexmap 2.9.0", ] @@ -3371,15 +3669,6 @@ dependencies = [ "once_cell", ] -[[package]] -name = "faster-hex" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2a2b11eda1d40935b26cf18f6833c526845ae8c41e58d09af6adeb6f0269183" -dependencies = [ - "serde", -] - [[package]] name = "fastrand" version = "2.3.0" @@ -3415,7 +3704,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ce92ff622d6dadf7349484f42c93271a0d49b7cc4d466a936405bacbe10aa78" dependencies = [ "cfg-if", - "rustix 1.0.5", + "rustix 1.0.7", "windows-sys 0.52.0", ] @@ -3479,18 +3768,18 @@ dependencies = [ [[package]] name = "fixedbitset" -version = "0.4.2" +version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" +checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99" [[package]] name = "flate2" -version = "1.1.1" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ced92e76e966ca2fd84c8f7aa01a4aea65b0eb6648d72f7c8f3e2764a67fece" +checksum = "4a3d7db9596fecd151c5f638c0ee5d5bd487b6e0ea232e5dc96d5250f6f94b1d" dependencies = [ "crc32fast", - "miniz_oxide 0.8.8", + "miniz_oxide", ] [[package]] @@ -3507,13 +3796,14 @@ checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" [[package]] name = "forge" -version = "1.1.0" +version = "1.2.3" dependencies = [ "alloy-chains", "alloy-dyn-abi", - "alloy-json-abi 0.8.25", + "alloy-hardforks", + "alloy-json-abi", "alloy-network", - "alloy-primitives 0.8.25", + "alloy-primitives", "alloy-provider", "alloy-rpc-types", "alloy-serde", @@ -3533,6 +3823,7 @@ dependencies = [ "eyre", "forge-doc", "forge-fmt", + "forge-lint", "forge-script", "forge-script-sequence", "forge-sol-macro-gen", @@ -3544,7 +3835,9 @@ dependencies = [ "foundry-config", "foundry-debugger", "foundry-evm", + "foundry-evm-core", "foundry-linking", + "foundry-solang-parser", "foundry-test-utils", "foundry-wallets", "futures", @@ -3553,7 +3846,7 @@ dependencies = [ "inferno", "itertools 0.14.0", "mockall", - "opener", + "opener 0.7.2", "parking_lot", "paste", "path-slash", @@ -3562,21 +3855,20 @@ dependencies = [ "rayon", "regex", "reqwest", + "revm", "semver 1.0.26", "serde", "serde_json", "similar", "similar-asserts", - "solang-parser", "solar-parse", + "solar-sema", "soldeer-commands", "strum 0.27.1", "svm-rs", "tempfile", "thiserror 2.0.12", - "tikv-jemallocator", "tokio", - "toml 0.8.21", "toml_edit", "tower-http", "tracing", @@ -3588,46 +3880,46 @@ dependencies = [ [[package]] name = "forge-doc" -version = "1.1.0" +version = "1.2.3" dependencies = [ - "alloy-primitives 0.8.25", + "alloy-primitives", "derive_more 2.0.1", "eyre", "forge-fmt", "foundry-common", "foundry-compilers", "foundry-config", + "foundry-solang-parser", "itertools 0.14.0", "mdbook", "rayon", "regex", "serde", "serde_json", - "solang-parser", "thiserror 2.0.12", - "toml 0.8.21", + "toml 0.8.23", "tracing", ] [[package]] name = "forge-fmt" -version = "1.1.0" +version = "1.2.3" dependencies = [ - "alloy-primitives 0.8.25", + "alloy-primitives", "ariadne", "foundry-config", + "foundry-solang-parser", "itertools 0.14.0", "similar-asserts", - "solang-parser", "thiserror 2.0.12", - "toml 0.8.21", + "toml 0.8.23", "tracing", - "tracing-subscriber", + "tracing-subscriber 0.3.19", ] [[package]] name = "forge-fmt-2" -version = "1.1.0" +version = "1.2.3" dependencies = [ "foundry-config", "itertools 0.14.0", @@ -3635,22 +3927,36 @@ dependencies = [ "similar-asserts", "snapbox", "solar-parse", - "toml 0.8.21", + "toml 0.8.23", "tracing", - "tracing-subscriber", + "tracing-subscriber 0.3.19", +] + +[[package]] +name = "forge-lint" +version = "1.2.3" +dependencies = [ + "foundry-compilers", + "foundry-config", + "heck", + "rayon", + "solar-ast", + "solar-interface", + "solar-parse", + "thiserror 2.0.12", ] [[package]] name = "forge-script" -version = "1.1.0" +version = "1.2.3" dependencies = [ "alloy-chains", "alloy-consensus", "alloy-dyn-abi", "alloy-eips", - "alloy-json-abi 0.8.25", + "alloy-json-abi", "alloy-network", - "alloy-primitives 0.8.25", + "alloy-primitives", "alloy-provider", "alloy-rpc-types", "alloy-serde", @@ -3661,6 +3967,7 @@ dependencies = [ "eyre", "forge-script-sequence", "forge-verify", + "foundry-block-explorers", "foundry-cheatcodes", "foundry-cli", "foundry-common", @@ -3686,10 +3993,10 @@ dependencies = [ [[package]] name = "forge-script-sequence" -version = "1.1.0" +version = "1.2.3" dependencies = [ "alloy-network", - "alloy-primitives 0.8.25", + "alloy-primitives", "eyre", "foundry-common", "foundry-compilers", @@ -3702,26 +4009,27 @@ dependencies = [ [[package]] name = "forge-sol-macro-gen" -version = "1.1.0" +version = "1.2.3" dependencies = [ "alloy-sol-macro-expander", "alloy-sol-macro-input", "eyre", "foundry-common", + "heck", "prettyplease", "proc-macro2", "quote", "serde_json", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] name = "forge-verify" -version = "1.1.0" +version = "1.2.3" dependencies = [ "alloy-dyn-abi", - "alloy-json-abi 0.8.25", - "alloy-primitives 0.8.25", + "alloy-json-abi", + "alloy-primitives", "alloy-provider", "alloy-rpc-types", "async-trait", @@ -3733,12 +4041,13 @@ dependencies = [ "foundry-compilers", "foundry-config", "foundry-evm", + "foundry-evm-core", "foundry-test-utils", "futures", "itertools 0.14.0", "regex", "reqwest", - "revm-primitives", + "revm", "semver 1.0.26", "serde", "serde_json", @@ -3759,13 +4068,13 @@ dependencies = [ [[package]] name = "foundry-block-explorers" -version = "0.13.1" +version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "001678abc9895502532c8c4a1a225079c580655fc82a194e78b06dcf99f49b8c" +checksum = "7dd57f39a860475780c0b001167b2bb9039e78d8d09323c6949897f5351ffae6" dependencies = [ "alloy-chains", - "alloy-json-abi 0.8.25", - "alloy-primitives 0.8.25", + "alloy-json-abi", + "alloy-primitives", "foundry-compilers", "reqwest", "semver 1.0.26", @@ -3777,15 +4086,17 @@ dependencies = [ [[package]] name = "foundry-cheatcodes" -version = "1.1.0" +version = "1.2.3" dependencies = [ "alloy-chains", "alloy-consensus", "alloy-dyn-abi", + "alloy-ens", + "alloy-evm", "alloy-genesis", - "alloy-json-abi 0.8.25", + "alloy-json-abi", "alloy-network", - "alloy-primitives 0.8.25", + "alloy-primitives", "alloy-provider", "alloy-rlp", "alloy-rpc-types", @@ -3811,45 +4122,49 @@ dependencies = [ "p256", "parking_lot", "proptest", - "rand 0.8.5", + "rand 0.9.1", "revm", "revm-inspectors", "semver 1.0.26", "serde", "serde_json", "thiserror 2.0.12", - "toml 0.8.21", + "toml 0.8.23", "tracing", "walkdir", ] [[package]] name = "foundry-cheatcodes-spec" -version = "1.1.0" +version = "1.2.3" dependencies = [ "alloy-sol-types", "foundry-macros", - "schemars", + "schemars 0.8.22", "serde", "serde_json", ] [[package]] name = "foundry-cli" -version = "1.1.0" +version = "1.2.3" dependencies = [ "alloy-chains", "alloy-dyn-abi", "alloy-eips", - "alloy-json-abi 0.8.25", - "alloy-primitives 0.8.25", + "alloy-ens", + "alloy-json-abi", + "alloy-primitives", "alloy-provider", "alloy-rlp", + "cfg-if", "clap", "color-eyre", "dotenvy", + "dunce", "eyre", "forge-fmt", + "foundry-block-explorers", "foundry-common", "foundry-compilers", "foundry-config", @@ -3859,33 +4174,36 @@ dependencies = [ "futures", "indicatif", "itertools 0.14.0", + "mimalloc", "rayon", "regex", "rustls", "serde", "serde_json", + "solar-sema", "strsim", "strum 0.27.1", "tempfile", + "tikv-jemallocator", "tokio", "tracing", - "tracing-subscriber", + "tracing-subscriber 0.3.19", "tracing-tracy", + "tracy-client", "yansi", ] [[package]] name = "foundry-common" -version = "1.1.0" +version = "1.2.3" dependencies = [ "alloy-consensus", - "alloy-contract", "alloy-dyn-abi", "alloy-eips", - "alloy-json-abi 0.8.25", + "alloy-json-abi", "alloy-json-rpc", "alloy-network", - "alloy-primitives 0.8.25", + "alloy-primitives", "alloy-provider", "alloy-pubsub", "alloy-rpc-client", @@ -3898,7 +4216,6 @@ dependencies = [ "alloy-transport-ws", "anstream", "anstyle", - "async-trait", "axum", "chrono", "ciborium", @@ -3923,7 +4240,7 @@ dependencies = [ "terminal_size", "thiserror 2.0.12", "tokio", - "tower 0.5.2", + "tower", "tracing", "url", "vergen", @@ -3933,18 +4250,17 @@ dependencies = [ [[package]] name = "foundry-common-fmt" -version = "1.1.0" +version = "1.2.3" dependencies = [ "alloy-consensus", "alloy-dyn-abi", "alloy-network", - "alloy-primitives 0.8.25", + "alloy-primitives", "alloy-rpc-types", "alloy-serde", "chrono", - "comfy-table", "foundry-macros", - "revm-primitives", + "revm", "serde", "serde_json", "similar-asserts", @@ -3953,12 +4269,12 @@ dependencies = [ [[package]] name = "foundry-compilers" -version = "0.14.1" +version = "0.17.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94bb4155f53d4b05642a1398ad105dc04d44b368a7932b85f6ed012af48768b7" +checksum = "72acadbe10bbcf1d2765155808a4b79744061ffba3ce78680ac9c86bd2222802" dependencies = [ - "alloy-json-abi 0.8.25", - "alloy-primitives 0.8.25", + "alloy-json-abi", + "alloy-primitives", "auto_impl", "derive_more 1.0.0", "dirs", @@ -3975,7 +4291,7 @@ dependencies = [ "semver 1.0.26", "serde", "serde_json", - "sha2", + "sha2 0.10.9", "solar-parse", "solar-sema", "svm-rs", @@ -3984,15 +4300,15 @@ dependencies = [ "thiserror 2.0.12", "tokio", "tracing", - "winnow 0.7.7", + "winnow", "yansi", ] [[package]] name = "foundry-compilers-artifacts" -version = "0.14.1" +version = "0.17.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8239fe85061cdb35b8041ef84918e657099fe26f41b016ab8d2560ae6413183" +checksum = "7f1f4c29ce88d24d1bebc5708bd14104534b0edbc9ddf2e76037bdb29a51c1c9" dependencies = [ "foundry-compilers-artifacts-solc", "foundry-compilers-artifacts-vyper", @@ -4000,12 +4316,12 @@ dependencies = [ [[package]] name = "foundry-compilers-artifacts-solc" -version = "0.14.1" +version = "0.17.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99d5c80fda7c4fde0d2964b329b22d09718838da0c940e5df418f2c1db14fd24" +checksum = "03f145ddd61f912cd8dc9cb57acaca6d771dc51ef23908bb98ad9c5859430679" dependencies = [ - "alloy-json-abi 0.8.25", - "alloy-primitives 0.8.25", + "alloy-json-abi", + "alloy-primitives", "foundry-compilers-core", "futures-util", "path-slash", @@ -4013,7 +4329,6 @@ dependencies = [ "semver 1.0.26", "serde", "serde_json", - "serde_repr", "thiserror 2.0.12", "tokio", "tracing", @@ -4023,12 +4338,12 @@ dependencies = [ [[package]] name = "foundry-compilers-artifacts-vyper" -version = "0.14.1" +version = "0.17.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2eaf3cad3dd7bd9eae02736e98f55aaf00ee31fbc0a367613436c2fb01c43914" +checksum = "d553aebe632477cff64cfa20fa3dbd819d3a454a6dfd748fde9206915e3fe0d9" dependencies = [ - "alloy-json-abi 0.8.25", - "alloy-primitives 0.8.25", + "alloy-json-abi", + "alloy-primitives", "foundry-compilers-artifacts-solc", "foundry-compilers-core", "path-slash", @@ -4038,11 +4353,11 @@ dependencies = [ [[package]] name = "foundry-compilers-core" -version = "0.14.1" +version = "0.17.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ac5a1aef4083544309765a1a10c310dffde8c9b8bcfda79b7c2bcfde32f3be3" +checksum = "afbd404c3ebcba24d0cb4746f78908ac0d681e9afb16a638a15c13d8e324e848" dependencies = [ - "alloy-primitives 0.8.25", + "alloy-primitives", "cfg-if", "dunce", "fs_extra", @@ -4061,10 +4376,11 @@ dependencies = [ [[package]] name = "foundry-config" -version = "1.1.0" +version = "1.2.3" dependencies = [ "alloy-chains", - "alloy-primitives 0.8.25", + "alloy-primitives", + "clap", "dirs", "dunce", "eyre", @@ -4080,16 +4396,17 @@ dependencies = [ "path-slash", "regex", "reqwest", - "revm-primitives", + "revm", "semver 1.0.26", "serde", "serde_json", "similar-asserts", + "solar-interface", "solar-parse", "soldeer-core", "tempfile", "thiserror 2.0.12", - "toml 0.8.21", + "toml 0.8.23", "toml_edit", "tracing", "walkdir", @@ -4098,9 +4415,9 @@ dependencies = [ [[package]] name = "foundry-debugger" -version = "1.1.0" +version = "1.2.3" dependencies = [ - "alloy-primitives 0.8.25", + "alloy-primitives", "crossterm", "eyre", "foundry-common", @@ -4116,11 +4433,12 @@ dependencies = [ [[package]] name = "foundry-evm" -version = "1.1.0" +version = "1.2.3" dependencies = [ "alloy-dyn-abi", - "alloy-json-abi 0.8.25", - "alloy-primitives 0.8.25", + "alloy-evm", + "alloy-json-abi", + "alloy-primitives", "alloy-sol-types", "eyre", "foundry-cheatcodes", @@ -4143,9 +4461,9 @@ dependencies = [ [[package]] name = "foundry-evm-abi" -version = "1.1.0" +version = "1.2.3" dependencies = [ - "alloy-primitives 0.8.25", + "alloy-primitives", "alloy-sol-types", "derive_more 2.0.1", "foundry-common-fmt", @@ -4155,14 +4473,16 @@ dependencies = [ [[package]] name = "foundry-evm-core" -version = "1.1.0" +version = "1.2.3" dependencies = [ "alloy-consensus", "alloy-dyn-abi", + "alloy-evm", "alloy-genesis", - "alloy-json-abi 0.8.25", + "alloy-json-abi", "alloy-network", - "alloy-primitives 0.8.25", + "alloy-op-evm", + "alloy-primitives", "alloy-provider", "alloy-rpc-types", "alloy-sol-types", @@ -4176,6 +4496,7 @@ dependencies = [ "foundry-test-utils", "futures", "itertools 0.14.0", + "op-revm", "parking_lot", "revm", "revm-inspectors", @@ -4189,9 +4510,9 @@ dependencies = [ [[package]] name = "foundry-evm-coverage" -version = "1.1.0" +version = "1.2.3" dependencies = [ - "alloy-primitives 0.8.25", + "alloy-primitives", "eyre", "foundry-common", "foundry-compilers", @@ -4204,11 +4525,11 @@ dependencies = [ [[package]] name = "foundry-evm-fuzz" -version = "1.1.0" +version = "1.2.3" dependencies = [ "alloy-dyn-abi", - "alloy-json-abi 0.8.25", - "alloy-primitives 0.8.25", + "alloy-json-abi", + "alloy-primitives", "eyre", "foundry-common", "foundry-compilers", @@ -4219,7 +4540,7 @@ dependencies = [ "itertools 0.14.0", "parking_lot", "proptest", - "rand 0.8.5", + "rand 0.9.1", "revm", "serde", "thiserror 2.0.12", @@ -4228,11 +4549,11 @@ dependencies = [ [[package]] name = "foundry-evm-traces" -version = "1.1.0" +version = "1.2.3" dependencies = [ "alloy-dyn-abi", - "alloy-json-abi 0.8.25", - "alloy-primitives 0.8.25", + "alloy-json-abi", + "alloy-primitives", "alloy-sol-types", "eyre", "foundry-block-explorers", @@ -4256,12 +4577,12 @@ dependencies = [ [[package]] name = "foundry-fork-db" -version = "0.12.0" +version = "0.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba7beb856e73f59015823eb221a98b7c22b58bc4e7066c9c86774ebe74e61dd6" +checksum = "5e3ce9907d94f0371f17930a79ced2c2d9f09131da93f8678f21505ed43c1f39" dependencies = [ "alloy-consensus", - "alloy-primitives 0.8.25", + "alloy-primitives", "alloy-provider", "alloy-rpc-types", "eyre", @@ -4278,9 +4599,9 @@ dependencies = [ [[package]] name = "foundry-linking" -version = "1.1.0" +version = "1.2.3" dependencies = [ - "alloy-primitives 0.8.25", + "alloy-primitives", "foundry-compilers", "semver 1.0.26", "thiserror 2.0.12", @@ -4288,19 +4609,33 @@ dependencies = [ [[package]] name = "foundry-macros" -version = "1.1.0" +version = "1.2.3" dependencies = [ "proc-macro-error2", "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", +] + +[[package]] +name = "foundry-solang-parser" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9645e75b89f977423690f3b4bfd8d84825e5fdabd7803cbce6d4a2c4d54972b4" +dependencies = [ + "itertools 0.14.0", + "lalrpop", + "lalrpop-util", + "phf", + "thiserror 2.0.12", + "unicode-xid", ] [[package]] name = "foundry-test-utils" -version = "1.1.0" +version = "1.2.3" dependencies = [ - "alloy-primitives 0.8.25", + "alloy-primitives", "alloy-provider", "eyre", "fd-lock", @@ -4310,24 +4645,26 @@ dependencies = [ "foundry-config", "idna_adapter", "parking_lot", - "rand 0.8.5", + "rand 0.9.1", "regex", "serde_json", "snapbox", "tempfile", "tokio", "tracing", - "tracing-subscriber", + "tracing-subscriber 0.3.19", + "ui_test", + "zip-extract", ] [[package]] name = "foundry-wallets" -version = "1.1.0" +version = "1.2.3" dependencies = [ "alloy-consensus", "alloy-dyn-abi", "alloy-network", - "alloy-primitives 0.8.25", + "alloy-primitives", "alloy-signer", "alloy-signer-aws", "alloy-signer-gcp", @@ -4363,7 +4700,7 @@ version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8640e34b88f7652208ce9e88b1a37a2ae95227d84abec377ccd3c5cfeb141ed4" dependencies = [ - "rustix 1.0.5", + "rustix 1.0.7", "windows-sys 0.59.0", ] @@ -4454,7 +4791,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -4495,9 +4832,9 @@ checksum = "42012b0f064e01aa58b545fe3727f90f7dd4020f4a3ea735b50344965f5a57e9" [[package]] name = "gcloud-sdk" -version = "0.26.4" +version = "0.27.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8269d6c07cddc7c4f7d679da74fbffa43713a891e0ccfcb37eb02deb21620225" +checksum = "a3ec9c312db09dc0dac684dda2f18d76e9ce00effdd27fcaaa90fa811691cd6d" dependencies = [ "async-trait", "bytes", @@ -4514,7 +4851,7 @@ dependencies = [ "serde_json", "tokio", "tonic", - "tower 0.5.2", + "tower", "tower-layer", "tower-util", "tracing", @@ -4523,15 +4860,16 @@ dependencies = [ [[package]] name = "generator" -version = "0.8.4" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc6bd114ceda131d3b1d665eba35788690ad37f5916457286b32ab6fd3c438dd" +checksum = "d18470a76cb7f8ff746cf1f7470914f900252ec36bbc40b569d74b1258446827" dependencies = [ + "cc", "cfg-if", "libc", "log", "rustversion", - "windows 0.58.0", + "windows", ] [[package]] @@ -4554,15 +4892,15 @@ dependencies = [ "cfg-if", "js-sys", "libc", - "wasi 0.11.0+wasi-snapshot-preview1", + "wasi 0.11.1+wasi-snapshot-preview1", "wasm-bindgen", ] [[package]] name = "getrandom" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73fea8450eea4bac3940448fb7ae50d91f034f941199fcd9d909a5a07aa455f0" +checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" dependencies = [ "cfg-if", "js-sys", @@ -4574,256 +4912,9 @@ dependencies = [ [[package]] name = "gimli" -version = "0.28.1" +version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" - -[[package]] -name = "gix-actor" -version = "0.33.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20018a1a6332e065f1fcc8305c1c932c6b8c9985edea2284b3c79dc6fa3ee4b2" -dependencies = [ - "bstr", - "gix-date", - "gix-utils", - "itoa", - "thiserror 2.0.12", - "winnow 0.6.26", -] - -[[package]] -name = "gix-config" -version = "0.42.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6649b406ca1f99cb148959cf00468b231f07950f8ec438cc0903cda563606f19" -dependencies = [ - "bstr", - "gix-config-value", - "gix-features", - "gix-glob", - "gix-path", - "gix-ref", - "gix-sec", - "memchr", - "once_cell", - "smallvec", - "thiserror 2.0.12", - "unicode-bom", - "winnow 0.6.26", -] - -[[package]] -name = "gix-config-value" -version = "0.14.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8dc2c844c4cf141884678cabef736fd91dd73068b9146e6f004ba1a0457944b6" -dependencies = [ - "bitflags 2.9.0", - "bstr", - "gix-path", - "libc", - "thiserror 2.0.12", -] - -[[package]] -name = "gix-date" -version = "0.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "daa30058ec7d3511fbc229e4f9e696a35abd07ec5b82e635eff864a2726217e4" -dependencies = [ - "bstr", - "itoa", - "jiff", - "thiserror 2.0.12", -] - -[[package]] -name = "gix-features" -version = "0.39.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d85d673f2e022a340dba4713bed77ef2cf4cd737d2f3e0f159d45e0935fd81f" -dependencies = [ - "gix-hash", - "gix-trace", - "gix-utils", - "libc", - "prodash", - "sha1_smol", - "walkdir", -] - -[[package]] -name = "gix-fs" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b3d4fac505a621f97e5ce2c69fdc425742af00c0920363ca4074f0eb48b1db9" -dependencies = [ - "fastrand", - "gix-features", - "gix-utils", -] - -[[package]] -name = "gix-glob" -version = "0.17.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aaf69a6bec0a3581567484bf99a4003afcaf6c469fd4214352517ea355cf3435" -dependencies = [ - "bitflags 2.9.0", - "bstr", - "gix-features", - "gix-path", -] - -[[package]] -name = "gix-hash" -version = "0.15.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b5eccc17194ed0e67d49285e4853307e4147e95407f91c1c3e4a13ba9f4e4ce" -dependencies = [ - "faster-hex", - "thiserror 2.0.12", -] - -[[package]] -name = "gix-hashtable" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ef65b256631078ef733bc5530c4e6b1c2e7d5c2830b75d4e9034ab3997d18fe" -dependencies = [ - "gix-hash", - "hashbrown 0.14.5", - "parking_lot", -] - -[[package]] -name = "gix-lock" -version = "15.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cd3ab68a452db63d9f3ebdacb10f30dba1fa0d31ac64f4203d395ed1102d940" -dependencies = [ - "gix-tempfile", - "gix-utils", - "thiserror 2.0.12", -] - -[[package]] -name = "gix-object" -version = "0.46.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e42d58010183ef033f31088479b4eb92b44fe341b35b62d39eb8b185573d77ea" -dependencies = [ - "bstr", - "gix-actor", - "gix-date", - "gix-features", - "gix-hash", - "gix-hashtable", - "gix-path", - "gix-utils", - "gix-validate 0.9.4", - "itoa", - "smallvec", - "thiserror 2.0.12", - "winnow 0.6.26", -] - -[[package]] -name = "gix-path" -version = "0.10.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c091d2e887e02c3462f52252c5ea61150270c0f2657b642e8d0d6df56c16e642" -dependencies = [ - "bstr", - "gix-trace", - "gix-validate 0.10.0", - "home", - "once_cell", - "thiserror 2.0.12", -] - -[[package]] -name = "gix-ref" -version = "0.49.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a91b61776c839d0f1b7114901179afb0947aa7f4d30793ca1c56d335dfef485f" -dependencies = [ - "gix-actor", - "gix-features", - "gix-fs", - "gix-hash", - "gix-lock", - "gix-object", - "gix-path", - "gix-tempfile", - "gix-utils", - "gix-validate 0.9.4", - "memmap2", - "thiserror 2.0.12", - "winnow 0.6.26", -] - -[[package]] -name = "gix-sec" -version = "0.10.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47aeb0f13de9ef2f3033f5ff218de30f44db827ac9f1286f9ef050aacddd5888" -dependencies = [ - "bitflags 2.9.0", - "gix-path", - "libc", - "windows-sys 0.52.0", -] - -[[package]] -name = "gix-tempfile" -version = "15.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2feb86ef094cc77a4a9a5afbfe5de626897351bbbd0de3cb9314baf3049adb82" -dependencies = [ - "gix-fs", - "libc", - "once_cell", - "parking_lot", - "tempfile", -] - -[[package]] -name = "gix-trace" -version = "0.1.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c396a2036920c69695f760a65e7f2677267ccf483f25046977d87e4cb2665f7" - -[[package]] -name = "gix-utils" -version = "0.1.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff08f24e03ac8916c478c8419d7d3c33393da9bb41fa4c24455d5406aeefd35f" -dependencies = [ - "fastrand", - "unicode-normalization", -] - -[[package]] -name = "gix-validate" -version = "0.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34b5f1253109da6c79ed7cf6e1e38437080bb6d704c76af14c93e2f255234084" -dependencies = [ - "bstr", - "thiserror 2.0.12", -] - -[[package]] -name = "gix-validate" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77b9e00cacde5b51388d28ed746c493b18a6add1f19b5e01d686b3b9ece66d4d" -dependencies = [ - "bstr", - "thiserror 2.0.12", -] +checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" [[package]] name = "glob" @@ -4857,9 +4948,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.4.9" +version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75249d144030531f8dee69fe9cea04d3edf809a017ae445e2abdff6629e86633" +checksum = "a9421a676d1b147b16b82c9225157dc629087ef8ec4d5e2960f9437a90dac0a5" dependencies = [ "atomic-waker", "bytes", @@ -4911,16 +5002,12 @@ name = "hashbrown" version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" -dependencies = [ - "ahash", - "allocator-api2", -] [[package]] name = "hashbrown" -version = "0.15.2" +version = "0.15.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" +checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5" dependencies = [ "allocator-api2", "equivalent", @@ -4936,15 +5023,9 @@ checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] name = "hermit-abi" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" - -[[package]] -name = "hermit-abi" -version = "0.5.0" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbd780fe5cc30f81464441920d82ac8740e2e46b29a6fad543ddd075229ce37e" +checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" [[package]] name = "hex" @@ -4952,7 +5033,16 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" dependencies = [ - "serde", + "serde", +] + +[[package]] +name = "hex-conservative" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5313b072ce3c597065a808dbf612c4c8e8590bdbf8b579508bf7a762c5eae6cd" +dependencies = [ + "arrayvec", ] [[package]] @@ -5103,11 +5193,10 @@ dependencies = [ [[package]] name = "hyper-rustls" -version = "0.27.5" +version = "0.27.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d191583f3da1305256f22463b9bb0471acad48a4e534a5218b9963e9c1f59b2" +checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" dependencies = [ - "futures-util", "http 1.3.1", "hyper", "hyper-util", @@ -5117,7 +5206,7 @@ dependencies = [ "tokio", "tokio-rustls", "tower-service", - "webpki-roots", + "webpki-roots 1.0.0", ] [[package]] @@ -5135,22 +5224,28 @@ dependencies = [ [[package]] name = "hyper-util" -version = "0.1.11" +version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "497bbc33a26fdd4af9ed9c70d63f61cf56a938375fbb32df34db9b1cd6d643f2" +checksum = "dc2fdfdbff08affe55bb779f33b053aa1fe5dd5b54c257343c17edfa55711bdb" dependencies = [ + "base64 0.22.1", "bytes", "futures-channel", + "futures-core", "futures-util", "http 1.3.1", "http-body 1.0.1", "hyper", + "ipnet", "libc", + "percent-encoding", "pin-project-lite", "socket2", + "system-configuration", "tokio", "tower-service", "tracing", + "windows-registry", ] [[package]] @@ -5165,7 +5260,7 @@ dependencies = [ "js-sys", "log", "wasm-bindgen", - "windows-core 0.61.0", + "windows-core", ] [[package]] @@ -5207,9 +5302,9 @@ dependencies = [ [[package]] name = "idna_mapping" -version = "1.0.0" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5422cc5bc64289a77dbb45e970b86b5e9a04cb500abc7240505aedc1bf40f38" +checksum = "11c13906586a4b339310541a274dd927aff6fcbb5b8e3af90634c4b31681c792" dependencies = [ "unicode-joining-type", ] @@ -5230,25 +5325,6 @@ dependencies = [ "winapi-util", ] -[[package]] -name = "ignore-files" -version = "3.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a20552979c32c84b0c7f6bb8d3e235627011e68eb9f6d59592f14a76b6b48ea" -dependencies = [ - "dunce", - "futures", - "gix-config", - "ignore", - "miette", - "normalize-path", - "project-origins", - "radix_trie", - "thiserror 2.0.12", - "tokio", - "tracing", -] - [[package]] name = "impl-codec" version = "0.6.0" @@ -5266,7 +5342,7 @@ checksum = "a0eb5a3343abf848c0984fe4604b2b105da9539376e24fc0a3b0007411ae4fd9" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -5300,7 +5376,7 @@ checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e" dependencies = [ "arbitrary", "equivalent", - "hashbrown 0.15.2", + "hashbrown 0.15.4", "serde", ] @@ -5334,7 +5410,7 @@ dependencies = [ "log", "num-format", "once_cell", - "quick-xml 0.37.4", + "quick-xml 0.37.5", "rgb", "str_stack", ] @@ -5345,7 +5421,7 @@ version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f37dccff2791ab604f9babef0ba14fbe0be30bd368dc541e2b08d07c8aa908f3" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "inotify-sys", "libc", ] @@ -5378,7 +5454,7 @@ dependencies = [ "indoc", "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -5396,19 +5472,42 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "inturn" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62f17d4bce58d4380de6432e6b1a0ebb561dfbbe21fc123204870b7006189677" +dependencies = [ + "boxcar", + "bumpalo", + "dashmap", + "hashbrown 0.14.5", + "thread_local", +] + [[package]] name = "ipnet" version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" +[[package]] +name = "iri-string" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbc5ebe9c3a1a7a5127f920a418f7585e9e758e911d0466ed004f393b0e380b2" +dependencies = [ + "memchr", + "serde", +] + [[package]] name = "is-terminal" version = "0.4.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e04d7f318608d35d4b61ddd75cbdaee86b023ebe2bd5a66ee0915f0bf93095a9" dependencies = [ - "hermit-abi 0.5.0", + "hermit-abi", "libc", "windows-sys 0.52.0", ] @@ -5430,9 +5529,9 @@ dependencies = [ [[package]] name = "itertools" -version = "0.11.0" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57" +checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" dependencies = [ "either", ] @@ -5463,9 +5562,9 @@ checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" [[package]] name = "jiff" -version = "0.2.10" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a064218214dc6a10fbae5ec5fa888d80c45d611aba169222fc272072bf7aef6" +checksum = "be1f93b8b1eb69c77f24bbb0afdf66f54b632ee39af40ca21c4365a1d7347e49" dependencies = [ "jiff-static", "jiff-tzdb-platform", @@ -5478,13 +5577,13 @@ dependencies = [ [[package]] name = "jiff-static" -version = "0.2.10" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "199b7932d97e325aff3a7030e141eafe7f2c6268e1d1b24859b753a627f45254" +checksum = "03343451ff899767262ec32146f6d559dd759fdadf42ff0e227c7c48f72594b4" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -5508,7 +5607,7 @@ version = "0.1.33" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38f262f097c174adebe41eb73d66ae9c06b2844fb0da69969647bbddd9b0538a" dependencies = [ - "getrandom 0.3.2", + "getrandom 0.3.3", "libc", ] @@ -5559,7 +5658,7 @@ dependencies = [ "elliptic-curve", "once_cell", "serdect", - "sha2", + "sha2 0.10.9", "signature", ] @@ -5584,9 +5683,9 @@ dependencies = [ [[package]] name = "kqueue" -version = "1.0.8" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7447f1ca1b7b563588a205fe93dea8df60fd981423a768bc1c0ded35ed147d0c" +checksum = "eac30106d7dce88daf4a3fcb4879ea939476d5074a9b7ddd0fb97fa4bed5596a" dependencies = [ "kqueue-sys", "libc", @@ -5604,42 +5703,33 @@ dependencies = [ [[package]] name = "lalrpop" -version = "0.20.2" +version = "0.22.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55cb077ad656299f160924eb2912aa147d7339ea7d69e1b5517326fdcec3c1ca" +checksum = "ba4ebbd48ce411c1d10fb35185f5a51a7bfa3d8b24b4e330d30c9e3a34129501" dependencies = [ "ascii-canvas", - "bit-set 0.5.3", + "bit-set", "ena", - "itertools 0.11.0", + "itertools 0.14.0", "lalrpop-util", "petgraph", "regex", "regex-syntax 0.8.5", + "sha3", "string_cache", "term", - "tiny-keccak", "unicode-xid", "walkdir", ] [[package]] name = "lalrpop-util" -version = "0.20.2" +version = "0.22.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "507460a910eb7b32ee961886ff48539633b788a36b65692b95f225b844c82553" +checksum = "b5baa5e9ff84f1aefd264e6869907646538a52147a755d494517a8007fb48733" dependencies = [ "regex-automata 0.4.9", -] - -[[package]] -name = "lasso" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e14eda50a3494b3bf7b9ce51c52434a761e383d7238ce1dd5dcec2fbc13e9fb" -dependencies = [ - "dashmap", - "hashbrown 0.14.5", + "rustversion", ] [[package]] @@ -5647,9 +5737,6 @@ name = "lazy_static" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" -dependencies = [ - "spin", -] [[package]] name = "lazycell" @@ -5657,11 +5744,17 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" +[[package]] +name = "levenshtein" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db13adb97ab515a3691f56e4dbab09283d0b86cb45abd991d8634a9d6f501760" + [[package]] name = "libc" -version = "0.2.172" +version = "0.2.174" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" +checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776" [[package]] name = "libdbus-sys" @@ -5675,19 +5768,29 @@ dependencies = [ [[package]] name = "libloading" -version = "0.8.6" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34" +checksum = "07033963ba89ebaf1584d767badaa2e8fcec21aedea6b8c0346d487d49c28667" dependencies = [ "cfg-if", - "windows-targets 0.52.6", + "windows-targets 0.53.2", ] [[package]] name = "libm" -version = "0.2.13" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" + +[[package]] +name = "libmimalloc-sys" +version = "0.1.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9627da5196e5d8ed0b0495e61e518847578da83483c37288316d9b2e03a7f72" +checksum = "bf88cd67e9de251c1781dbe2f641a1a3ad66eaae831b8a2c38fbdc5ddae16d4d" +dependencies = [ + "cc", + "libc", +] [[package]] name = "libredox" @@ -5695,11 +5798,57 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "libc", "redox_syscall", ] +[[package]] +name = "libsecp256k1" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e79019718125edc905a079a70cfa5f3820bc76139fc91d6f9abc27ea2a887139" +dependencies = [ + "arrayref", + "base64 0.22.1", + "digest 0.9.0", + "libsecp256k1-core", + "libsecp256k1-gen-ecmult", + "libsecp256k1-gen-genmult", + "rand 0.8.5", + "serde", + "sha2 0.9.9", +] + +[[package]] +name = "libsecp256k1-core" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5be9b9bb642d8522a44d533eab56c16c738301965504753b03ad1de3425d5451" +dependencies = [ + "crunchy", + "digest 0.9.0", + "subtle", +] + +[[package]] +name = "libsecp256k1-gen-ecmult" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3038c808c55c87e8a172643a7d87187fc6c4174468159cb3090659d55bcb4809" +dependencies = [ + "libsecp256k1-core", +] + +[[package]] +name = "libsecp256k1-gen-genmult" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3db8d6ba2cec9eacc40e6e8ccc98931840301f1006e95647ceb2dd5c3aa06f7c" +dependencies = [ + "libsecp256k1-core", +] + [[package]] name = "libusb1-sys" version = "0.7.0" @@ -5726,9 +5875,9 @@ checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" [[package]] name = "lock_api" -version = "0.4.12" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" +checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765" dependencies = [ "autocfg", "scopeguard", @@ -5753,7 +5902,7 @@ dependencies = [ "generator", "scoped-tls", "tracing", - "tracing-subscriber", + "tracing-subscriber 0.3.19", ] [[package]] @@ -5762,7 +5911,7 @@ version = "0.12.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "234cf4f4a04dc1f57e24b96cc0cd600cf2af460d4161ac5ecdd0af8e1f3b2a38" dependencies = [ - "hashbrown 0.15.2", + "hashbrown 0.15.4", ] [[package]] @@ -5771,7 +5920,34 @@ version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "227748d55f2f0ab4735d87fd623798cb6b664512fe979705f829c9f81c934465" dependencies = [ - "hashbrown 0.15.2", + "hashbrown 0.15.4", +] + +[[package]] +name = "lru-slab" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" + +[[package]] +name = "lzma-rs" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "297e814c836ae64db86b36cf2a557ba54368d03f6afcd7d947c266692f71115e" +dependencies = [ + "byteorder", + "crc", +] + +[[package]] +name = "lzma-sys" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fda04ab3764e6cde78b9974eec4f779acaba7c4e84b36eca3cf77c581b85d27" +dependencies = [ + "cc", + "libc", + "pkg-config", ] [[package]] @@ -5788,7 +5964,7 @@ checksum = "1b27834086c65ec3f9387b096d66e99f221cf081c2b738042aa252bcd41204e3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -5822,7 +5998,7 @@ checksum = "88a9689d8d44bf9964484516275f5cd4c9b59457a6940c1d5d0ecbb94510a36b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -5836,15 +6012,15 @@ dependencies = [ [[package]] name = "matchit" -version = "0.7.3" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94" +checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3" [[package]] name = "mdbook" -version = "0.4.48" +version = "0.4.51" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b6fbb4ac2d9fd7aa987c3510309ea3c80004a968d063c42f0d34fea070817c1" +checksum = "a87e65420ab45ca9c1b8cdf698f95b710cc826d373fa550f0f7fad82beac9328" dependencies = [ "ammonia", "anyhow", @@ -5857,13 +6033,12 @@ dependencies = [ "hex", "log", "memchr", - "once_cell", - "opener", + "opener 0.8.2", "pulldown-cmark", "regex", "serde", "serde_json", - "sha2", + "sha2 0.10.9", "shlex", "tempfile", "toml 0.5.11", @@ -5872,18 +6047,9 @@ dependencies = [ [[package]] name = "memchr" -version = "2.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" - -[[package]] -name = "memmap2" -version = "0.9.5" +version = "2.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd3f7eed9d3848f8b98834af67102b720745c4ec028fcd0aa0239277e7de374f" -dependencies = [ - "libc", -] +checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" [[package]] name = "memoffset" @@ -5907,25 +6073,33 @@ dependencies = [ [[package]] name = "miette" -version = "7.5.0" +version = "7.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a955165f87b37fd1862df2a59547ac542c77ef6d17c666f619d1ad22dd89484" +checksum = "5f98efec8807c63c752b5bd61f862c165c115b0a35685bdcfd9238c7aeb592b7" dependencies = [ "cfg-if", "miette-derive", - "thiserror 1.0.69", "unicode-width 0.1.14", ] [[package]] name = "miette-derive" -version = "7.5.0" +version = "7.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf45bf44ab49be92fd1227a3be6fc6f617f1a337c06af54981048574d8783147" +checksum = "db5b29714e950dbb20d5e6f74f9dcec4edbcc1067bb7f8ed198c097b8c1a818b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", +] + +[[package]] +name = "mimalloc" +version = "0.1.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1791cbe101e95af5764f06f20f6760521f7158f69dbf9d6baf941ee1bf6bc40" +dependencies = [ + "libmimalloc-sys", ] [[package]] @@ -5952,32 +6126,23 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "miniz_oxide" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8a240ddb74feaf34a79a7add65a741f3167852fba007066dcac1ca548d89c08" -dependencies = [ - "adler", -] - -[[package]] -name = "miniz_oxide" -version = "0.8.8" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3be647b768db090acb35d5ec5db2b0e1f1de11133ca123b9eacf5137868f892a" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" dependencies = [ "adler2", ] [[package]] name = "mio" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" +checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c" dependencies = [ "libc", "log", - "wasi 0.11.0+wasi-snapshot-preview1", - "windows-sys 0.52.0", + "wasi 0.11.1+wasi-snapshot-preview1", + "windows-sys 0.59.0", ] [[package]] @@ -6003,7 +6168,7 @@ dependencies = [ "cfg-if", "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -6014,11 +6179,11 @@ checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" [[package]] name = "newtype-uuid" -version = "1.2.1" +version = "1.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee3224f0e8be7c2a1ebc77ef9c3eecb90f55c6594399ee825de964526b3c9056" +checksum = "a17d82edb1c8a6c20c238747ae7aae9181133e766bc92cd2556fdd764407d0d1" dependencies = [ - "uuid 1.16.0", + "uuid 1.17.0", ] [[package]] @@ -6049,7 +6214,19 @@ version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", + "cfg-if", + "cfg_aliases", + "libc", +] + +[[package]] +name = "nix" +version = "0.30.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6" +dependencies = [ + "bitflags 2.9.1", "cfg-if", "cfg_aliases", "libc", @@ -6092,7 +6269,7 @@ version = "8.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2fee8403b3d66ac7b26aee6e40a897d85dc5ce26f44da36b8b73e987cc52e943" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "filetime", "fsevent-sys", "inotify", @@ -6228,11 +6405,11 @@ dependencies = [ [[package]] name = "num_cpus" -version = "1.16.0" +version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" +checksum = "91df4bbde75afed763b708b7eee1e8e7651e02d97f6d5dd763e89367e957b23b" dependencies = [ - "hermit-abi 0.3.9", + "hermit-abi", "libc", ] @@ -6251,9 +6428,10 @@ version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af1844ef2428cc3e1cb900be36181049ef3d3193c63e43026cfe202983b27a56" dependencies = [ + "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -6286,9 +6464,9 @@ dependencies = [ [[package]] name = "object" -version = "0.32.2" +version = "0.36.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441" +checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" dependencies = [ "memchr", ] @@ -6299,6 +6477,12 @@ version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" +[[package]] +name = "once_cell_polyfill" +version = "1.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad" + [[package]] name = "once_map" version = "0.4.21" @@ -6306,45 +6490,72 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7bd2cae3bec3936bbed1ccc5a3343b3738858182419f9c0522c7260c80c430b0" dependencies = [ "ahash", - "hashbrown 0.15.2", + "hashbrown 0.15.4", "parking_lot", "stable_deref_trait", ] [[package]] name = "op-alloy-consensus" -version = "0.11.4" +version = "0.17.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "889facbf449b2d9c8de591cd467a6c7217936f3c1c07a281759c01c49d08d66d" +checksum = "b2423a125ef2daa0d15dacc361805a0b6f76d6acfc6e24a1ff6473582087fe75" dependencies = [ "alloy-consensus", "alloy-eips", - "alloy-primitives 0.8.25", + "alloy-network", + "alloy-primitives", "alloy-rlp", + "alloy-rpc-types-eth", "alloy-serde", "derive_more 2.0.1", "serde", "thiserror 2.0.12", ] +[[package]] +name = "op-alloy-flz" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a79f352fc3893dcd670172e615afef993a41798a1d3fc0db88a3e60ef2e70ecc" + [[package]] name = "op-alloy-rpc-types" -version = "0.11.4" +version = "0.17.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eba7bfcc5d0b08c7a8bbdfaffa81e47edbb00185f0bf08e9f008216057700e50" +checksum = "f82a315004b6720fbf756afdcfdc97ea7ddbcdccfec86ea7df7562bb0da29a3f" dependencies = [ "alloy-consensus", "alloy-eips", "alloy-network-primitives", - "alloy-primitives 0.8.25", + "alloy-primitives", "alloy-rpc-types-eth", "alloy-serde", "derive_more 2.0.1", "op-alloy-consensus", "serde", "serde_json", + "thiserror 2.0.12", +] + +[[package]] +name = "op-revm" +version = "5.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0e8a3830a2be82166fbe9ead34361149ff4320743ed7ee5502ab779de221361" +dependencies = [ + "auto_impl", + "once_cell", + "revm", + "serde", ] +[[package]] +name = "opaque-debug" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" + [[package]] name = "opener" version = "0.7.2" @@ -6357,6 +6568,17 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "opener" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "771b9704f8cd8b424ec747a320b30b47517a6966ba2c7da90047c16f4a962223" +dependencies = [ + "bstr", + "normpath", + "windows-sys 0.59.0", +] + [[package]] name = "openssl-probe" version = "0.1.6" @@ -6387,6 +6609,12 @@ version = "3.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c1b04fb49957986fdce4d6ee7a65027d55d4b6d2265e5848bbb507b58ccfdb6f" +[[package]] +name = "owo-colors" +version = "4.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26995317201fa17f3656c36716aed4a7c81743a9634ac4c99c0eeda495db0cec" + [[package]] name = "p256" version = "0.13.2" @@ -6396,14 +6624,23 @@ dependencies = [ "ecdsa", "elliptic-curve", "primeorder", - "sha2", + "sha2 0.10.9", +] + +[[package]] +name = "pad" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2ad9b889f1b12e0b9ee24db044b5129150d5eada288edc800f789928dc8c0e3" +dependencies = [ + "unicode-width 0.1.14", ] [[package]] name = "parity-scale-codec" -version = "3.7.4" +version = "3.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9fde3d0718baf5bc92f577d652001da0f8d54cd03a7974e118d04fc888dc23d" +checksum = "799781ae679d79a948e13d4824a40970bfa500058d245760dd857301059810fa" dependencies = [ "arrayvec", "bitvec", @@ -6417,14 +6654,14 @@ dependencies = [ [[package]] name = "parity-scale-codec-derive" -version = "3.7.4" +version = "3.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "581c837bb6b9541ce7faa9377c20616e4fb7650f6b0f68bc93c827ee504fb7b3" +checksum = "34b4653168b563151153c9e4c08ebed57fb8262bebfa79711552fa983c623e7a" dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -6435,9 +6672,9 @@ checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" [[package]] name = "parking_lot" -version = "0.12.3" +version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" +checksum = "70d58bf43669b5795d1576d0641cfb6fbb2057bf629506267a92807158584a13" dependencies = [ "lock_api", "parking_lot_core", @@ -6445,9 +6682,9 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.10" +version = "0.9.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" +checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5" dependencies = [ "cfg-if", "libc", @@ -6514,9 +6751,9 @@ checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "pest" -version = "2.8.0" +version = "2.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "198db74531d58c70a361c42201efde7e2591e976d518caf7662a47dc5720e7b6" +checksum = "1db05f56d34358a8b1066f67cbb203ee3e7ed2ba674a6263a1d5ec6db2204323" dependencies = [ "memchr", "thiserror 2.0.12", @@ -6525,9 +6762,9 @@ dependencies = [ [[package]] name = "pest_derive" -version = "2.8.0" +version = "2.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d725d9cfd79e87dccc9341a2ef39d1b6f6353d68c4b33c177febbe1a402c97c5" +checksum = "bb056d9e8ea77922845ec74a1c4e8fb17e7c218cc4fc11a15c5d25e189aa40bc" dependencies = [ "pest", "pest_generator", @@ -6535,33 +6772,32 @@ dependencies = [ [[package]] name = "pest_generator" -version = "2.8.0" +version = "2.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db7d01726be8ab66ab32f9df467ae8b1148906685bbe75c82d1e65d7f5b3f841" +checksum = "87e404e638f781eb3202dc82db6760c8ae8a1eeef7fb3fa8264b2ef280504966" dependencies = [ "pest", "pest_meta", "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] name = "pest_meta" -version = "2.8.0" +version = "2.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f9f832470494906d1fca5329f8ab5791cc60beb230c74815dff541cbd2b5ca0" +checksum = "edd1101f170f5903fde0914f899bb503d9ff5271d7ba76bbb70bea63690cc0d5" dependencies = [ - "once_cell", "pest", - "sha2", + "sha2 0.10.9", ] [[package]] name = "petgraph" -version = "0.6.5" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db" +checksum = "3672b37090dbd86368a4145bc067582552b29c27377cad4e0a306c97f9bd7772" dependencies = [ "fixedbitset", "indexmap 2.9.0", @@ -6585,6 +6821,7 @@ checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078" dependencies = [ "phf_macros", "phf_shared", + "serde", ] [[package]] @@ -6617,7 +6854,7 @@ dependencies = [ "phf_shared", "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -6666,7 +6903,7 @@ checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -6699,9 +6936,9 @@ checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" [[package]] name = "portable-atomic" -version = "1.11.0" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "350e9b48cbc6b0e028b0473b114454c6316e57336ee184ceab6e53f72c178b3e" +checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483" [[package]] name = "portable-atomic-util" @@ -6724,7 +6961,7 @@ version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" dependencies = [ - "zerocopy 0.8.25", + "zerocopy", ] [[package]] @@ -6759,14 +6996,24 @@ dependencies = [ "termtree", ] +[[package]] +name = "prettydiff" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abec3fb083c10660b3854367697da94c674e9e82aa7511014dc958beeb7215e9" +dependencies = [ + "owo-colors 3.5.0", + "pad", +] + [[package]] name = "prettyplease" -version = "0.2.32" +version = "0.2.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "664ec5419c51e34154eec046ebcba56312d5a2fc3b09a06da188e1ad21afadf6" +checksum = "6837b9e10d61f45f987d50808f83d1ee3d206c66acf650c3e4ae2e1f6ddedf55" dependencies = [ "proc-macro2", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -6817,7 +7064,7 @@ dependencies = [ "proc-macro-error-attr2", "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -6831,52 +7078,31 @@ dependencies = [ [[package]] name = "process-wrap" -version = "8.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d35f4dc9988d1326b065b4def5e950c3ed727aa03e3151b86cc9e2aec6b03f54" -dependencies = [ - "futures", - "indexmap 2.9.0", - "nix 0.29.0", - "tokio", - "tracing", - "windows 0.59.0", -] - -[[package]] -name = "prodash" -version = "29.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f04bb108f648884c23b98a0e940ebc2c93c0c3b89f04dbaf7eb8256ce617d1bc" -dependencies = [ - "log", - "parking_lot", -] - -[[package]] -name = "project-origins" -version = "1.4.1" +version = "8.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1a0207163ace81dd9ff23a5225188a4eef8eb7de7b570f609407e521a8c9c2c" +checksum = "a3ef4f2f0422f23a82ec9f628ea2acd12871c81a9362b02c43c1aa86acfc3ba1" dependencies = [ "futures", + "indexmap 2.9.0", + "nix 0.30.1", "tokio", - "tokio-stream", + "tracing", + "windows", ] [[package]] name = "proptest" -version = "1.6.0" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14cae93065090804185d3b75f0bf93b8eeda30c7a9b4a33d3bdb3988d6229e50" +checksum = "6fcdab19deb5195a31cf7726a210015ff1496ba1464fd42cb4f537b8b01b471f" dependencies = [ - "bit-set 0.8.0", - "bit-vec 0.8.0", - "bitflags 2.9.0", + "bit-set", + "bit-vec", + "bitflags 2.9.1", "lazy_static", "num-traits", - "rand 0.8.5", - "rand_chacha 0.3.1", + "rand 0.9.1", + "rand_chacha 0.9.0", "rand_xorshift", "regex-syntax 0.8.5", "rusty-fork", @@ -6892,7 +7118,7 @@ checksum = "4ee1c9ac207483d5e7db4940700de86a9aae46ef90c48b57f99fe7edb8345e49" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -6915,7 +7141,7 @@ dependencies = [ "itertools 0.14.0", "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -6953,7 +7179,7 @@ version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "76979bea66e7875e7509c4ec5300112b316af87fa7a252ca91c448b32dfe3993" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "memchr", "pulldown-cmark-escape", "unicase", @@ -6980,10 +7206,10 @@ dependencies = [ "chrono", "indexmap 2.9.0", "newtype-uuid", - "quick-xml 0.37.4", + "quick-xml 0.37.5", "strip-ansi-escapes", "thiserror 2.0.12", - "uuid 1.16.0", + "uuid 1.17.0", ] [[package]] @@ -6997,18 +7223,18 @@ dependencies = [ [[package]] name = "quick-xml" -version = "0.37.4" +version = "0.37.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4ce8c88de324ff838700f36fb6ab86c96df0e3c4ab6ef3a9b2044465cce1369" +checksum = "331e97a1af0bf59823e6eadffe373d7b27f485be8748f71471c662c1f269b7fb" dependencies = [ "memchr", ] [[package]] name = "quinn" -version = "0.11.7" +version = "0.11.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3bd15a6f2967aef83887dcb9fec0014580467e33720d073560cf015a5683012" +checksum = "626214629cda6781b6dc1d316ba307189c85ba657213ce642d9c77670f8202c8" dependencies = [ "bytes", "cfg_aliases", @@ -7026,12 +7252,13 @@ dependencies = [ [[package]] name = "quinn-proto" -version = "0.11.11" +version = "0.11.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcbafbbdbb0f638fe3f35f3c56739f77a8a1d070cb25603226c83339b391472b" +checksum = "49df843a9161c85bb8aae55f101bc0bac8bcafd637a620d9122fd7e0b2f7422e" dependencies = [ "bytes", - "getrandom 0.3.2", + "getrandom 0.3.3", + "lru-slab", "rand 0.9.1", "ring", "rustc-hash 2.1.1", @@ -7046,9 +7273,9 @@ dependencies = [ [[package]] name = "quinn-udp" -version = "0.5.11" +version = "0.5.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "541d0f57c6ec747a90738a52741d3221f7960e8ac2f0ff4b1a63680e033b4ab5" +checksum = "fcebb1209ee276352ef14ff8732e24cc2b02bbac986cd74a4c81bcb2f9881970" dependencies = [ "cfg_aliases", "libc", @@ -7069,9 +7296,9 @@ dependencies = [ [[package]] name = "r-efi" -version = "5.2.0" +version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" [[package]] name = "radium" @@ -7147,17 +7374,17 @@ version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" dependencies = [ - "getrandom 0.3.2", + "getrandom 0.3.3", "serde", ] [[package]] name = "rand_xorshift" -version = "0.3.0" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d25bf25ec5ae4a3f1b92f929810509a2f53d7dca2f50b794ff57e3face536c8f" +checksum = "513962919efc330f829edb2535844d1b912b0fbe2ca165d613e4e8788bb05a5a" dependencies = [ - "rand_core 0.6.4", + "rand_core 0.9.3", ] [[package]] @@ -7166,7 +7393,7 @@ version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eabd94c2f37801c20583fc49dd5cd6b0ba68c716787c2dd6ed18571e1e63117b" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "cassowary", "compact_str", "crossterm", @@ -7209,33 +7436,42 @@ checksum = "d3edd4d5d42c92f0a659926464d4cce56b562761267ecf0f469d85b7de384175" [[package]] name = "redox_syscall" -version = "0.5.11" +version = "0.5.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2f103c6d277498fbceb16e84d317e2a400f160f46904d5f5410848c829511a3" +checksum = "0d04b7d0ee6b4a0207a0a7adb104d23ecb0b47d6beae7152d0fa34b692b29fd6" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", ] [[package]] name = "redox_users" -version = "0.4.6" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" +checksum = "dd6f9d3d47bdd2ad6945c5015a226ec6155d0bcdfd8f7cd29f86b71f8de99d2b" dependencies = [ "getrandom 0.2.16", "libredox", - "thiserror 1.0.69", + "thiserror 2.0.12", ] [[package]] -name = "redox_users" -version = "0.5.0" +name = "ref-cast" +version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd6f9d3d47bdd2ad6945c5015a226ec6155d0bcdfd8f7cd29f86b71f8de99d2b" +checksum = "4a0ae411dbe946a674d89546582cea4ba2bb8defac896622d6496f14c23ba5cf" dependencies = [ - "getrandom 0.2.16", - "libredox", - "thiserror 2.0.12", + "ref-cast-impl", +] + +[[package]] +name = "ref-cast-impl" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1165225c21bff1f3bbce98f5a1f889949bc902d3575308cc7b0de30b4f6d27c7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.103", ] [[package]] @@ -7290,9 +7526,9 @@ checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[package]] name = "reqwest" -version = "0.12.15" +version = "0.12.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d19c46a6fdd48bc4dab94b6103fccc55d34c67cc0ad04653aad4ea2a07cd7bbb" +checksum = "eabf4c97d9130e2bf606614eb937e86edac8292eaa6f422f995d7e8de1eb1813" dependencies = [ "async-compression", "base64 0.22.1", @@ -7308,62 +7544,166 @@ dependencies = [ "hyper", "hyper-rustls", "hyper-util", - "ipnet", "js-sys", "log", "mime", "mime_guess", - "once_cell", "percent-encoding", "pin-project-lite", "quinn", "rustls", "rustls-native-certs", - "rustls-pemfile", "rustls-pki-types", "serde", "serde_json", "serde_urlencoded", "sync_wrapper", - "system-configuration", "tokio", "tokio-rustls", - "tokio-socks", "tokio-util", - "tower 0.5.2", + "tower", + "tower-http", "tower-service", "url", "wasm-bindgen", "wasm-bindgen-futures", "wasm-streams", "web-sys", - "webpki-roots", - "windows-registry", + "webpki-roots 1.0.0", ] [[package]] name = "revm" -version = "19.7.0" +version = "24.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c175ecec83bba464aa8406502fe5bf670491c2ace81a153264891d43bc7fa332" +checksum = "01d277408ff8d6f747665ad9e52150ab4caf8d5eaf0d787614cf84633c8337b4" dependencies = [ - "auto_impl", - "cfg-if", - "dyn-clone", + "revm-bytecode", + "revm-context", + "revm-context-interface", + "revm-database", + "revm-database-interface", + "revm-handler", + "revm-inspector", + "revm-interpreter", + "revm-precompile", + "revm-primitives", + "revm-state", +] + +[[package]] +name = "revm-bytecode" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "942fe4724cf552fd28db6b0a2ca5b79e884d40dd8288a4027ed1e9090e0c6f49" +dependencies = [ + "bitvec", "once_cell", + "phf", + "revm-primitives", + "serde", +] + +[[package]] +name = "revm-context" +version = "5.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b01aad49e1233f94cebda48a4e5cef022f7c7ed29b4edf0d202b081af23435ef" +dependencies = [ + "cfg-if", + "derive-where", + "revm-bytecode", + "revm-context-interface", + "revm-database-interface", + "revm-primitives", + "revm-state", + "serde", +] + +[[package]] +name = "revm-context-interface" +version = "5.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b844f48a411e62c7dde0f757bf5cce49c85b86d6fc1d3b2722c07f2bec4c3ce" +dependencies = [ + "alloy-eip2930", + "alloy-eip7702", + "auto_impl", + "either", + "revm-database-interface", + "revm-primitives", + "revm-state", + "serde", +] + +[[package]] +name = "revm-database" +version = "4.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad3fbe34f6bb00a9c3155723b3718b9cb9f17066ba38f9eb101b678cd3626775" +dependencies = [ + "alloy-eips", + "revm-bytecode", + "revm-database-interface", + "revm-primitives", + "revm-state", + "serde", +] + +[[package]] +name = "revm-database-interface" +version = "4.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b8acd36784a6d95d5b9e1b7be3ce014f1e759abb59df1fa08396b30f71adc2a" +dependencies = [ + "auto_impl", + "revm-primitives", + "revm-state", + "serde", +] + +[[package]] +name = "revm-handler" +version = "5.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "481e8c3290ff4fa1c066592fdfeb2b172edfd14d12e6cade6f6f5588cad9359a" +dependencies = [ + "auto_impl", + "revm-bytecode", + "revm-context", + "revm-context-interface", + "revm-database-interface", "revm-interpreter", "revm-precompile", + "revm-primitives", + "revm-state", + "serde", +] + +[[package]] +name = "revm-inspector" +version = "5.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdc1167ef8937d8867888e63581d8ece729a72073d322119ef4627d813d99ecb" +dependencies = [ + "auto_impl", + "revm-context", + "revm-database-interface", + "revm-handler", + "revm-interpreter", + "revm-primitives", + "revm-state", "serde", "serde_json", ] [[package]] name = "revm-inspectors" -version = "0.16.0" +version = "0.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6a43423d81f4bef634469bfb2d9ebe36a9ea9167f20ab3a7d1ff1e05fa63099" +checksum = "4b50ef375dbacefecfdacf8f02afc31df98acc5d8859a6f2b24d121ff2a740a8" dependencies = [ - "alloy-primitives 0.8.25", + "alloy-primitives", "alloy-rpc-types-eth", "alloy-rpc-types-trace", "alloy-sol-types", @@ -7377,51 +7717,61 @@ dependencies = [ [[package]] name = "revm-interpreter" -version = "15.2.0" +version = "20.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7dcab7ef2064057acfc84731205f4bc77f4ec1b35630800b26ff6a185731c5ab" +checksum = "b5ee65e57375c6639b0f50555e92a4f1b2434349dd32f52e2176f5c711171697" dependencies = [ + "revm-bytecode", + "revm-context-interface", "revm-primitives", "serde", ] [[package]] name = "revm-precompile" -version = "16.2.0" +version = "21.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99743c3a2cac341084cc15ac74286c4bf34a0941ebf60aa420cfdb9f81f72f9f" +checksum = "0f9311e735123d8d53a02af2aa81877bba185be7c141be7f931bb3d2f3af449c" dependencies = [ + "ark-bls12-381", + "ark-bn254", + "ark-ec", + "ark-ff 0.5.0", + "ark-serialize 0.5.0", "aurora-engine-modexp", "blst", "c-kzg", "cfg-if", "k256", + "libsecp256k1", "once_cell", "p256", "revm-primitives", "ripemd", "secp256k1", - "sha2", - "substrate-bn", + "sha2 0.10.9", ] [[package]] name = "revm-primitives" -version = "15.2.0" +version = "19.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0f987564210317706def498421dfba2ae1af64a8edce82c6102758b48133fcb" +checksum = "1c1588093530ec4442461163be49c433c07a3235d1ca6f6799fef338dacc50d3" dependencies = [ - "alloy-eip2930", - "alloy-eip7702", - "alloy-primitives 0.8.25", - "auto_impl", - "bitflags 2.9.0", - "bitvec", - "c-kzg", - "cfg-if", - "dyn-clone", - "enumn", - "hex", + "alloy-primitives", + "num_enum", + "serde", +] + +[[package]] +name = "revm-state" +version = "4.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0040c61c30319254b34507383ba33d85f92949933adf6525a2cede05d165e1fa" +dependencies = [ + "bitflags 2.9.1", + "revm-bytecode", + "revm-primitives", "serde", ] @@ -7500,9 +7850,9 @@ dependencies = [ [[package]] name = "ruint" -version = "1.14.0" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78a46eb779843b2c4f21fac5773e25d6d5b7c8f0922876c91541790d2ca27eef" +checksum = "11256b5fe8c68f56ac6f39ef0720e592f33d2367a4782740d9c9142e889c7fb4" dependencies = [ "alloy-rlp", "arbitrary", @@ -7544,9 +7894,9 @@ dependencies = [ [[package]] name = "rustc-demangle" -version = "0.1.24" +version = "0.1.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" +checksum = "989e6739f80c4ad5b13e0fd7fe89531180375b18520cc8c82080e4dc4035b84f" [[package]] name = "rustc-hash" @@ -7587,13 +7937,25 @@ dependencies = [ "semver 1.0.26", ] +[[package]] +name = "rustfix" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82fa69b198d894d84e23afde8e9ab2af4400b2cba20d6bf2b428a8b01c222c5a" +dependencies = [ + "serde", + "serde_json", + "thiserror 1.0.69", + "tracing", +] + [[package]] name = "rustix" version = "0.38.44" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "errno", "libc", "linux-raw-sys 0.4.15", @@ -7602,11 +7964,11 @@ dependencies = [ [[package]] name = "rustix" -version = "1.0.5" +version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d97817398dd4bb2e6da002002db259209759911da105da92bec29ccb12cf58bf" +checksum = "c71e83d6afe7ff64890ec6b71d6a69bb8a610ab78ce364b3352876bb4c801266" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "errno", "libc", "linux-raw-sys 0.9.4", @@ -7615,9 +7977,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.26" +version = "0.23.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df51b5869f3a441595eac5e8ff14d486ff285f7b8c0df8770e49c3b56351f0f0" +checksum = "7160e3e10bf4535308537f3c4e1641468cd0e485175d6163087c0393c7d46643" dependencies = [ "aws-lc-rs", "log", @@ -7641,29 +8003,21 @@ dependencies = [ "security-framework", ] -[[package]] -name = "rustls-pemfile" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" -dependencies = [ - "rustls-pki-types", -] - [[package]] name = "rustls-pki-types" -version = "1.11.0" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "917ce264624a4b4db1c364dcc35bfca9ded014d0a958cd47ad3e960e988ea51c" +checksum = "229a4a4c221013e7e1f1a043678c5cc39fe5171437c88fb47151a21e6f5b5c79" dependencies = [ "web-time", + "zeroize", ] [[package]] name = "rustls-webpki" -version = "0.103.1" +version = "0.103.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fef8b8769aaccf73098557a87cd1816b4f9c7c16811c9c77142aa695c16f2c03" +checksum = "e4a72fe2bcf7a6ac6fd7d0b9e5cb68aeb7d4c0a0271730218b3e92d43b4eb435" dependencies = [ "aws-lc-rs", "ring", @@ -7673,9 +8027,9 @@ dependencies = [ [[package]] name = "rustversion" -version = "1.0.20" +version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eded382c5f5f786b989652c49544c4877d9f015cc22e145a5ea8ea66c2921cd2" +checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d" [[package]] name = "rusty-fork" @@ -7695,7 +8049,7 @@ version = "15.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2ee1e066dc922e513bda599c6ccb5f3bb2b0ea5870a579448f2622993f0a9a2f" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "cfg-if", "clipboard-win", "fd-lock", @@ -7765,6 +8119,18 @@ dependencies = [ "serde_json", ] +[[package]] +name = "schemars" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd191f9397d57d581cddd31014772520aa448f65ef991055d7f61582c65165f" +dependencies = [ + "dyn-clone", + "ref-cast", + "serde", + "serde_json", +] + [[package]] name = "schemars_derive" version = "0.8.22" @@ -7774,7 +8140,7 @@ dependencies = [ "proc-macro2", "quote", "serde_derive_internals", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -7798,7 +8164,7 @@ dependencies = [ "hmac", "pbkdf2 0.11.0", "salsa20", - "sha2", + "sha2 0.10.9", ] [[package]] @@ -7818,12 +8184,14 @@ dependencies = [ [[package]] name = "secp256k1" -version = "0.29.1" +version = "0.30.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9465315bc9d4566e1724f0fffcbcc446268cb522e60f9a27bcded6b19c108113" +checksum = "b50c5943d326858130af85e049f2661ba3c78b26589b8ab98e65e80ae44a1252" dependencies = [ + "bitcoin_hashes", "rand 0.8.5", "secp256k1-sys", + "serde", ] [[package]] @@ -7854,8 +8222,8 @@ version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "271720403f46ca04f7ba6f55d438f8bd878d6b8ca0a1046e8228c4145bcbb316" dependencies = [ - "bitflags 2.9.0", - "core-foundation 0.10.0", + "bitflags 2.9.1", + "core-foundation 0.10.1", "core-foundation-sys", "libc", "security-framework-sys", @@ -7921,7 +8289,7 @@ checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -7932,7 +8300,7 @@ checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -7967,22 +8335,11 @@ dependencies = [ "serde", ] -[[package]] -name = "serde_repr" -version = "0.1.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.101", -] - [[package]] name = "serde_spanned" -version = "0.6.8" +version = "0.6.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1" +checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" dependencies = [ "serde", ] @@ -8001,15 +8358,16 @@ dependencies = [ [[package]] name = "serde_with" -version = "3.12.0" +version = "3.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6b6f7f2fcb69f747921f79f3926bd1e203fce4fef62c268dd3abfb6d86029aa" +checksum = "bf65a400f8f66fb7b0552869ad70157166676db75ed8181f8104ea91cf9d0b42" dependencies = [ "base64 0.22.1", "chrono", "hex", "indexmap 1.9.3", "indexmap 2.9.0", + "schemars 0.9.0", "serde", "serde_derive", "serde_json", @@ -8019,14 +8377,14 @@ dependencies = [ [[package]] name = "serde_with_macros" -version = "3.12.0" +version = "3.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d00caa5193a3c8362ac2b73be6b9e768aa5a4b2f721d8f4b339600c3cb51f8e" +checksum = "81679d9ed988d5e9a5e6531dc3f2c28efbd639cbd1dfb628df08edea6004da77" dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -8051,16 +8409,23 @@ dependencies = [ ] [[package]] -name = "sha1_smol" -version = "1.0.1" +name = "sha2" +version = "0.9.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbfa15b3dddfee50a0fff136974b3e1bde555604ba463834a7eb7deb6417705d" +checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800" +dependencies = [ + "block-buffer 0.9.0", + "cfg-if", + "cpufeatures", + "digest 0.9.0", + "opaque-debug", +] [[package]] name = "sha2" -version = "0.10.8" +version = "0.10.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" dependencies = [ "cfg-if", "cpufeatures", @@ -8110,9 +8475,9 @@ checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] name = "signal-hook" -version = "0.3.17" +version = "0.3.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8621587d4798caf8eb44879d42e56b9a93ea5dcd315a6487c357130095b62801" +checksum = "d881a16cf4426aa584979d30bd82cb33429027e42122b169753d6ef1085ed6e2" dependencies = [ "libc", "signal-hook-registry", @@ -8194,18 +8559,15 @@ checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" [[package]] name = "slab" -version = "0.4.9" +version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" -dependencies = [ - "autocfg", -] +checksum = "04dc19736151f35336d325007ac991178d504a119863a2fcb3758cdb5e52c50d" [[package]] name = "smallvec" -version = "1.15.0" +version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8917285742e9f3e1683f0a9c4e6b57960b7314d0b08d30d1ecd426713ee2eee9" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" dependencies = [ "serde", ] @@ -8244,34 +8606,20 @@ dependencies = [ [[package]] name = "socket2" -version = "0.5.9" +version = "0.5.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f5fd57c80058a56cf5c777ab8a126398ece8e442983605d280a44ce79d0edef" +checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678" dependencies = [ "libc", "windows-sys 0.52.0", ] -[[package]] -name = "solang-parser" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c425ce1c59f4b154717592f0bdf4715c3a1d55058883622d3157e1f0908a5b26" -dependencies = [ - "itertools 0.11.0", - "lalrpop", - "lalrpop-util", - "phf", - "thiserror 1.0.69", - "unicode-xid", -] - [[package]] name = "solar-ast" -version = "0.1.2" -source = "git+https://github.com/paradigmxyz/solar?branch=main#53ee2ccfa71eac49a8076ed1d3573ac6ef3a1719" +version = "0.1.4" +source = "git+https://github.com/paradigmxyz/solar?branch=main#2020b272fb5d805f6487c9b5b439ab40cc79671c" dependencies = [ - "alloy-primitives 1.0.0", + "alloy-primitives", "bumpalo", "either", "num-bigint", @@ -8286,16 +8634,16 @@ dependencies = [ [[package]] name = "solar-config" -version = "0.1.2" -source = "git+https://github.com/paradigmxyz/solar?branch=main#53ee2ccfa71eac49a8076ed1d3573ac6ef3a1719" +version = "0.1.4" +source = "git+https://github.com/paradigmxyz/solar?branch=main#2020b272fb5d805f6487c9b5b439ab40cc79671c" dependencies = [ "strum 0.27.1", ] [[package]] name = "solar-data-structures" -version = "0.1.2" -source = "git+https://github.com/paradigmxyz/solar?branch=main#53ee2ccfa71eac49a8076ed1d3573ac6ef3a1719" +version = "0.1.4" +source = "git+https://github.com/paradigmxyz/solar?branch=main#2020b272fb5d805f6487c9b5b439ab40cc79671c" dependencies = [ "bumpalo", "index_vec", @@ -8308,8 +8656,8 @@ dependencies = [ [[package]] name = "solar-interface" -version = "0.1.2" -source = "git+https://github.com/paradigmxyz/solar?branch=main#53ee2ccfa71eac49a8076ed1d3573ac6ef3a1719" +version = "0.1.4" +source = "git+https://github.com/paradigmxyz/solar?branch=main#2020b272fb5d805f6487c9b5b439ab40cc79671c" dependencies = [ "annotate-snippets", "anstream", @@ -8318,40 +8666,42 @@ dependencies = [ "derive_builder", "derive_more 2.0.1", "dunce", - "itertools 0.11.0", + "inturn", + "itertools 0.10.5", "itoa", - "lasso", "match_cfg", "normalize-path", "rayon", "scoped-tls", + "serde", + "serde_json", "solar-config", "solar-data-structures", "solar-macros", - "thiserror 2.0.12", + "thiserror 1.0.69", "tracing", "unicode-width 0.2.0", ] [[package]] name = "solar-macros" -version = "0.1.2" -source = "git+https://github.com/paradigmxyz/solar?branch=main#53ee2ccfa71eac49a8076ed1d3573ac6ef3a1719" +version = "0.1.4" +source = "git+https://github.com/paradigmxyz/solar?branch=main#2020b272fb5d805f6487c9b5b439ab40cc79671c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] name = "solar-parse" -version = "0.1.2" -source = "git+https://github.com/paradigmxyz/solar?branch=main#53ee2ccfa71eac49a8076ed1d3573ac6ef3a1719" +version = "0.1.4" +source = "git+https://github.com/paradigmxyz/solar?branch=main#2020b272fb5d805f6487c9b5b439ab40cc79671c" dependencies = [ - "alloy-primitives 1.0.0", - "bitflags 2.9.0", + "alloy-primitives", + "bitflags 2.9.1", "bumpalo", - "itertools 0.11.0", + "itertools 0.10.5", "memchr", "num-bigint", "num-rational", @@ -8365,12 +8715,12 @@ dependencies = [ [[package]] name = "solar-sema" -version = "0.1.2" -source = "git+https://github.com/paradigmxyz/solar?branch=main#53ee2ccfa71eac49a8076ed1d3573ac6ef3a1719" +version = "0.1.4" +source = "git+https://github.com/paradigmxyz/solar?branch=main#2020b272fb5d805f6487c9b5b439ab40cc79671c" dependencies = [ - "alloy-json-abi 1.0.0", - "alloy-primitives 1.0.0", - "bitflags 2.9.0", + "alloy-json-abi", + "alloy-primitives", + "bitflags 2.9.1", "bumpalo", "derive_more 2.0.1", "either", @@ -8431,20 +8781,24 @@ dependencies = [ "semver 1.0.26", "serde", "serde_json", - "sha2", + "sha2 0.10.9", "thiserror 2.0.12", "tokio", "toml_edit", - "uuid 1.16.0", + "uuid 1.17.0", "zip", "zip-extract", ] [[package]] -name = "spin" -version = "0.9.8" +name = "spanned" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +checksum = "86af297923fbcfd107c20a189a6e9c872160df71a7190ae4a7a6c5dce4b2feb6" +dependencies = [ + "bstr", + "color-eyre", +] [[package]] name = "spki" @@ -8542,7 +8896,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -8555,20 +8909,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.101", -] - -[[package]] -name = "substrate-bn" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b5bbfa79abbae15dd642ea8176a21a635ff3c00059961d1ea27ad04e5b441c" -dependencies = [ - "byteorder", - "crunchy", - "lazy_static", - "rand 0.8.5", - "rustc-hex", + "syn 2.0.103", ] [[package]] @@ -8657,9 +8998,9 @@ dependencies = [ [[package]] name = "svm-rs" -version = "0.5.15" +version = "0.5.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "039f8b5327f4c4c94384ad8596cc62fb23f58ef5e5d940945757b627fa56d0c2" +checksum = "62d304f1b54e9c83ec8f0537c9dd40d46344bd9142cc528d5242c4b6fe11ced0" dependencies = [ "const-hex", "dirs", @@ -8668,7 +9009,7 @@ dependencies = [ "semver 1.0.26", "serde", "serde_json", - "sha2", + "sha2 0.10.9", "tempfile", "thiserror 2.0.12", "url", @@ -8677,11 +9018,10 @@ dependencies = [ [[package]] name = "svm-rs-builds" -version = "0.5.15" +version = "0.5.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05723cae9acea48e97af3357b25cf0079277bf2ab54405fd3dd62258caae1a48" +checksum = "5ed5035d2abae3cd98c201b116ff22a82861589c060f3e4b687ce951cf381a6e" dependencies = [ - "build_const", "const-hex", "semver 1.0.26", "serde_json", @@ -8701,9 +9041,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.101" +version = "2.0.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ce2b7fc941b3a24138a0a7cf8e858bfc6a992e7978a068a5c760deb0ed43caf" +checksum = "e4307e30089d6fd6aff212f2da3a1f9e32f3223b1f010fb09b7c95f90f3ca1e8" dependencies = [ "proc-macro2", "quote", @@ -8712,14 +9052,14 @@ dependencies = [ [[package]] name = "syn-solidity" -version = "0.8.25" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4560533fbd6914b94a8fb5cc803ed6801c3455668db3b810702c57612bac9412" +checksum = "14c8c8f496c33dc6343dac05b4be8d9e0bca180a4caa81d7b8416b10cc2273cd" dependencies = [ "paste", "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -8737,7 +9077,7 @@ version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "core-foundation 0.9.4", "system-configuration-sys", ] @@ -8760,14 +9100,14 @@ checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" [[package]] name = "tempfile" -version = "3.19.1" +version = "3.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7437ac7763b9b123ccf33c338a5cc1bac6f69b45a136c19bdd8a65e3916435bf" +checksum = "e8a64e3985349f2441a1a9ef0b853f869006c3855f2cda6862a94d26ebb9d6a1" dependencies = [ "fastrand", - "getrandom 0.3.2", + "getrandom 0.3.3", "once_cell", - "rustix 1.0.5", + "rustix 1.0.7", "windows-sys 0.52.0", ] @@ -8784,13 +9124,12 @@ dependencies = [ [[package]] name = "term" -version = "0.7.0" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c59df8ac95d96ff9bede18eb7300b0fda5e5d8d90960e76f8e14ae765eedbf1f" +checksum = "8a984c8d058c627faaf5e8e2ed493fa3c51771889196de1016cf9c1c6e90d750" dependencies = [ - "dirs-next", - "rustversion", - "winapi", + "home", + "windows-sys 0.59.0", ] [[package]] @@ -8799,7 +9138,7 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "45c6481c4829e4cc63825e62c49186a34538b7b2750b73b266581ffb612fb5ed" dependencies = [ - "rustix 1.0.5", + "rustix 1.0.7", "windows-sys 0.59.0", ] @@ -8858,7 +9197,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -8869,17 +9208,16 @@ checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] name = "thread_local" -version = "1.1.8" +version = "1.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" +checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" dependencies = [ "cfg-if", - "once_cell", ] [[package]] @@ -8970,9 +9308,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.44.2" +version = "1.45.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6b88822cbe49de4185e3a4cbf8321dd487cf5fe0c5c65695fef6346371e9c48" +checksum = "75ef51a33ef1da925cea3e4eb122833cb377c61439ca401b770f54902b806779" dependencies = [ "backtrace", "bytes", @@ -8994,28 +9332,16 @@ checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] name = "tokio-rustls" version = "0.26.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e727b36a1a0e8b74c376ac2211e40c2c8af09fb4013c60d910495810f008e9b" -dependencies = [ - "rustls", - "tokio", -] - -[[package]] -name = "tokio-socks" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d4770b8024672c1101b3f6733eab95b18007dbe0847a8afe341fcf79e06043f" -dependencies = [ - "either", - "futures-util", - "thiserror 1.0.69", +checksum = "8e727b36a1a0e8b74c376ac2211e40c2c8af09fb4013c60d910495810f008e9b" +dependencies = [ + "rustls", "tokio", ] @@ -9031,18 +9357,6 @@ dependencies = [ "tokio-util", ] -[[package]] -name = "tokio-tungstenite" -version = "0.24.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edc5f74e248dc973e0dbb7b74c7e0d6fcc301c694ff50049504004ef4d0cdcd9" -dependencies = [ - "futures-util", - "log", - "tokio", - "tungstenite 0.24.0", -] - [[package]] name = "tokio-tungstenite" version = "0.26.2" @@ -9055,8 +9369,8 @@ dependencies = [ "rustls-pki-types", "tokio", "tokio-rustls", - "tungstenite 0.26.2", - "webpki-roots", + "tungstenite", + "webpki-roots 0.26.11", ] [[package]] @@ -9083,9 +9397,9 @@ dependencies = [ [[package]] name = "toml" -version = "0.8.21" +version = "0.8.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "900f6c86a685850b1bc9f6223b20125115ee3f31e01207d81655bbcc0aea9231" +checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362" dependencies = [ "indexmap 2.9.0", "serde", @@ -9096,40 +9410,39 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "0.6.9" +version = "0.6.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3da5db5a963e24bc68be8b17b6fa82814bb22ee8660f192bb182771d498f09a3" +checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" dependencies = [ "serde", ] [[package]] name = "toml_edit" -version = "0.22.25" +version = "0.22.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10558ed0bd2a1562e630926a2d1f0b98c827da99fabd3fe20920a59642504485" +checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" dependencies = [ "indexmap 2.9.0", "serde", "serde_spanned", "toml_datetime", "toml_write", - "winnow 0.7.7", + "winnow", ] [[package]] name = "toml_write" -version = "0.1.0" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28391a4201ba7eb1984cfeb6862c0b3ea2cfe23332298967c749dddc0d6cd976" +checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801" [[package]] name = "tonic" -version = "0.12.3" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "877c5b330756d856ffcc4553ab34a5684481ade925ecc54bcd1bf02b1d0d4d52" +checksum = "7e581ba15a835f4d9ea06c55ab1bd4dce26fc53752c69a04aac00703bfb49ba9" dependencies = [ - "async-stream", "async-trait", "axum", "base64 0.22.1", @@ -9145,12 +9458,11 @@ dependencies = [ "pin-project 1.1.10", "prost", "rustls-native-certs", - "rustls-pemfile", "socket2", "tokio", "tokio-rustls", "tokio-stream", - "tower 0.4.13", + "tower", "tower-layer", "tower-service", "tracing", @@ -9162,26 +9474,6 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ea68304e134ecd095ac6c3574494fc62b909f416c4fca77e440530221e549d3d" -[[package]] -name = "tower" -version = "0.4.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" -dependencies = [ - "futures-core", - "futures-util", - "indexmap 1.9.3", - "pin-project 1.1.10", - "pin-project-lite", - "rand 0.8.5", - "slab", - "tokio", - "tokio-util", - "tower-layer", - "tower-service", - "tracing", -] - [[package]] name = "tower" version = "0.5.2" @@ -9190,9 +9482,12 @@ checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" dependencies = [ "futures-core", "futures-util", + "indexmap 2.9.0", "pin-project-lite", + "slab", "sync_wrapper", "tokio", + "tokio-util", "tower-layer", "tower-service", "tracing", @@ -9200,24 +9495,27 @@ dependencies = [ [[package]] name = "tower-http" -version = "0.6.2" +version = "0.6.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "403fa3b783d4b626a8ad51d766ab03cb6d2dbfc46b1c5d4448395e6628dc9697" +checksum = "adc82fd73de2a9722ac5da747f12383d2bfdb93591ee6c58486e0097890f05f2" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "bytes", + "futures-core", "futures-util", "http 1.3.1", "http-body 1.0.1", "http-body-util", "http-range-header", "httpdate", + "iri-string", "mime", "mime_guess", "percent-encoding", "pin-project-lite", "tokio", "tokio-util", + "tower", "tower-layer", "tower-service", "tracing", @@ -9261,20 +9559,20 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.28" +version = "0.1.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" +checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] name = "tracing-core" -version = "0.1.33" +version = "0.1.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" +checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" dependencies = [ "once_cell", "valuable", @@ -9287,7 +9585,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b1581020d7a273442f5b45074a6a57d5757ad0a47dac0e9f0bd57b81936f3db" dependencies = [ "tracing", - "tracing-subscriber", + "tracing-subscriber 0.3.19", ] [[package]] @@ -9313,6 +9611,15 @@ dependencies = [ "tracing-core", ] +[[package]] +name = "tracing-subscriber" +version = "0.2.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e0d2eaa99c3c2e41547cfa109e910a68ea03823cccad4a0525dcbc9b01e8c71" +dependencies = [ + "tracing-core", +] + [[package]] name = "tracing-subscriber" version = "0.3.19" @@ -9338,26 +9645,27 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eaa1852afa96e0fe9e44caa53dc0bd2d9d05e0f2611ce09f97f8677af56e4ba" dependencies = [ "tracing-core", - "tracing-subscriber", + "tracing-subscriber 0.3.19", "tracy-client", ] [[package]] name = "tracy-client" -version = "0.18.0" +version = "0.18.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d90a2c01305b02b76fdd89ac8608bae27e173c829a35f7d76a345ab5d33836db" +checksum = "3927832d93178f979a970d26deed7b03510586e328f31b0f9ad7a73985b8332a" dependencies = [ "loom", "once_cell", + "rustc-demangle", "tracy-client-sys", ] [[package]] name = "tracy-client-sys" -version = "0.24.3" +version = "0.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69fff37da548239c3bf9e64a12193d261e8b22b660991c6fd2df057c168f435f" +checksum = "c032d68a49d25d9012a864fef1c64ac17aee43c87e0477bf7301d8ae8bfea7b7" dependencies = [ "cc", "windows-targets 0.52.6", @@ -9383,24 +9691,6 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" -[[package]] -name = "tungstenite" -version = "0.24.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18e5b8366ee7a95b16d32197d0b2604b43a0be89dc5fac9f8e96ccafbaedda8a" -dependencies = [ - "byteorder", - "bytes", - "data-encoding", - "http 1.3.1", - "httparse", - "log", - "rand 0.8.5", - "sha1", - "thiserror 1.0.69", - "utf-8", -] - [[package]] name = "tungstenite" version = "0.26.2" @@ -9444,6 +9734,32 @@ version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" +[[package]] +name = "ui_test" +version = "0.29.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1211b1111c752c73b33073d2958072be08825fd97c9ab4d83444da361a06634b" +dependencies = [ + "annotate-snippets", + "anyhow", + "bstr", + "cargo-platform", + "cargo_metadata", + "color-eyre", + "colored", + "comma", + "crossbeam-channel", + "indicatif", + "levenshtein", + "prettydiff", + "regex", + "rustc_version 0.4.1", + "rustfix", + "serde", + "serde_json", + "spanned", +] + [[package]] name = "uint" version = "0.9.5" @@ -9483,12 +9799,6 @@ version = "0.3.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5" -[[package]] -name = "unicode-bom" -version = "2.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7eec5d1121208364f6793f7d2e222bf75a915c19557537745b195b253dd64217" - [[package]] name = "unicode-ident" version = "1.0.18" @@ -9497,9 +9807,9 @@ checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" [[package]] name = "unicode-joining-type" -version = "0.7.0" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22f8cb47ccb8bc750808755af3071da4a10dcd147b68fc874b7ae4b12543f6f5" +checksum = "d8d00a78170970967fdb83f9d49b92f959ab2bb829186b113e4f4604ad98e180" [[package]] name = "unicode-linebreak" @@ -9610,12 +9920,14 @@ dependencies = [ [[package]] name = "uuid" -version = "1.16.0" +version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "458f7a779bf54acc9f347480ac654f68407d3aab21269a6e3c9f922acd9e2da9" +checksum = "3cf4199d1e5d15ddd86a694e4d0dffa9c323ce759fea589f00fef9d81cc1931d" dependencies = [ - "getrandom 0.3.2", + "getrandom 0.3.3", + "js-sys", "serde", + "wasm-bindgen", ] [[package]] @@ -9729,9 +10041,9 @@ dependencies = [ [[package]] name = "wasi" -version = "0.11.0+wasi-snapshot-preview1" +version = "0.11.1+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] name = "wasi" @@ -9764,7 +10076,7 @@ dependencies = [ "log", "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", "wasm-bindgen-shared", ] @@ -9799,7 +10111,7 @@ checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -9828,9 +10140,9 @@ dependencies = [ [[package]] name = "wasmtimer" -version = "0.4.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0048ad49a55b9deb3953841fa1fc5858f0efbcb7a18868c899a360269fac1b23" +checksum = "d8d49b5d6c64e8558d9b1b065014426f35c18de636895d24893dbbd329743446" dependencies = [ "futures", "js-sys", @@ -9842,22 +10154,16 @@ dependencies = [ [[package]] name = "watchexec" -version = "6.0.0" +version = "8.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "285b8a386af1994c82423eddd4090ae800b2dc4c9e39da8724ccc5a240ecf843" +checksum = "bc35794a21139060aca512393e9b1a225fe48fc11edee65c84d6d76b25a53331" dependencies = [ "async-priority-channel", - "async-recursion", "atomic-take", "futures", - "ignore-files", "miette", - "nix 0.29.0", "normalize-path", "notify", - "once_cell", - "process-wrap", - "project-origins", "thiserror 2.0.12", "tokio", "tracing", @@ -9868,34 +10174,32 @@ dependencies = [ [[package]] name = "watchexec-events" -version = "5.0.0" +version = "6.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cfbd49bb801e0128aa931808a3953df026b90608e044c22d814b63439fe5f51" +checksum = "9c4a8973a20c7d30198a12272519163168a9ba8b687693ec9d1f027b75b860d1" dependencies = [ - "nix 0.29.0", "notify-types", "watchexec-signals", ] [[package]] name = "watchexec-signals" -version = "4.0.1" +version = "5.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8834ddd08f1ce18ea85e4ccbdafaea733851c7dc6afefd50037aea17845a861a" +checksum = "377729679262964c27e6a28f360a84b7aedb172b59841301c1c77922305dfd83" dependencies = [ "miette", - "nix 0.29.0", + "nix 0.30.1", "thiserror 2.0.12", ] [[package]] name = "watchexec-supervisor" -version = "4.0.0" +version = "5.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "676d7fe6bff6247b03ddc719de66b6548712187d61a49f98fa012c88529a757d" +checksum = "92a45c50ea6b2795f3d070ad621618c8737bb98f6bc2eb4847e8e8e2ce2f446c" dependencies = [ "futures", - "nix 0.29.0", "process-wrap", "tokio", "tracing", @@ -9925,9 +10229,9 @@ dependencies = [ [[package]] name = "web_atoms" -version = "0.1.0" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "954c5a41f2bcb7314344079d0891505458cc2f4b422bdea1d5bfbe6d1a04903b" +checksum = "57ffde1dc01240bdf9992e3205668b235e59421fd085e8a317ed98da0178d414" dependencies = [ "phf", "phf_codegen", @@ -9937,9 +10241,18 @@ dependencies = [ [[package]] name = "webpki-roots" -version = "0.26.8" +version = "0.26.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "521bc38abb08001b01866da9f51eb7c5d647a19260e00054a8c7fd5f9e57f7a9" +dependencies = [ + "webpki-roots 1.0.0", +] + +[[package]] +name = "webpki-roots" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2210b291f7ea53617fbafcc4939f10914214ec15aace5ba62293a668f322c5c9" +checksum = "2853738d1cc4f2da3a225c18ec6c3721abb31961096e9dbf5ab35fa88b19cfdb" dependencies = [ "rustls-pki-types", ] @@ -9964,7 +10277,7 @@ checksum = "24d643ce3fd3e5b54854602a080f34fb10ab75e0b813ee32d00ca2b44fa74762" dependencies = [ "either", "env_home", - "rustix 1.0.5", + "rustix 1.0.7", "winsafe", ] @@ -10007,83 +10320,48 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windows" -version = "0.58.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd04d41d93c4992d421894c18c8b43496aa748dd4c081bac0dc93eb0489272b6" -dependencies = [ - "windows-core 0.58.0", - "windows-targets 0.52.6", -] - -[[package]] -name = "windows" -version = "0.59.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f919aee0a93304be7f62e8e5027811bbba96bcb1de84d6618be56e43f8a32a1" -dependencies = [ - "windows-core 0.59.0", - "windows-targets 0.53.0", -] - -[[package]] -name = "windows-core" -version = "0.58.0" +version = "0.61.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ba6d44ec8c2591c134257ce647b7ea6b20335bf6379a27dac5f1641fcf59f99" +checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893" dependencies = [ - "windows-implement 0.58.0", - "windows-interface 0.58.0", - "windows-result 0.2.0", - "windows-strings 0.1.0", - "windows-targets 0.52.6", + "windows-collections", + "windows-core", + "windows-future", + "windows-link", + "windows-numerics", ] [[package]] -name = "windows-core" -version = "0.59.0" +name = "windows-collections" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "810ce18ed2112484b0d4e15d022e5f598113e220c53e373fb31e67e21670c1ce" +checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8" dependencies = [ - "windows-implement 0.59.0", - "windows-interface 0.59.1", - "windows-result 0.3.2", - "windows-strings 0.3.1", - "windows-targets 0.53.0", + "windows-core", ] [[package]] name = "windows-core" -version = "0.61.0" +version = "0.61.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4763c1de310c86d75a878046489e2e5ba02c649d185f21c67d4cf8a56d098980" +checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" dependencies = [ - "windows-implement 0.60.0", - "windows-interface 0.59.1", + "windows-implement", + "windows-interface", "windows-link", - "windows-result 0.3.2", - "windows-strings 0.4.0", -] - -[[package]] -name = "windows-implement" -version = "0.58.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2bbd5b46c938e506ecbce286b6628a02171d56153ba733b6c741fc627ec9579b" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.101", + "windows-result", + "windows-strings", ] [[package]] -name = "windows-implement" -version = "0.59.0" +name = "windows-future" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83577b051e2f49a058c308f17f273b570a6a758386fc291b5f6a934dd84e48c1" +checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e" dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.101", + "windows-core", + "windows-link", + "windows-threading", ] [[package]] @@ -10094,18 +10372,7 @@ checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", -] - -[[package]] -name = "windows-interface" -version = "0.58.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "053c4c462dc91d3b1504c6fe5a726dd15e216ba718e84a0e46a88fbe5ded3515" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -10116,68 +10383,50 @@ checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] name = "windows-link" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76840935b766e1b0a05c0066835fb9ec80071d4c09a16f6bd5f7e655e3c14c38" - -[[package]] -name = "windows-registry" -version = "0.4.0" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4286ad90ddb45071efd1a66dfa43eb02dd0dfbae1545ad6cc3c51cf34d7e8ba3" -dependencies = [ - "windows-result 0.3.2", - "windows-strings 0.3.1", - "windows-targets 0.53.0", -] +checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" [[package]] -name = "windows-result" +name = "windows-numerics" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e" -dependencies = [ - "windows-targets 0.52.6", -] - -[[package]] -name = "windows-result" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c64fd11a4fd95df68efcfee5f44a294fe71b8bc6a91993e2791938abcc712252" +checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1" dependencies = [ + "windows-core", "windows-link", ] [[package]] -name = "windows-strings" -version = "0.1.0" +name = "windows-registry" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10" +checksum = "b3bab093bdd303a1240bb99b8aba8ea8a69ee19d34c9e2ef9594e708a4878820" dependencies = [ - "windows-result 0.2.0", - "windows-targets 0.52.6", + "windows-link", + "windows-result", + "windows-strings", ] [[package]] -name = "windows-strings" -version = "0.3.1" +name = "windows-result" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87fa48cc5d406560701792be122a10132491cff9d0aeb23583cc2dcafc847319" +checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" dependencies = [ "windows-link", ] [[package]] name = "windows-strings" -version = "0.4.0" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a2ba9642430ee452d5a7aa78d72907ebe8cfda358e8cb7918a2050581322f97" +checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" dependencies = [ "windows-link", ] @@ -10200,6 +10449,15 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows-sys" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +dependencies = [ + "windows-targets 0.53.2", +] + [[package]] name = "windows-targets" version = "0.52.6" @@ -10218,9 +10476,9 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.53.0" +version = "0.53.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1e4c7e8ceaaf9cb7d7507c974735728ab453b67ef8f18febdd7c11fe59dca8b" +checksum = "c66f69fcc9ce11da9966ddb31a40968cad001c5bedeb5c2b82ede4253ab48aef" dependencies = [ "windows_aarch64_gnullvm 0.53.0", "windows_aarch64_msvc 0.53.0", @@ -10232,6 +10490,15 @@ dependencies = [ "windows_x86_64_msvc 0.53.0", ] +[[package]] +name = "windows-threading" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b66463ad2e0ea3bbf808b7f1d371311c80e115c0b71d60efc142cafbcfb057a6" +dependencies = [ + "windows-link", +] + [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" @@ -10330,18 +10597,9 @@ checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" [[package]] name = "winnow" -version = "0.6.26" +version = "0.7.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e90edd2ac1aa278a5c4599b1d89cf03074b610800f866d4026dc199d7929a28" -dependencies = [ - "memchr", -] - -[[package]] -name = "winnow" -version = "0.7.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6cb8234a863ea0e8cd7284fcdd4f145233eb00fee02bbdd9861aec44e6477bc5" +checksum = "74c7b26e3480b707944fc872477815d29a8e429d2f93a1ce000f5fa84a15cbcd" dependencies = [ "memchr", ] @@ -10358,14 +10616,14 @@ version = "0.39.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", ] [[package]] name = "ws_stream_wasm" -version = "0.7.4" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7999f5f4217fe3818726b66257a4475f71e74ffd190776ad053fa159e50737f5" +checksum = "6c173014acad22e83f16403ee360115b38846fe754e735c5d9d3803fe70c6abc" dependencies = [ "async_io_stream", "futures", @@ -10374,7 +10632,7 @@ dependencies = [ "pharos", "rustc_version 0.4.1", "send_wrapper", - "thiserror 1.0.69", + "thiserror 2.0.12", "wasm-bindgen", "wasm-bindgen-futures", "web-sys", @@ -10402,52 +10660,41 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fdd20c5420375476fbd4394763288da7eb0cc0b8c11deed431a91562af7335d3" [[package]] -name = "yansi" -version = "1.0.1" +name = "xz2" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" +checksum = "388c44dc09d76f1536602ead6d325eb532f5c122f17782bd57fb47baeeb767e2" dependencies = [ - "is-terminal", + "lzma-sys", ] [[package]] -name = "zerocopy" -version = "0.7.35" +name = "yansi" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" +checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" dependencies = [ - "zerocopy-derive 0.7.35", + "is-terminal", ] [[package]] name = "zerocopy" -version = "0.8.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1702d9583232ddb9174e01bb7c15a2ab8fb1bc6f227aa1233858c351a3ba0cb" -dependencies = [ - "zerocopy-derive 0.8.25", -] - -[[package]] -name = "zerocopy-derive" -version = "0.7.35" +version = "0.8.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" +checksum = "1039dd0d3c310cf05de012d8a39ff557cb0d23087fd44cad61df08fc31907a2f" dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.101", + "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.25" +version = "0.8.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28a6e20d751156648aa063f3800b706ee209a32c0b4d9f24be3d980b01be55ef" +checksum = "9ecf5b4cc5364572d7f4c329661bcc82724222973f2cab6f050a4e5c22f75181" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -10467,33 +10714,47 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] name = "zip" -version = "2.6.1" +version = "2.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1dcb24d0152526ae49b9b96c1dcf71850ca1e0b882e4e28ed898a93c41334744" +checksum = "fabe6324e908f85a1c52063ce7aa26b68dcb7eb6dbc83a2d148403c9bc3eba50" dependencies = [ + "aes", "arbitrary", "bzip2", + "constant_time_eq", "crc32fast", "crossbeam-utils", + "deflate64", + "displaydoc", "flate2", + "getrandom 0.3.3", + "hmac", "indexmap 2.9.0", + "lzma-rs", "memchr", + "pbkdf2 0.12.2", + "sha1", + "thiserror 2.0.12", + "time", + "xz2", + "zeroize", "zopfli", + "zstd", ] [[package]] name = "zip-extract" -version = "0.2.2" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df3938d2b7d7ffd0fb7d4a86baeade9189535487d05d175401daf92306c531c0" +checksum = "25a8c9e90f27d1435088a7b540b6cc8ae6ee525d992a695f16012d2f365b3d3c" dependencies = [ "log", - "thiserror 2.0.12", + "thiserror 1.0.69", "zip", ] @@ -10508,3 +10769,31 @@ dependencies = [ "log", "simd-adler32", ] + +[[package]] +name = "zstd" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e91ee311a569c327171651566e07972200e76fcfe2242a4fa446149a3881c08a" +dependencies = [ + "zstd-safe", +] + +[[package]] +name = "zstd-safe" +version = "7.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f49c4d5f0abb602a93fb8736af2a4f4dd9512e36f7f570d66e65ff867ed3b9d" +dependencies = [ + "zstd-sys", +] + +[[package]] +name = "zstd-sys" +version = "2.0.15+zstd.1.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb81183ddd97d0c74cedf1d50d85c8d08c1b8b68ee863bdee9e706eedba1a237" +dependencies = [ + "cc", + "pkg-config", +] diff --git a/Cargo.toml b/Cargo.toml index ebefa9ace48f1..e810f505733e0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,14 +24,15 @@ members = [ "crates/script-sequence/", "crates/macros/", "crates/test-utils/", + "crates/lint/", ] resolver = "2" [workspace.package] -version = "1.1.0" +version = "1.2.3" edition = "2021" # Remember to update clippy.toml as well -rust-version = "1.83" +rust-version = "1.87" authors = ["Foundry Contributors"] license = "MIT OR Apache-2.0" homepage = "https://github.com/foundry-rs/foundry" @@ -39,6 +40,11 @@ repository = "https://github.com/foundry-rs/foundry" exclude = ["benches/", "tests/", "test-data/", "testdata/"] [workspace.lints.clippy] +borrow_as_ptr = "warn" +branches_sharing_code = "warn" +clear_with_drain = "warn" +cloned_instead_of_copied = "warn" +collection_is_never_read = "warn" dbg-macro = "warn" explicit_iter_loop = "warn" manual-string-new = "warn" @@ -51,10 +57,11 @@ literal-string-with-formatting-args = "allow" result_large_err = "allow" [workspace.lints.rust] +redundant_imports = "warn" +redundant-lifetimes = "warn" rust-2018-idioms = "warn" -# unreachable-pub = "warn" unused-must-use = "warn" -redundant-lifetimes = "warn" +# unreachable-pub = "warn" [workspace.lints.rustdoc] all = "warn" @@ -100,7 +107,7 @@ foundry-compilers.opt-level = 3 serde_json.opt-level = 3 serde.opt-level = 3 -solang-parser.opt-level = 3 +foundry-solang-parser.opt-level = 3 lalrpop-util.opt-level = 3 solar-ast.opt-level = 3 @@ -117,10 +124,17 @@ alloy-sol-types.opt-level = 3 hashbrown.opt-level = 3 foldhash.opt-level = 3 keccak.opt-level = 3 +revm.opt-level = 3 +revm-primitives.opt-level = 3 revm-interpreter.opt-level = 3 revm-precompile.opt-level = 3 -revm-primitives.opt-level = 3 -revm.opt-level = 3 +revm-database-interface.opt-level = 3 +revm-database.opt-level = 3 +revm-bytecode.opt-level = 3 +revm-state.opt-level = 3 +revm-context-interface.opt-level = 3 +revm-context.opt-level = 3 +revm-inspector.opt-level = 3 ruint.opt-level = 3 sha2.opt-level = 3 sha3.opt-level = 3 @@ -166,6 +180,7 @@ forge = { path = "crates/forge" } forge-doc = { path = "crates/doc" } forge-fmt = { path = "crates/fmt" } +forge-lint = { path = "crates/lint" } forge-verify = { path = "crates/verify" } forge-script = { path = "crates/script" } forge-sol-macro-gen = { path = "crates/sol-macro-gen" } @@ -189,61 +204,71 @@ foundry-wallets = { path = "crates/wallets" } foundry-linking = { path = "crates/linking" } # solc & compilation utilities -foundry-block-explorers = { version = "0.13.0", default-features = false } -foundry-compilers = { version = "0.14.0", default-features = false } -foundry-fork-db = "0.12" -solang-parser = "=0.3.3" -solar-parse = { version = "=0.1.2", default-features = false } -solar-sema = { version = "=0.1.2", default-features = false } - -## revm -revm = { version = "19.4.0", default-features = false } -revm-primitives = { version = "15.1.0", default-features = false } -revm-inspectors = { version = "0.16.0", features = ["serde"] } +foundry-block-explorers = { version = "0.18.0", default-features = false } +foundry-compilers = { version = "0.17.3", default-features = false } +foundry-fork-db = "0.15" +solang-parser = { version = "=0.3.9", package = "foundry-solang-parser" } +solar-ast = { version = "=0.1.4", default-features = false } +solar-parse = { version = "=0.1.4", default-features = false } +solar-interface = { version = "=0.1.4", default-features = false } +solar-sema = { version = "=0.1.4", default-features = false } ## alloy -alloy-consensus = { version = "0.12.1", default-features = false } -alloy-contract = { version = "0.12.1", default-features = false } -alloy-eips = { version = "0.12.1", default-features = false } -alloy-genesis = { version = "0.12.1", default-features = false } -alloy-json-rpc = { version = "0.12.1", default-features = false } -alloy-network = { version = "0.12.1", default-features = false } -alloy-provider = { version = "0.12.1", default-features = false } -alloy-pubsub = { version = "0.12.1", default-features = false } -alloy-rpc-client = { version = "0.12.1", default-features = false } -alloy-rpc-types = { version = "0.12.1", default-features = true } -alloy-serde = { version = "0.12.1", default-features = false } -alloy-signer = { version = "0.12.1", default-features = false } -alloy-signer-aws = { version = "0.12.1", default-features = false } -alloy-signer-gcp = { version = "0.12.1", default-features = false } -alloy-signer-ledger = { version = "0.12.1", default-features = false } -alloy-signer-local = { version = "0.12.1", default-features = false } -alloy-signer-trezor = { version = "0.12.1", default-features = false } -alloy-transport = { version = "0.12.1", default-features = false } -alloy-transport-http = { version = "0.12.1", default-features = false } -alloy-transport-ipc = { version = "0.12.1", default-features = false } -alloy-transport-ws = { version = "0.12.1", default-features = false } +alloy-consensus = { version = "1.0.11", default-features = false } +alloy-contract = { version = "1.0.11", default-features = false } +alloy-eips = { version = "1.0.11", default-features = false } +alloy-ens = { version = "1.0.11", default-features = false } +alloy-genesis = { version = "1.0.11", default-features = false } +alloy-json-rpc = { version = "1.0.11", default-features = false } +alloy-network = { version = "1.0.11", default-features = false } +alloy-provider = { version = "1.0.11", default-features = false } +alloy-pubsub = { version = "1.0.11", default-features = false } +alloy-rpc-client = { version = "1.0.11", default-features = false } +alloy-rpc-types = { version = "1.0.11", default-features = true } +alloy-serde = { version = "1.0.11", default-features = false } +alloy-signer = { version = "1.0.11", default-features = false } +alloy-signer-aws = { version = "1.0.11", default-features = false } +alloy-signer-gcp = { version = "1.0.11", default-features = false } +alloy-signer-ledger = { version = "1.0.11", default-features = false } +alloy-signer-local = { version = "1.0.11", default-features = false } +alloy-signer-trezor = { version = "1.0.11", default-features = false } +alloy-transport = { version = "1.0.11", default-features = false } +alloy-transport-http = { version = "1.0.11", default-features = false } +alloy-transport-ipc = { version = "1.0.11", default-features = false } +alloy-transport-ws = { version = "1.0.11", default-features = false } +alloy-hardforks = { version = "0.2.6", default-features = false } +alloy-op-hardforks = { version = "0.2.6", default-features = false } ## alloy-core -alloy-dyn-abi = "0.8.22" -alloy-json-abi = "0.8.22" -alloy-primitives = { version = "0.8.22", features = [ +alloy-dyn-abi = "1.1" +alloy-json-abi = "1.1" +alloy-primitives = { version = "1.1", features = [ "getrandom", "rand", "map-fxhash", "map-foldhash", ] } -alloy-sol-macro-expander = "0.8.22" -alloy-sol-macro-input = "0.8.22" -alloy-sol-types = "0.8.22" +alloy-sol-macro-expander = "1.1" +alloy-sol-macro-input = "1.1" +alloy-sol-types = "1.1" -alloy-chains = "0.1" +alloy-chains = "0.2" alloy-rlp = "0.3" -alloy-trie = "0.7.0" +alloy-trie = "0.8.1" ## op-alloy -op-alloy-consensus = "0.11.0" -op-alloy-rpc-types = "0.11.0" +op-alloy-consensus = "0.17.2" +op-alloy-rpc-types = "0.17.2" +op-alloy-flz = "0.13.1" + +## revm +revm = { version = "24.0.1", default-features = false } +revm-inspectors = { version = "0.23.0", features = ["serde"] } +op-revm = { version = "5.0.1", default-features = false } + +## alloy-evm +alloy-evm = "0.10.0" +alloy-op-evm = "0.10.0" ## cli anstream = "0.6" @@ -258,11 +283,16 @@ async-trait = "0.1" derive_more = { version = "2.0", features = ["full"] } thiserror = "2" +# allocators +mimalloc = "0.1" +tikv-jemallocator = "0.6" +tracy-client = "0.18" + # misc auto_impl = "1" aws-config = { version = "1", default-features = true } aws-sdk-kms = { version = "1", default-features = false } -bytes = "1.8" +bytes = "1.10" walkdir = "2" prettyplease = "0.2" base64 = "0.22" @@ -270,18 +300,18 @@ chrono = { version = "0.4", default-features = false, features = [ "clock", "std", ] } -axum = "0.7" +axum = "0.8" ciborium = "0.2" color-eyre = "0.6" comfy-table = "7" dirs = "6" dunce = "1" evm-disassembler = "0.5" -evmole = "0.7" +evmole = "0.8" eyre = "0.6" figment = "0.10" futures = "0.3" -hyper = "1.5" +hyper = "1.6" indicatif = "0.17" itertools = "0.14" jsonpath_lib = "0.3" @@ -289,8 +319,10 @@ k256 = "0.13" mesc = "0.3" num-format = "0.4" parking_lot = "0.12" -proptest = "1" -rand = "0.8" +proptest = "1.7.0" +rand = "0.9" +rand_08 = { package = "rand", version = "0.8" } +rand_chacha = "0.9.0" rayon = "1" regex = { version = "1", default-features = false } reqwest = { version = "0.12", default-features = false, features = [ @@ -304,8 +336,7 @@ serde_json = { version = "1.0", features = ["arbitrary_precision"] } soldeer-commands = "=0.5.4" soldeer-core = { version = "=0.5.4", features = ["serde"] } strum = "0.27" -tempfile = "3.13" -tikv-jemallocator = "0.6" +tempfile = "3.20" tokio = "1" toml = "0.8" tower = "0.5" @@ -317,19 +348,26 @@ vergen = { version = "8", default-features = false } yansi = { version = "1.0", features = ["detect-tty", "detect-env"] } path-slash = "0.2" jiff = "0.2" +heck = "0.5" + +## Pinned dependencies. Enabled for the workspace in crates/test-utils. # testing -similar-asserts = "1.6" +similar-asserts = "1.7" snapbox = { version = "0.6", features = ["json", "regex", "term-svg"] } # Use unicode-rs which has a smaller binary size than the default ICU4X as the IDNA backend, used # by the `url` crate. # See the `idna_adapter` README.md for more details: https://docs.rs/crate/idna_adapter/latest idna_adapter = "=1.1.0" +# Avoid duplicating `zip 2` and `zip 3`. Remove once all `zip` dependencies are updated to `zip 3`. +zip-extract = "=0.2.1" [patch.crates-io] solar-parse = { git = "https://github.com/paradigmxyz/solar", branch = "main" } solar-sema = { git = "https://github.com/paradigmxyz/solar", branch = "main" } +solar-ast = { git = "https://github.com/paradigmxyz/solar", branch = "main" } +solar-interface = { git = "https://github.com/paradigmxyz/solar", branch = "main" } figment = { git = "https://github.com/DaniPopes/Figment", branch = "less-mono" } @@ -368,3 +406,16 @@ figment = { git = "https://github.com/DaniPopes/Figment", branch = "less-mono" } # alloy-transport-http = { git = "https://github.com/alloy-rs/alloy", rev = "7fab7ee" } # alloy-transport-ipc = { git = "https://github.com/alloy-rs/alloy", rev = "7fab7ee" } # alloy-transport-ws = { git = "https://github.com/alloy-rs/alloy", rev = "7fab7ee" } + +## alloy-evm +# alloy-evm = { git = "https://github.com/alloy-rs/evm.git", rev = "dce752f" } +# alloy-op-evm = { git = "https://github.com/alloy-rs/evm.git", rev = "dce752f" } + +## revm +# revm = { git = "https://github.com/bluealloy/revm.git", rev = "b5808253" } +# op-revm = { git = "https://github.com/bluealloy/revm.git", rev = "b5808253" } +# revm-inspectors = { git = "https://github.com/paradigmxyz/revm-inspectors.git", rev = "a625c04" } + +## foundry +# foundry-compilers = { git = "https://github.com/foundry-rs/compilers.git", rev = "e4a9b04" } +# foundry-fork-db = { git = "https://github.com/foundry-rs/foundry-fork-db", rev = "811a61a" } diff --git a/FUNDING.json b/FUNDING.json index 52baedf3bfc3c..8f127f0f4e008 100644 --- a/FUNDING.json +++ b/FUNDING.json @@ -3,5 +3,8 @@ "ethereum": { "ownedBy": "0x86308c59a6005d012C51Eef104bBc21786aC5D2E" } + }, + "opRetro": { + "projectId": "0x4562c0630907577f433cad78c7e2cc03349d918b6c14ef982f11a2678f5999ad" } -} +} \ No newline at end of file diff --git a/Makefile b/Makefile index 24431a88458a7..0e98b93d9f48f 100644 --- a/Makefile +++ b/Makefile @@ -5,8 +5,10 @@ # Cargo profile for builds. PROFILE ?= dev + # The docker image name DOCKER_IMAGE_NAME ?= ghcr.io/foundry-rs/foundry:latest + BIN_DIR = dist/bin CARGO_TARGET_DIR ?= target @@ -22,7 +24,7 @@ endif .PHONY: help help: ## Display this help. - @awk 'BEGIN {FS = ":.*##"; printf "Usage:\n make \033[36m\033[0m\n"} /^[a-zA-Z_0-9-]+:.*?##/ { printf " \033[36m%-15s\033[0m %s\n", $$1, $$2 } /^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) } ' $(MAKEFILE_LIST) + @awk 'BEGIN {FS = ":.*##"; printf "Usage:\n make \033[36m\033[0m\n"} /^[a-zA-Z_0-9-]+:.*?##/ { printf " \033[36m%-20s\033[0m %s\n", $$1, $$2 } /^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) } ' $(MAKEFILE_LIST) ##@ Build @@ -73,47 +75,62 @@ docker-build-prepare: ## Prepare the Docker build environment. docker buildx use cross-builder; \ fi -##@ Other +##@ Test -.PHONY: clean -clean: ## Clean the project. - cargo clean +.PHONY: test-unit +test-unit: ## Run unit tests. + cargo nextest run -E 'kind(test) & !test(/\b(issue|ext_integration)/)' -## Linting +.PHONY: test-doc +test-doc: ## Run doc tests. + cargo test --doc --workspace + +.PHONY: test +test: ## Run all tests. + make test-unit && \ + make test-doc + +##@ Linting +.PHONY: fmt fmt: ## Run all formatters. cargo +nightly fmt ./.github/scripts/format.sh --check -lint-foundry: - RUSTFLAGS="-Dwarnings" cargo clippy --workspace --all-targets --all-features - -lint-codespell: ensure-codespell - codespell --skip "*.json" - -ensure-codespell: - @if ! command -v codespell &> /dev/null; then \ - echo "codespell not found. Please install it by running the command `pip install codespell` or refer to the following link for more information: https://github.com/codespell-project/codespell" \ +.PHONY: lint-clippy +lint-clippy: ## Run clippy on the codebase. + cargo +nightly clippy \ + --workspace \ + --all-targets \ + --all-features \ + -- -D warnings + +.PHONY: lint-codespell +lint-codespell: ## Run codespell on the codebase. + @command -v codespell >/dev/null || { \ + echo "codespell not found. Please install it by running the command `pipx install codespell` or refer to the following link for more information: https://github.com/codespell-project/codespell" \ exit 1; \ - fi + } + codespell --skip "*.json" +.PHONY: lint lint: ## Run all linters. make fmt && \ - make lint-foundry && \ + make lint-clippy && \ make lint-codespell -## Testing - -test-foundry: - cargo nextest run -E 'kind(test) & !test(/\b(issue|ext_integration)/)' +##@ Other -test-doc: - cargo test --doc --workspace +.PHONY: clean +clean: ## Clean the project. + cargo clean -test: ## Run all tests. - make test-foundry && \ - make test-doc +.PHONY: deny +deny: ## Perform a `cargo` deny check. + cargo deny --all-features check all -pr: ## Run all tests and linters in preparation for a PR. +.PHONY: pr +pr: ## Run all checks and tests. + make deny && \ make lint && \ - make test + make test \ No newline at end of file diff --git a/README.md b/README.md index 4fa5510481304..3d8a1f35f098f 100644 --- a/README.md +++ b/README.md @@ -13,9 +13,9 @@ [tg-support-badge]: https://img.shields.io/endpoint?color=neon&logo=telegram&label=support&style=flat-square&url=https%3A%2F%2Ftg.sumanjay.workers.dev%2Ffoundry_support [tg-support-url]: https://t.me/foundry_support -**[Install](https://book.getfoundry.sh/getting-started/installation)** -| [User Book][foundry-book] -| [Developer Docs](./docs/dev/README.md) +**[Install](https://getfoundry.sh/getting-started/installation)** +| [Docs][foundry-docs] +| [Developer Guidelines](./docs/dev/README.md) | [Contributing](./CONTRIBUTING.md) @@ -31,7 +31,7 @@ Foundry consists of: - [**Anvil**](#anvil): Fast local Ethereum development node, akin to Hardhat Network, Tenderly. - [**Chisel**](#chisel): Fast, utilitarian, and verbose Solidity REPL. -**Need help getting started with Foundry? Read the [📖 Foundry Book][foundry-book]!** +**Need help getting started with Foundry? Read the [📖 Foundry Docs][foundry-docs]!** ![Demo](.github/assets/demo.gif) @@ -82,9 +82,9 @@ foundryup **Done!** -For additional details see the [installation guide](https://book.getfoundry.sh/getting-started/installation) in the [Foundry Book][foundry-book]. +For additional details see the [installation guide](https://getfoundry.sh/getting-started/installation) in the [Foundry Docs][foundry-docs]. -If you're experiencing any issues while installing, check out [Getting Help](#getting-help) and the [FAQ](https://book.getfoundry.sh/faq). +If you're experiencing any issues while installing, check out [Getting Help](#getting-help) and the [FAQ](https://getfoundry.sh/faq). ## How Fast? @@ -151,7 +151,7 @@ forge build Compiler run successful! ``` -Let's [test](https://book.getfoundry.sh/forge/tests#tests) our contracts: +Let's [test](https://getfoundry.sh/forge/tests#tests) our contracts: ```sh forge test @@ -186,7 +186,7 @@ If you wish to simulate on-chain transactions pass a RPC URL. Run `forge --help` to explore the full list of available subcommands and their usage. -More documentation can be found in the [forge][foundry-book-forge] section of the Foundry Book. +More documentation can be found in the [forge](https://getfoundry.sh/forge/overview) section of the Foundry Docs. ## Cast @@ -218,7 +218,7 @@ Optionally, pass `--etherscan-api-key ` to decode transaction traces us Run `cast --help` to explore the full list of available subcommands and their usage. -More documentation can be found in the [cast][foundry-book-cast] section of the Foundry Book. +More documentation can be found in the [cast](https://getfoundry.sh/cast/overview) section of the Foundry Docs. ## Anvil @@ -240,7 +240,7 @@ cast block-number Run `anvil --help` to explore the full list of available features and their usage. -More documentation can be found in the [anvil][foundry-book-anvil] section of the Foundry Book. +More documentation can be found in the [anvil](https://getfoundry.sh/anvil/overview) section of the Foundry Docs. ## Chisel @@ -287,7 +287,7 @@ contract REPL { Run `chisel --help` to explore the full list of available features and their usage. -More documentation can be found in the [chisel][foundry-book-chisel] section of the Foundry Book. +More documentation can be found in the [chisel](https://getfoundry.sh/chisel/overview) section of the Foundry Docs. ## Configuration @@ -302,9 +302,9 @@ Foundry is highly configurable, allowing you to tailor it to your needs. Configu --- -You can find additional [setup and configurations guides][foundry-book-config] in the [Foundry Book][foundry-book] and in the [config crate](./crates/config/README.md): +You can find additional [setup and configurations guides](https://getfoundry.sh/config/overview) in the [Foundry Docs][foundry-docs] and in the [config crate](./crates/config/README.md): -- [Configuring with `foundry.toml`](https://book.getfoundry.sh/config/) +- [Configuring with `foundry.toml`](https://getfoundry.sh/config/overview) - [Setting up VSCode][vscode-setup] - [Shell autocompletions][shell-setup] @@ -314,7 +314,7 @@ See our [contributing guidelines](./CONTRIBUTING.md). ## Getting Help -First, see if the answer to your question can be found in the [Foundy Book][foundry-book], or in the relevant crate. +First, see if the answer to your question can be found in the [Foundy Docs][foundry-docs], or in the relevant crate. If the answer is not there: @@ -342,12 +342,7 @@ shall be dual licensed as above, without any additional terms or conditions. - All the other [contributors](https://github.com/foundry-rs/foundry/graphs/contributors) to the [ethers-rs](https://github.com/gakonst/ethers-rs), [alloy][alloy] & [foundry](https://github.com/foundry-rs/foundry) repositories and chatrooms. [solidity]: https://soliditylang.org/ -[foundry-book]: https://book.getfoundry.sh -[foundry-book-config]: https://book.getfoundry.sh/config/ -[foundry-book-forge]: https://book.getfoundry.sh/reference/forge/ -[foundry-book-anvil]: https://book.getfoundry.sh/reference/anvil/ -[foundry-book-cast]: https://book.getfoundry.sh/reference/cast/ -[foundry-book-chisel]: https://book.getfoundry.sh/reference/chisel/ +[foundry-docs]: https://getfoundry.sh [foundry-gha]: https://github.com/foundry-rs/foundry-toolchain [foundry-compilers]: https://github.com/foundry-rs/compilers [ethers-solc]: https://github.com/gakonst/ethers-rs/tree/master/ethers-solc/ @@ -359,8 +354,8 @@ shall be dual licensed as above, without any additional terms or conditions. [geb]: https://github.com/reflexer-labs/geb [benchmark-post]: https://www.paradigm.xyz/2022/03/foundry-02#blazing-fast-compilation--testing [convex]: https://github.com/mds1/convex-shutdown-simulation -[vscode-setup]: https://book.getfoundry.sh/config/vscode.html -[shell-setup]: https://book.getfoundry.sh/config/shell-autocompletion.html +[vscode-setup]: https://getfoundry.sh/config/vscode.html +[shell-setup]: https://getfoundry.sh/config/shell-autocompletion.html [foundry-0.2]: https://github.com/foundry-rs/foundry/releases/tag/nightly-5b7e4cb3c882b28f3c32ba580de27ce7381f415a [foundry-1.0]: https://github.com/foundry-rs/foundry/releases/tag/nightly-59f354c179f4e7f6d7292acb3d068815c79286d1 [dapptools]: https://github.com/dapphub/dapptools diff --git a/clippy.toml b/clippy.toml index 69dfa469ce10b..6f0698f3c87ab 100644 --- a/clippy.toml +++ b/clippy.toml @@ -1,4 +1,4 @@ -msrv = "1.83" +msrv = "1.87" # `bytes::Bytes` is included by default and `alloy_primitives::Bytes` is a wrapper around it, # so it is safe to ignore it as well. diff --git a/crates/anvil/Cargo.toml b/crates/anvil/Cargo.toml index ffbfd9d6e3348..24abbca6feeb6 100644 --- a/crates/anvil/Cargo.toml +++ b/crates/anvil/Cargo.toml @@ -27,15 +27,11 @@ foundry-cli.workspace = true foundry-common.workspace = true foundry-config.workspace = true foundry-evm.workspace = true +foundry-evm-core.workspace = true -# evm support -revm = { workspace = true, features = [ - "std", - "serde", - "memory_limit", - "c-kzg", -] } -revm-inspectors.workspace = true +# alloy +alloy-evm.workspace = true +alloy-op-evm.workspace = true alloy-primitives = { workspace = true, features = ["serde"] } alloy-consensus = { workspace = true, features = ["k256", "kzg"] } alloy-contract = { workspace = true, features = ["pubsub"] } @@ -59,8 +55,20 @@ alloy-transport.workspace = true alloy-chains.workspace = true alloy-genesis.workspace = true alloy-trie.workspace = true +alloy-hardforks.workspace = true +alloy-op-hardforks.workspace = true op-alloy-consensus = { workspace = true, features = ["serde"] } +# revm +revm = { workspace = true, features = [ + "std", + "serde", + "memory_limit", + "c-kzg", +] } +revm-inspectors.workspace = true +op-revm.workspace = true + # axum related axum.workspace = true hyper.workspace = true @@ -76,14 +84,14 @@ futures.workspace = true async-trait.workspace = true # misc -flate2 = "1.0" +flate2 = "1.1" serde_json.workspace = true serde.workspace = true thiserror.workspace = true yansi.workspace = true tempfile.workspace = true itertools.workspace = true -rand.workspace = true +rand_08.workspace = true eyre.workspace = true # cli @@ -98,12 +106,10 @@ ctrlc = { version = "3", optional = true } fdlimit = { version = "0.3", optional = true } clap_complete_fig = "4" -[target.'cfg(unix)'.dependencies] -tikv-jemallocator = { workspace = true, optional = true } - [dev-dependencies] alloy-provider = { workspace = true, features = ["txpool-api"] } alloy-pubsub.workspace = true +rand.workspace = true foundry-test-utils.workspace = true tokio = { workspace = true, features = ["full"] } @@ -112,7 +118,9 @@ op-alloy-rpc-types.workspace = true [features] default = ["cli", "jemalloc"] asm-keccak = ["alloy-primitives/asm-keccak"] -jemalloc = ["dep:tikv-jemallocator"] +jemalloc = ["foundry-cli/jemalloc"] +mimalloc = ["foundry-cli/mimalloc"] +tracy-allocator = ["foundry-cli/tracy-allocator"] cli = ["tokio/full", "cmd"] cmd = [ "clap", diff --git a/crates/anvil/bin/main.rs b/crates/anvil/bin/main.rs index e8ee4306265e1..327264335842a 100644 --- a/crates/anvil/bin/main.rs +++ b/crates/anvil/bin/main.rs @@ -2,9 +2,8 @@ use anvil::args::run; -#[cfg(all(feature = "jemalloc", unix))] #[global_allocator] -static ALLOC: tikv_jemallocator::Jemalloc = tikv_jemallocator::Jemalloc; +static ALLOC: foundry_cli::utils::Allocator = foundry_cli::utils::new_allocator(); fn main() { if let Err(err) = run() { diff --git a/crates/anvil/core/Cargo.toml b/crates/anvil/core/Cargo.toml index b1cf977d0a468..0b64c630bda9b 100644 --- a/crates/anvil/core/Cargo.toml +++ b/crates/anvil/core/Cargo.toml @@ -21,6 +21,7 @@ revm = { workspace = true, default-features = false, features = [ "memory_limit", "c-kzg", ] } +op-revm.workspace = true alloy-primitives = { workspace = true, features = ["serde", "rlp"] } alloy-rpc-types = { workspace = true, features = ["anvil", "trace"] } @@ -29,7 +30,6 @@ alloy-rlp.workspace = true alloy-eips.workspace = true alloy-consensus = { workspace = true, features = ["k256", "kzg"] } alloy-dyn-abi = { workspace = true, features = ["std", "eip712"] } -alloy-trie.workspace = true op-alloy-consensus = { workspace = true, features = ["serde"] } alloy-network.workspace = true serde.workspace = true diff --git a/crates/anvil/core/src/eth/block.rs b/crates/anvil/core/src/eth/block.rs index 8ac74bceb7c3f..fa68530e0f26b 100644 --- a/crates/anvil/core/src/eth/block.rs +++ b/crates/anvil/core/src/eth/block.rs @@ -1,9 +1,5 @@ -use super::{ - transaction::{TransactionInfo, TypedReceipt}, - trie, -}; -use alloy_consensus::{Header, EMPTY_OMMER_ROOT_HASH}; -use alloy_eips::eip2718::Encodable2718; +use super::transaction::{TransactionInfo, TypedReceipt}; +use alloy_consensus::{proofs::calculate_transaction_root, Header, EMPTY_OMMER_ROOT_HASH}; use alloy_primitives::{Address, Bloom, Bytes, B256, B64, U256}; use alloy_rlp::{RlpDecodable, RlpEncodable}; @@ -36,8 +32,9 @@ impl Block { T: Into, { let transactions: Vec<_> = transactions.into_iter().map(Into::into).collect(); - let transactions_root = - trie::ordered_trie_root(transactions.iter().map(|r| r.encoded_2718())); + let transactions1: Vec = + transactions.clone().into_iter().map(Into::into).collect(); + let transactions_root = calculate_transaction_root(&transactions1); Self { header: Header { diff --git a/crates/anvil/core/src/eth/mod.rs b/crates/anvil/core/src/eth/mod.rs index d380c3cc47e22..c1d54aa68e005 100644 --- a/crates/anvil/core/src/eth/mod.rs +++ b/crates/anvil/core/src/eth/mod.rs @@ -10,7 +10,7 @@ use alloy_rpc_types::{ filter::TraceFilter, geth::{GethDebugTracingCallOptions, GethDebugTracingOptions}, }, - BlockId, BlockNumberOrTag as BlockNumber, Filter, Index, + BlockId, BlockNumberOrTag as BlockNumber, BlockOverrides, Filter, Index, }; use alloy_serde::WithOtherFields; use foundry_common::serde_helpers::{ @@ -18,10 +18,8 @@ use foundry_common::serde_helpers::{ }; pub mod block; -pub mod proof; pub mod subscription; pub mod transaction; -pub mod trie; pub mod wallet; pub mod serde_helpers; @@ -37,7 +35,7 @@ pub struct Params { /// Represents ethereum JSON-RPC API #[derive(Clone, Debug, serde::Deserialize)] #[serde(tag = "method", content = "params")] -#[expect(clippy::large_enum_variant)] +#[allow(clippy::large_enum_variant)] pub enum EthRequest { #[serde(rename = "web3_clientVersion", with = "empty_params")] Web3ClientVersion(()), @@ -75,6 +73,9 @@ pub enum EthRequest { #[serde(rename = "eth_getAccount")] EthGetAccount(Address, Option), + #[serde(rename = "eth_getAccountInfo")] + EthGetAccountInfo(Address, Option), + #[serde(rename = "eth_getStorageAt")] EthGetStorageAt(Address, U256, Option), @@ -151,6 +152,7 @@ pub enum EthRequest { WithOtherFields, #[serde(default)] Option, #[serde(default)] Option, + #[serde(default)] Option>, ), #[serde(rename = "eth_simulateV1")] @@ -164,6 +166,7 @@ pub enum EthRequest { WithOtherFields, #[serde(default)] Option, #[serde(default)] Option, + #[serde(default)] Option>, ), #[serde(rename = "eth_getTransactionByHash", with = "sequence")] @@ -358,9 +361,38 @@ pub enum EthRequest { SetRpcUrl(String), /// Modifies the balance of an account. - #[serde(rename = "anvil_setBalance", alias = "hardhat_setBalance")] + #[serde( + rename = "anvil_setBalance", + alias = "hardhat_setBalance", + alias = "tenderly_setBalance" + )] SetBalance(Address, #[serde(deserialize_with = "deserialize_number")] U256), + /// Increases the balance of an account. + #[serde( + rename = "anvil_addBalance", + alias = "hardhat_addBalance", + alias = "tenderly_addBalance" + )] + AddBalance(Address, #[serde(deserialize_with = "deserialize_number")] U256), + + /// Modifies the ERC20 balance of an account. + #[serde( + rename = "anvil_dealERC20", + alias = "hardhat_dealERC20", + alias = "anvil_setERC20Balance" + )] + DealERC20(Address, Address, #[serde(deserialize_with = "deserialize_number")] U256), + + /// Sets the ERC20 allowance for a spender + #[serde(rename = "anvil_setERC20Allowance")] + SetERC20Allowance( + Address, + Address, + Address, + #[serde(deserialize_with = "deserialize_number")] U256, + ), + /// Sets the code of a contract #[serde(rename = "anvil_setCode", alias = "hardhat_setCode")] SetCode(Address, Bytes), @@ -640,7 +672,7 @@ pub enum EthRequest { /// Add an address to the [`DelegationCapability`] of the wallet /// - /// [`DelegationCapability`]: wallet::DelegationCapability + /// [`DelegationCapability`]: wallet::DelegationCapability #[serde(rename = "anvil_addCapability", with = "sequence")] AnvilAddCapability(Address), diff --git a/crates/anvil/core/src/eth/proof.rs b/crates/anvil/core/src/eth/proof.rs deleted file mode 100644 index 450e53e471410..0000000000000 --- a/crates/anvil/core/src/eth/proof.rs +++ /dev/null @@ -1,24 +0,0 @@ -//! Return types for `eth_getProof` - -use crate::eth::trie::KECCAK_NULL_RLP; -use alloy_primitives::{B256, U256}; -use revm::primitives::KECCAK_EMPTY; - -#[derive(Clone, Debug, PartialEq, Eq, alloy_rlp::RlpEncodable, alloy_rlp::RlpDecodable)] -pub struct BasicAccount { - pub nonce: U256, - pub balance: U256, - pub storage_root: B256, - pub code_hash: B256, -} - -impl Default for BasicAccount { - fn default() -> Self { - Self { - balance: U256::ZERO, - nonce: U256::ZERO, - code_hash: KECCAK_EMPTY, - storage_root: KECCAK_NULL_RLP, - } - } -} diff --git a/crates/anvil/core/src/eth/subscription.rs b/crates/anvil/core/src/eth/subscription.rs index 80aee56b1a001..d0cada75e830b 100644 --- a/crates/anvil/core/src/eth/subscription.rs +++ b/crates/anvil/core/src/eth/subscription.rs @@ -1,6 +1,6 @@ //! Subscription types use alloy_primitives::hex; -use rand::{distributions::Alphanumeric, thread_rng, Rng}; +use rand::{distr::Alphanumeric, rng, Rng}; use std::fmt; /// Unique subscription id @@ -48,7 +48,7 @@ impl HexIdProvider { /// Generates a random hex encoded Id pub fn gen(&self) -> String { let id: String = - (&mut thread_rng()).sample_iter(Alphanumeric).map(char::from).take(self.len).collect(); + (&mut rng()).sample_iter(Alphanumeric).map(char::from).take(self.len).collect(); let out = hex::encode(id); format!("0x{out}") } diff --git a/crates/anvil/core/src/eth/transaction/mod.rs b/crates/anvil/core/src/eth/transaction/mod.rs index a5975fa41d2c6..ae6a7262565ca 100644 --- a/crates/anvil/core/src/eth/transaction/mod.rs +++ b/crates/anvil/core/src/eth/transaction/mod.rs @@ -1,19 +1,15 @@ //! Transaction related types - -use crate::eth::transaction::optimism::DepositTransaction; use alloy_consensus::{ transaction::{ eip4844::{TxEip4844, TxEip4844Variant, TxEip4844WithSidecar}, Recovered, TxEip7702, }, - Receipt, ReceiptEnvelope, ReceiptWithBloom, Signed, TxEip1559, TxEip2930, TxEnvelope, TxLegacy, - TxReceipt, Typed2718, + Receipt, ReceiptEnvelope, ReceiptWithBloom, Signed, Transaction, TxEip1559, TxEip2930, + TxEnvelope, TxLegacy, TxReceipt, Typed2718, }; use alloy_eips::eip2718::{Decodable2718, Eip2718Error, Encodable2718}; use alloy_network::{AnyReceiptEnvelope, AnyRpcTransaction, AnyTransactionReceipt, AnyTxEnvelope}; -use alloy_primitives::{ - Address, Bloom, Bytes, Log, PrimitiveSignature, TxHash, TxKind, B256, U256, U64, -}; +use alloy_primitives::{Address, Bloom, Bytes, Log, Signature, TxHash, TxKind, B256, U256, U64}; use alloy_rlp::{length_of_length, Decodable, Encodable, Header}; use alloy_rpc_types::{ request::TransactionRequest, trace::otterscan::OtsReceipt, AccessList, ConversionError, @@ -23,15 +19,11 @@ use alloy_serde::{OtherFields, WithOtherFields}; use bytes::BufMut; use foundry_evm::traces::CallTraceNode; use op_alloy_consensus::{TxDeposit, DEPOSIT_TX_TYPE_ID}; -use revm::{ - interpreter::InstructionResult, - primitives::{OptimismFields, TxEnv}, -}; +use op_revm::{transaction::deposit::DepositTransactionParts, OpTransaction}; +use revm::{context::TxEnv, interpreter::InstructionResult}; use serde::{Deserialize, Serialize}; use std::ops::{Deref, Mul}; -pub mod optimism; - /// Converts a [TransactionRequest] into a [TypedTransactionRequest]. /// Should be removed once the call builder abstraction for providers is in place. pub fn transaction_request_to_typed( @@ -54,7 +46,8 @@ pub fn transaction_request_to_typed( access_list, sidecar, transaction_type, - .. + authorization_list, + chain_id: _, }, other, } = tx; @@ -67,7 +60,7 @@ pub fn transaction_request_to_typed( from: from.unwrap_or_default(), source_hash: other.get_deserialized::("sourceHash")?.ok()?, to: to.unwrap_or_default(), - mint: Some(mint), + mint, value: value.unwrap_or_default(), gas_limit: gas.unwrap_or_default(), is_system_transaction: other.get_deserialized::("isSystemTx")?.ok()?, @@ -75,6 +68,23 @@ pub fn transaction_request_to_typed( })); } + // EIP7702 + if transaction_type == Some(4) || authorization_list.is_some() { + return Some(TypedTransactionRequest::EIP7702(TxEip7702 { + nonce: nonce.unwrap_or_default(), + max_fee_per_gas: max_fee_per_gas.unwrap_or_default(), + max_priority_fee_per_gas: max_priority_fee_per_gas.unwrap_or_default(), + gas_limit: gas.unwrap_or_default(), + value: value.unwrap_or(U256::ZERO), + input: input.into_input().unwrap_or_default(), + // requires to + to: to?.into_to()?, + chain_id: 0, + access_list: access_list.unwrap_or_default(), + authorization_list: authorization_list.unwrap(), + })); + } + match ( transaction_type, gas_price, @@ -162,7 +172,7 @@ pub fn transaction_request_to_typed( } } -fn has_optimism_fields(other: &OtherFields) -> bool { +pub fn has_optimism_fields(other: &OtherFields) -> bool { other.contains_key("sourceHash") && other.contains_key("mint") && other.contains_key("isSystemTx") @@ -173,6 +183,7 @@ pub enum TypedTransactionRequest { Legacy(TxLegacy), EIP2930(TxEip2930), EIP1559(TxEip1559), + EIP7702(TxEip7702), EIP4844(TxEip4844Variant), Deposit(TxDeposit), } @@ -384,7 +395,9 @@ impl PendingTransaction { /// Converts the [PendingTransaction] into the [TxEnv] context that [`revm`](foundry_evm) /// expects. - pub fn to_revm_tx_env(&self) -> TxEnv { + /// + /// Base [`TxEnv`] is encapsulated in the [`op_revm::OpTransaction`] + pub fn to_revm_tx_env(&self) -> OpTransaction { fn transact_to(kind: &TxKind) -> TxKind { match kind { TxKind::Call(c) => TxKind::Call(*c), @@ -397,19 +410,20 @@ impl PendingTransaction { TypedTransaction::Legacy(tx) => { let chain_id = tx.tx().chain_id; let TxLegacy { nonce, gas_price, gas_limit, value, to, input, .. } = tx.tx(); - TxEnv { + OpTransaction::new(TxEnv { caller, - transact_to: transact_to(to), + kind: transact_to(to), data: input.clone(), chain_id, - nonce: Some(*nonce), + nonce: *nonce, value: (*value), - gas_price: U256::from(*gas_price), + gas_price: *gas_price, gas_priority_fee: None, gas_limit: *gas_limit, - access_list: vec![], + access_list: vec![].into(), + tx_type: 0, ..Default::default() - } + }) } TypedTransaction::EIP2930(tx) => { let TxEip2930 { @@ -423,19 +437,20 @@ impl PendingTransaction { access_list, .. } = tx.tx(); - TxEnv { + OpTransaction::new(TxEnv { caller, - transact_to: transact_to(to), + kind: transact_to(to), data: input.clone(), chain_id: Some(*chain_id), - nonce: Some(*nonce), + nonce: *nonce, value: *value, - gas_price: U256::from(*gas_price), + gas_price: *gas_price, gas_priority_fee: None, gas_limit: *gas_limit, - access_list: access_list.clone().into(), + access_list: access_list.clone(), + tx_type: 1, ..Default::default() - } + }) } TypedTransaction::EIP1559(tx) => { let TxEip1559 { @@ -450,19 +465,20 @@ impl PendingTransaction { access_list, .. } = tx.tx(); - TxEnv { + OpTransaction::new(TxEnv { caller, - transact_to: transact_to(to), + kind: transact_to(to), data: input.clone(), chain_id: Some(*chain_id), - nonce: Some(*nonce), + nonce: *nonce, value: *value, - gas_price: U256::from(*max_fee_per_gas), - gas_priority_fee: Some(U256::from(*max_priority_fee_per_gas)), + gas_price: *max_fee_per_gas, + gas_priority_fee: Some(*max_priority_fee_per_gas), gas_limit: *gas_limit, - access_list: access_list.clone().into(), + access_list: access_list.clone(), + tx_type: 2, ..Default::default() - } + }) } TypedTransaction::EIP4844(tx) => { let TxEip4844 { @@ -479,21 +495,22 @@ impl PendingTransaction { blob_versioned_hashes, .. } = tx.tx().tx(); - TxEnv { + OpTransaction::new(TxEnv { caller, - transact_to: TxKind::Call(*to), + kind: TxKind::Call(*to), data: input.clone(), chain_id: Some(*chain_id), - nonce: Some(*nonce), + nonce: *nonce, value: *value, - gas_price: U256::from(*max_fee_per_gas), - gas_priority_fee: Some(U256::from(*max_priority_fee_per_gas)), - max_fee_per_blob_gas: Some(U256::from(*max_fee_per_blob_gas)), + gas_price: *max_fee_per_gas, + gas_priority_fee: Some(*max_priority_fee_per_gas), + max_fee_per_blob_gas: *max_fee_per_blob_gas, blob_hashes: blob_versioned_hashes.clone(), gas_limit: *gas_limit, - access_list: access_list.clone().into(), + access_list: access_list.clone(), + tx_type: 3, ..Default::default() - } + }) } TypedTransaction::EIP7702(tx) => { let TxEip7702 { @@ -508,53 +525,60 @@ impl PendingTransaction { authorization_list, input, } = tx.tx(); - TxEnv { + + let mut tx = TxEnv { caller, - transact_to: TxKind::Call(*to), + kind: TxKind::Call(*to), data: input.clone(), chain_id: Some(*chain_id), - nonce: Some(*nonce), + nonce: *nonce, value: *value, - gas_price: U256::from(*max_fee_per_gas), - gas_priority_fee: Some(U256::from(*max_priority_fee_per_gas)), + gas_price: *max_fee_per_gas, + gas_priority_fee: Some(*max_priority_fee_per_gas), gas_limit: *gas_limit, - access_list: access_list.clone().into(), - authorization_list: Some(authorization_list.clone().into()), + access_list: access_list.clone(), + tx_type: 4, ..Default::default() - } + }; + tx.set_signed_authorization(authorization_list.clone()); + + OpTransaction::new(tx) } TypedTransaction::Deposit(tx) => { let chain_id = tx.chain_id(); - let DepositTransaction { - nonce, + let TxDeposit { source_hash, - gas_limit, - value, - kind, + to, mint, + value, + gas_limit, + is_system_transaction, input, - is_system_tx, .. } = tx; - TxEnv { + + let base = TxEnv { caller, - transact_to: transact_to(kind), + kind: transact_to(to), data: input.clone(), chain_id, - nonce: Some(*nonce), + nonce: 0, value: *value, - gas_price: U256::ZERO, + gas_price: 0, gas_priority_fee: None, gas_limit: { *gas_limit }, - access_list: vec![], - optimism: OptimismFields { - source_hash: Some(*source_hash), - mint: Some(mint.to::()), - is_system_transaction: Some(*is_system_tx), - enveloped_tx: None, - }, + access_list: vec![].into(), + tx_type: DEPOSIT_TX_TYPE_ID, ..Default::default() - } + }; + + let deposit = DepositTransactionParts { + source_hash: *source_hash, + mint: Some(*mint), + is_system_transaction: *is_system_transaction, + }; + + OpTransaction { base, deposit, enveloped_tx: None } } } } @@ -574,38 +598,7 @@ pub enum TypedTransaction { /// EIP-7702 transaction EIP7702(Signed), /// op-stack deposit transaction - Deposit(DepositTransaction), -} - -/// This is a function that demotes TypedTransaction to TransactionRequest for greater flexibility -/// over the type. -/// -/// This function is purely for convenience and specific use cases, e.g. RLP encoded transactions -/// decode to TypedTransactions where the API over TypedTransctions is quite strict. -impl TryFrom for TransactionRequest { - type Error = ConversionError; - - fn try_from(value: TypedTransaction) -> Result { - let from = - value.recover().map_err(|_| ConversionError::Custom("InvalidSignature".to_string()))?; - let essentials = value.essentials(); - let tx_type = value.r#type(); - Ok(Self { - from: Some(from), - to: Some(value.kind()), - gas_price: essentials.gas_price, - max_fee_per_gas: essentials.max_fee_per_gas, - max_priority_fee_per_gas: essentials.max_priority_fee_per_gas, - max_fee_per_blob_gas: essentials.max_fee_per_blob_gas, - gas: Some(essentials.gas_limit), - value: Some(essentials.value), - input: essentials.input.into(), - nonce: Some(essentials.nonce), - chain_id: essentials.chain_id, - transaction_type: tx_type, - ..Default::default() - }) - } + Deposit(TxDeposit), } impl TryFrom for TypedTransaction { @@ -625,7 +618,6 @@ impl TryFrom for TypedTransaction { AnyTxEnvelope::Unknown(mut tx) => { // Try to convert to deposit transaction if tx.ty() == DEPOSIT_TX_TYPE_ID { - let nonce = get_field::(&tx.inner.fields, "nonce")?; tx.inner.fields.insert("from".to_string(), serde_json::to_value(from).unwrap()); let deposit_tx = tx.inner.fields.deserialize_into::().map_err(|e| { @@ -634,29 +626,6 @@ impl TryFrom for TypedTransaction { )) })?; - let TxDeposit { - source_hash, - is_system_transaction, - value, - gas_limit, - input, - mint, - from, - to, - } = deposit_tx; - - let deposit_tx = DepositTransaction { - nonce: nonce.to(), - source_hash, - from, - kind: to, - mint: mint.map(|m| U256::from(m)).unwrap_or_default(), - value, - gas_limit, - is_system_tx: is_system_transaction, - input, - }; - return Ok(Self::Deposit(deposit_tx)); }; @@ -666,16 +635,6 @@ impl TryFrom for TypedTransaction { } } -fn get_field( - fields: &OtherFields, - key: &str, -) -> Result { - fields - .get_deserialized::(key) - .ok_or_else(|| ConversionError::Custom(format!("Missing{key}")))? - .map_err(|e| ConversionError::Custom(format!("Failed to deserialize {key}: {e}"))) -} - impl TypedTransaction { /// Returns true if the transaction uses dynamic fees: EIP1559, EIP4844 or EIP7702 pub fn is_dynamic_fee(&self) -> bool { @@ -845,9 +804,9 @@ impl TypedTransaction { access_list: t.tx().access_list.clone(), }, Self::Deposit(t) => TransactionEssentials { - kind: t.kind, + kind: t.to, input: t.input.clone(), - nonce: t.nonce, + nonce: 0, gas_limit: t.gas_limit, gas_price: Some(0), max_fee_per_gas: None, @@ -868,7 +827,7 @@ impl TypedTransaction { Self::EIP1559(t) => t.tx().nonce, Self::EIP4844(t) => t.tx().tx().nonce, Self::EIP7702(t) => t.tx().nonce, - Self::Deposit(t) => t.nonce, + Self::Deposit(_t) => 0, } } @@ -921,7 +880,7 @@ impl TypedTransaction { Self::EIP1559(t) => *t.hash(), Self::EIP4844(t) => *t.hash(), Self::EIP7702(t) => *t.hash(), - Self::Deposit(t) => t.hash(), + Self::Deposit(t) => t.tx_hash(), } } @@ -943,7 +902,7 @@ impl TypedTransaction { Self::EIP1559(tx) => tx.recover_signer(), Self::EIP4844(tx) => tx.recover_signer(), Self::EIP7702(tx) => tx.recover_signer(), - Self::Deposit(tx) => tx.recover(), + Self::Deposit(tx) => Ok(tx.from), } } @@ -955,7 +914,7 @@ impl TypedTransaction { Self::EIP1559(tx) => tx.tx().to, Self::EIP4844(tx) => TxKind::Call(tx.tx().tx().to), Self::EIP7702(tx) => TxKind::Call(tx.tx().to), - Self::Deposit(tx) => tx.kind, + Self::Deposit(tx) => tx.to, } } @@ -965,14 +924,14 @@ impl TypedTransaction { } /// Returns the Signature of the transaction - pub fn signature(&self) -> PrimitiveSignature { + pub fn signature(&self) -> Signature { match self { Self::Legacy(tx) => *tx.signature(), Self::EIP2930(tx) => *tx.signature(), Self::EIP1559(tx) => *tx.signature(), Self::EIP4844(tx) => *tx.signature(), Self::EIP7702(tx) => *tx.signature(), - Self::Deposit(_) => PrimitiveSignature::from_scalars_and_parity( + Self::Deposit(_) => Signature::from_scalars_and_parity( B256::with_last_byte(1), B256::with_last_byte(1), false, @@ -1007,7 +966,7 @@ impl Decodable for TypedTransaction { if ty != 0x7E { Ok(TxEnvelope::decode(buf)?.into()) } else { - Ok(Self::Deposit(DepositTransaction::decode_2718(buf)?)) + Ok(Self::Deposit(TxDeposit::decode_2718(buf)?)) } } } @@ -1047,7 +1006,7 @@ impl Encodable2718 for TypedTransaction { impl Decodable2718 for TypedTransaction { fn typed_decode(ty: u8, buf: &mut &[u8]) -> Result { if ty == 0x7E { - return Ok(Self::Deposit(DepositTransaction::decode(buf)?)) + return Ok(Self::Deposit(TxDeposit::decode(buf)?)) } match TxEnvelope::typed_decode(ty, buf)? { TxEnvelope::Eip2930(tx) => Ok(Self::EIP2930(tx)), @@ -1524,7 +1483,7 @@ mod tests { chain_id: Some(4), }; - let signature = PrimitiveSignature::from_str("0eb96ca19e8a77102767a41fc85a36afd5c61ccb09911cec5d3e86e193d9c5ae3a456401896b1b6055311536bf00a718568c744d8c1f9df59879e8350220ca182b").unwrap(); + let signature = Signature::from_str("0eb96ca19e8a77102767a41fc85a36afd5c61ccb09911cec5d3e86e193d9c5ae3a456401896b1b6055311536bf00a718568c744d8c1f9df59879e8350220ca182b").unwrap(); let tx = TypedTransaction::Legacy(Signed::new_unchecked( tx, @@ -1720,7 +1679,7 @@ mod tests { fn deser_to_type_tx() { let tx = r#" { - "EIP1559": { + "EIP1559": { "chainId": "0x7a69", "nonce": "0x0", "gas": "0x5209", diff --git a/crates/anvil/core/src/eth/transaction/optimism.rs b/crates/anvil/core/src/eth/transaction/optimism.rs deleted file mode 100644 index 502c5dd8c3dc6..0000000000000 --- a/crates/anvil/core/src/eth/transaction/optimism.rs +++ /dev/null @@ -1,195 +0,0 @@ -use alloy_primitives::{Address, Bytes, TxKind, B256, U256}; -use alloy_rlp::{Decodable, Encodable, Error as DecodeError, Header as RlpHeader}; -use op_alloy_consensus::TxDeposit; -use serde::{Deserialize, Serialize}; - -pub const DEPOSIT_TX_TYPE_ID: u8 = 0x7E; - -impl From for TxDeposit { - fn from(tx: DepositTransaction) -> Self { - Self { - from: tx.from, - source_hash: tx.source_hash, - to: tx.kind, - mint: Some(tx.mint.to::()), - value: tx.value, - gas_limit: tx.gas_limit, - is_system_transaction: tx.is_system_tx, - input: tx.input, - } - } -} - -/// An op-stack deposit transaction. -/// See -#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] -pub struct DepositTransaction { - pub nonce: u64, - pub source_hash: B256, - pub from: Address, - pub kind: TxKind, - pub mint: U256, - pub value: U256, - pub gas_limit: u64, - pub is_system_tx: bool, - pub input: Bytes, -} - -impl DepositTransaction { - pub fn nonce(&self) -> &u64 { - &self.nonce - } - - pub fn hash(&self) -> B256 { - let mut encoded = Vec::new(); - self.encode_2718(&mut encoded); - B256::from_slice(alloy_primitives::keccak256(encoded).as_slice()) - } - - // /// Recovers the Ethereum address which was used to sign the transaction. - pub fn recover(&self) -> Result { - Ok(self.from) - } - - pub fn chain_id(&self) -> Option { - None - } - - pub fn encode_2718(&self, out: &mut dyn alloy_rlp::BufMut) { - out.put_u8(DEPOSIT_TX_TYPE_ID); - self.encode(out); - } - - /// Encodes only the transaction's fields into the desired buffer, without a RLP header. - pub(crate) fn encode_fields(&self, out: &mut dyn alloy_rlp::BufMut) { - self.source_hash.encode(out); - self.from.encode(out); - self.kind.encode(out); - self.mint.encode(out); - self.value.encode(out); - self.gas_limit.encode(out); - self.is_system_tx.encode(out); - self.input.encode(out); - } - - /// Calculates the length of the RLP-encoded transaction's fields. - pub(crate) fn fields_len(&self) -> usize { - let mut len = 0; - len += self.source_hash.length(); - len += self.from.length(); - len += self.kind.length(); - len += self.mint.length(); - len += self.value.length(); - len += self.gas_limit.length(); - len += self.is_system_tx.length(); - len += self.input.length(); - len - } - - pub fn decode_2718(buf: &mut &[u8]) -> Result { - use bytes::Buf; - - let tx_type = *buf.first().ok_or(alloy_rlp::Error::Custom("empty slice"))?; - - if tx_type != DEPOSIT_TX_TYPE_ID { - return Err(alloy_rlp::Error::Custom("invalid tx type: expected deposit tx type")); - } - - // Skip the tx type byte - buf.advance(1); - Self::decode(buf) - } - - /// Decodes the inner fields from RLP bytes - /// - /// NOTE: This assumes a RLP header has already been decoded, and _just_ decodes the following - /// RLP fields in the following order: - /// - /// - `source_hash` - /// - `from` - /// - `kind` - /// - `mint` - /// - `value` - /// - `gas_limit` - /// - `is_system_tx` - /// - `input` - pub fn decode_inner(buf: &mut &[u8]) -> Result { - Ok(Self { - nonce: 0, - source_hash: Decodable::decode(buf)?, - from: Decodable::decode(buf)?, - kind: Decodable::decode(buf)?, - mint: Decodable::decode(buf)?, - value: Decodable::decode(buf)?, - gas_limit: Decodable::decode(buf)?, - is_system_tx: Decodable::decode(buf)?, - input: Decodable::decode(buf)?, - }) - } -} - -impl Encodable for DepositTransaction { - fn encode(&self, out: &mut dyn bytes::BufMut) { - RlpHeader { list: true, payload_length: self.fields_len() }.encode(out); - self.encode_fields(out); - } -} - -impl Decodable for DepositTransaction { - fn decode(buf: &mut &[u8]) -> alloy_rlp::Result { - let header = RlpHeader::decode(buf)?; - let remaining_len = buf.len(); - if header.payload_length > remaining_len { - return Err(alloy_rlp::Error::InputTooShort); - } - - Self::decode_inner(buf) - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_encode_decode() { - let tx = DepositTransaction { - nonce: 0, - source_hash: B256::default(), - from: Address::default(), - kind: TxKind::Call(Address::default()), - mint: U256::from(100), - value: U256::from(100), - gas_limit: 50000, - is_system_tx: false, - input: Bytes::default(), - }; - - let encoded_tx: Vec = alloy_rlp::encode(&tx); - - let decoded_tx = DepositTransaction::decode(&mut encoded_tx.as_slice()).unwrap(); - - assert_eq!(tx, decoded_tx); - } - #[test] - fn test_encode_decode_2718() { - let tx = DepositTransaction { - nonce: 0, - source_hash: B256::default(), - from: Address::default(), - kind: TxKind::Call(Address::default()), - mint: U256::from(100), - value: U256::from(100), - gas_limit: 50000, - is_system_tx: false, - input: Bytes::default(), - }; - - let mut encoded_tx: Vec = Vec::new(); - tx.encode_2718(&mut encoded_tx); - - let decoded_tx = DepositTransaction::decode_2718(&mut encoded_tx.as_slice()).unwrap(); - - assert_eq!(tx, decoded_tx); - } -} diff --git a/crates/anvil/core/src/eth/trie.rs b/crates/anvil/core/src/eth/trie.rs deleted file mode 100644 index 59c5cd910a33e..0000000000000 --- a/crates/anvil/core/src/eth/trie.rs +++ /dev/null @@ -1,30 +0,0 @@ -//! Utility functions for Ethereum adapted from - -use alloy_primitives::{fixed_bytes, B256}; -use alloy_trie::{HashBuilder, Nibbles}; -use std::collections::BTreeMap; - -/// The KECCAK of the RLP encoding of empty data. -pub const KECCAK_NULL_RLP: B256 = - fixed_bytes!("56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421"); - -/// Generates a trie root hash for a vector of values -pub fn ordered_trie_root(input: I) -> B256 -where - I: IntoIterator, - V: AsRef<[u8]>, -{ - let mut builder = HashBuilder::default(); - - let input = input - .into_iter() - .enumerate() - .map(|(i, v)| (alloy_rlp::encode(i), v)) - .collect::>(); - - for (key, value) in input { - builder.add_leaf(Nibbles::unpack(key), value.as_ref()); - } - - builder.root() -} diff --git a/crates/anvil/core/src/types.rs b/crates/anvil/core/src/types.rs index e9d9c682e12dc..e0cf8762ad9af 100644 --- a/crates/anvil/core/src/types.rs +++ b/crates/anvil/core/src/types.rs @@ -1,30 +1,6 @@ -use alloy_primitives::{Bytes, B256, U256}; +use alloy_primitives::Bytes; use alloy_rpc_types::TransactionRequest; -use serde::{Deserialize, Serialize, Serializer}; - -/// Represents the result of `eth_getWork`. -/// -/// This may or may not include the block number. -#[derive(Debug, Default, PartialEq, Eq)] -pub struct Work { - pub pow_hash: B256, - pub seed_hash: B256, - pub target: B256, - pub number: Option, -} - -impl Serialize for Work { - fn serialize(&self, s: S) -> Result - where - S: Serializer, - { - if let Some(num) = self.number { - (&self.pow_hash, &self.seed_hash, &self.target, U256::from(num)).serialize(s) - } else { - (&self.pow_hash, &self.seed_hash, &self.target).serialize(s) - } - } -} +use serde::Deserialize; /// Represents the options used in `anvil_reorg` #[derive(Debug, Clone, Deserialize)] diff --git a/crates/anvil/server/src/ws.rs b/crates/anvil/server/src/ws.rs index e31652be5a205..c082414e9e2d0 100644 --- a/crates/anvil/server/src/ws.rs +++ b/crates/anvil/server/src/ws.rs @@ -45,7 +45,7 @@ impl Sink for SocketConn { } fn start_send(self: Pin<&mut Self>, item: String) -> Result<(), Self::Error> { - self.project().0.start_send(Message::Text(item)) + self.project().0.start_send(Message::Text(item.into())) } fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { diff --git a/crates/anvil/src/cmd.rs b/crates/anvil/src/cmd.rs index 4b89f0a6dfb89..4ca9624d57f6e 100644 --- a/crates/anvil/src/cmd.rs +++ b/crates/anvil/src/cmd.rs @@ -1,10 +1,10 @@ use crate::{ config::{ForkChoice, DEFAULT_MNEMONIC}, eth::{backend::db::SerializableState, pool::transactions::TransactionOrder, EthApi}, - hardfork::OptimismHardfork, AccountGenerator, EthereumHardfork, NodeConfig, CHAIN_ID, }; use alloy_genesis::Genesis; +use alloy_op_hardforks::OpHardfork; use alloy_primitives::{utils::Unit, B256, U256}; use alloy_signer_local::coins_bip39::{English, Mnemonic}; use anvil_server::ServerConfig; @@ -13,7 +13,7 @@ use core::fmt; use foundry_common::shell; use foundry_config::{Chain, Config, FigmentProviders}; use futures::FutureExt; -use rand::{rngs::StdRng, SeedableRng}; +use rand_08::{rngs::StdRng, SeedableRng}; use std::{ future::Future, net::IpAddr, @@ -79,7 +79,7 @@ pub struct NodeArgs { /// The EVM hardfork to use. /// - /// Choose the hardfork by name, e.g. `cancun`, `shanghai`, `paris`, `london`, etc... + /// Choose the hardfork by name, e.g. `prague`, `cancun`, `shanghai`, `paris`, `london`, etc... /// [default: latest] #[arg(long)] pub hardfork: Option, @@ -219,7 +219,7 @@ impl NodeArgs { let hardfork = match &self.hardfork { Some(hf) => { if self.evm.optimism { - Some(OptimismHardfork::from_str(hf)?.into()) + Some(OpHardfork::from_str(hf)?.into()) } else { Some(EthereumHardfork::from_str(hf)?.into()) } @@ -293,7 +293,7 @@ impl NodeArgs { if let Some(ref mnemonic) = self.mnemonic { gen = gen.phrase(mnemonic); } else if let Some(count) = self.mnemonic_random { - let mut rng = rand::thread_rng(); + let mut rng = rand_08::thread_rng(); let mnemonic = match Mnemonic::::new_with_count(&mut rng, count) { Ok(mnemonic) => mnemonic.to_phrase(), Err(_) => DEFAULT_MNEMONIC.to_string(), @@ -521,7 +521,7 @@ pub struct AnvilEvmArgs { /// The block gas limit. #[arg(long, alias = "block-gas-limit", help_heading = "Environment config")] - pub gas_limit: Option, + pub gas_limit: Option, /// Disable the `call.gas_limit <= block.gas_limit` constraint. #[arg( @@ -792,8 +792,6 @@ fn duration_from_secs_f64(s: &str) -> Result { #[cfg(test)] mod tests { - use crate::EthereumHardfork; - use super::*; use std::{env, net::Ipv4Addr}; @@ -839,7 +837,7 @@ mod tests { let args: NodeArgs = NodeArgs::parse_from(["anvil", "--optimism", "--hardfork", "Regolith"]); let config = args.into_node_config().unwrap(); - assert_eq!(config.hardfork, Some(OptimismHardfork::Regolith.into())); + assert_eq!(config.hardfork, Some(OpHardfork::Regolith.into())); } #[test] diff --git a/crates/anvil/src/config.rs b/crates/anvil/src/config.rs index 43ae552adaf3f..d86515f9f8fba 100644 --- a/crates/anvil/src/config.rs +++ b/crates/anvil/src/config.rs @@ -2,6 +2,7 @@ use crate::{ eth::{ backend::{ db::{Db, SerializableState}, + env::Env, fork::{ClientFork, ClientForkConfig}, genesis::GenesisConfig, mem::fork_db::ForkedDatabase, @@ -10,13 +11,14 @@ use crate::{ fees::{INITIAL_BASE_FEE, INITIAL_GAS_PRICE}, pool::transactions::{PoolTransaction, TransactionOrder}, }, - hardfork::{ChainHardfork, OptimismHardfork}, + hardfork::{ethereum_hardfork_from_block_tag, spec_id_from_ethereum_hardfork, ChainHardfork}, mem::{self, in_memory_db::MemDb}, EthereumHardfork, FeeManager, PrecompileFactory, }; use alloy_consensus::BlockHeader; use alloy_genesis::Genesis; use alloy_network::{AnyNetwork, TransactionResponse}; +use alloy_op_hardforks::OpHardfork; use alloy_primitives::{hex, map::HashMap, utils::Unit, BlockNumber, TxHash, U256}; use alloy_provider::Provider; use alloy_rpc_types::{Block, BlockNumberOrTag}; @@ -36,13 +38,18 @@ use foundry_config::Config; use foundry_evm::{ backend::{BlockchainDb, BlockchainDbMeta, SharedBackend}, constants::DEFAULT_CREATE2_DEPLOYER, - revm::primitives::{BlockEnv, CfgEnv, CfgEnvWithHandlerCfg, EnvWithHandlerCfg, SpecId, TxEnv}, utils::apply_chain_and_block_specific_env_changes, }; +use foundry_evm_core::AsEnvMut; use itertools::Itertools; +use op_revm::OpTransaction; use parking_lot::RwLock; -use rand::thread_rng; -use revm::primitives::BlobExcessGasAndPrice; +use rand_08::thread_rng; +use revm::{ + context::{BlockEnv, CfgEnv, TxEnv}, + context_interface::block::BlobExcessGasAndPrice, + primitives::hardfork::SpecId, +}; use serde_json::{json, Value}; use std::{ fmt::Write as FmtWrite, @@ -63,7 +70,7 @@ pub const NODE_PORT: u16 = 8545; /// Default chain id of the node pub const CHAIN_ID: u64 = 31337; /// The default gas limit for all transactions -pub const DEFAULT_GAS_LIMIT: u128 = 30_000_000; +pub const DEFAULT_GAS_LIMIT: u64 = 30_000_000; /// Default mnemonic for dev accounts pub const DEFAULT_MNEMONIC: &str = "test test test test test test test test test test test junk"; @@ -86,7 +93,7 @@ pub struct NodeConfig { /// Chain ID of the EVM chain pub chain_id: Option, /// Default gas limit for all txs - pub gas_limit: Option, + pub gas_limit: Option, /// If set to `true`, disables the block gas limit pub disable_block_gas_limit: bool, /// Default gas price for all txs @@ -505,7 +512,7 @@ impl NodeConfig { pub fn get_blob_excess_gas_and_price(&self) -> BlobExcessGasAndPrice { if let Some(blob_excess_gas_and_price) = &self.blob_excess_gas_and_price { - blob_excess_gas_and_price.clone() + *blob_excess_gas_and_price } else if let Some(excess_blob_gas) = self.genesis.as_ref().and_then(|g| g.excess_blob_gas) { BlobExcessGasAndPrice::new(excess_blob_gas, false) @@ -518,13 +525,13 @@ impl NodeConfig { /// Returns the hardfork to use pub fn get_hardfork(&self) -> ChainHardfork { if self.odyssey { - return ChainHardfork::Ethereum(EthereumHardfork::PragueEOF); + return ChainHardfork::Ethereum(EthereumHardfork::default()); } if let Some(hardfork) = self.hardfork { return hardfork; } if self.enable_optimism { - return OptimismHardfork::default().into(); + return OpHardfork::default().into(); } EthereumHardfork::default().into() } @@ -587,7 +594,7 @@ impl NodeConfig { /// Sets the gas limit #[must_use] - pub fn with_gas_limit(mut self, gas_limit: Option) -> Self { + pub fn with_gas_limit(mut self, gas_limit: Option) -> Self { self.gas_limit = gas_limit; self } @@ -1018,8 +1025,9 @@ impl NodeConfig { pub(crate) async fn setup(&mut self) -> Result { // configure the revm environment - let mut cfg = - CfgEnvWithHandlerCfg::new_with_spec_id(CfgEnv::default(), self.get_hardfork().into()); + let mut cfg = CfgEnv::default(); + cfg.spec = self.get_hardfork().into(); + cfg.chain_id = self.get_chain_id(); cfg.limit_contract_code_size = self.code_size_limit; // EIP-3607 rejects transactions from senders with deployed code. @@ -1027,25 +1035,28 @@ impl NodeConfig { // caller is a contract. So we disable the check by default. cfg.disable_eip3607 = true; cfg.disable_block_gas_limit = self.disable_block_gas_limit; - cfg.handler_cfg.is_optimism = self.enable_optimism; if let Some(value) = self.memory_limit { cfg.memory_limit = value; } - let env = revm::primitives::Env { - cfg: cfg.cfg_env, - block: BlockEnv { - gas_limit: U256::from(self.gas_limit()), - basefee: U256::from(self.get_base_fee()), + let spec_id = cfg.spec; + let mut env = Env::new( + cfg, + BlockEnv { + gas_limit: self.gas_limit(), + basefee: self.get_base_fee(), ..Default::default() }, - tx: TxEnv { chain_id: self.get_chain_id().into(), ..Default::default() }, - }; - let mut env = EnvWithHandlerCfg::new(Box::new(env), cfg.handler_cfg); + OpTransaction { + base: TxEnv { chain_id: Some(self.get_chain_id()), ..Default::default() }, + ..Default::default() + }, + self.enable_optimism, + ); let fees = FeeManager::new( - cfg.handler_cfg.spec_id, + spec_id, self.get_base_fee(), !self.disable_min_priority_fee, self.get_gas_price(), @@ -1064,16 +1075,16 @@ impl NodeConfig { // --chain-id flag gets precedence over the genesis.json chain id // if self.chain_id.is_none() { - env.cfg.chain_id = genesis.config.chain_id; + env.evm_env.cfg_env.chain_id = genesis.config.chain_id; } - env.block.timestamp = U256::from(genesis.timestamp); + env.evm_env.block_env.timestamp = genesis.timestamp; if let Some(base_fee) = genesis.base_fee_per_gas { - env.block.basefee = U256::from(base_fee); + env.evm_env.block_env.basefee = base_fee.try_into()?; } if let Some(number) = genesis.number { - env.block.number = U256::from(number); + env.evm_env.block_env.number = number; } - env.block.coinbase = genesis.coinbase; + env.evm_env.block_env.beneficiary = genesis.coinbase; } let genesis = GenesisConfig { @@ -1129,7 +1140,7 @@ impl NodeConfig { pub async fn setup_fork_db( &mut self, eth_rpc_url: String, - env: &mut EnvWithHandlerCfg, + env: &mut Env, fees: &FeeManager, ) -> Result<(Arc>>, Option)> { let (db, config) = self.setup_fork_db_config(eth_rpc_url, env, fees).await?; @@ -1146,10 +1157,10 @@ impl NodeConfig { pub async fn setup_fork_db_config( &mut self, eth_rpc_url: String, - env: &mut EnvWithHandlerCfg, + env: &mut Env, fees: &FeeManager, ) -> Result<(ForkedDatabase, ClientForkConfig)> { - // TODO make provider agnostic + debug!(target: "node", ?eth_rpc_url, "setting up fork db"); let provider = Arc::new( ProviderBuilder::new(ð_rpc_url) .timeout(self.fork_request_timeout) @@ -1176,8 +1187,10 @@ impl NodeConfig { let chain_id = provider.get_chain_id().await.wrap_err("failed to fetch network chain ID")?; if alloy_chains::NamedChain::Mainnet == chain_id { - let hardfork: EthereumHardfork = fork_block_number.into(); - env.handler_cfg.spec_id = hardfork.into(); + let hardfork: EthereumHardfork = + ethereum_hardfork_from_block_tag(fork_block_number); + + env.evm_env.cfg_env.spec = spec_id_from_ethereum_hardfork(hardfork); self.hardfork = Some(ChainHardfork::Ethereum(hardfork)); } Some(U256::from(chain_id)) @@ -1221,16 +1234,16 @@ latest block number: {latest_block}" let gas_limit = self.fork_gas_limit(&block); self.gas_limit = Some(gas_limit); - env.block = BlockEnv { - number: U256::from(fork_block_number), - timestamp: U256::from(block.header.timestamp), + env.evm_env.block_env = BlockEnv { + number: fork_block_number, + timestamp: block.header.timestamp, difficulty: block.header.difficulty, // ensures prevrandao is set prevrandao: Some(block.header.mix_hash.unwrap_or_default()), - gas_limit: U256::from(gas_limit), + gas_limit, // Keep previous `coinbase` and `basefee` value - coinbase: env.block.coinbase, - basefee: env.block.basefee, + beneficiary: env.evm_env.block_env.beneficiary, + basefee: env.evm_env.block_env.basefee, ..Default::default() }; @@ -1238,11 +1251,11 @@ latest block number: {latest_block}" if self.base_fee.is_none() { if let Some(base_fee) = block.header.base_fee_per_gas { self.base_fee = Some(base_fee); - env.block.basefee = U256::from(base_fee); + env.evm_env.block_env.basefee = base_fee; // this is the base fee of the current block, but we need the base fee of // the next block let next_block_base_fee = fees.get_next_block_base_fee_per_gas( - block.header.gas_used as u128, + block.header.gas_used, gas_limit, block.header.base_fee_per_gas.unwrap_or_default(), ); @@ -1253,10 +1266,10 @@ latest block number: {latest_block}" if let (Some(blob_excess_gas), Some(blob_gas_used)) = (block.header.excess_blob_gas, block.header.blob_gas_used) { - env.block.blob_excess_gas_and_price = + env.evm_env.block_env.blob_excess_gas_and_price = Some(BlobExcessGasAndPrice::new(blob_excess_gas, false)); - let next_block_blob_excess_gas = fees - .get_next_block_blob_excess_gas(blob_excess_gas as u128, blob_gas_used as u128); + let next_block_blob_excess_gas = + fees.get_next_block_blob_excess_gas(blob_excess_gas, blob_gas_used); fees.set_blob_excess_gas_and_price(BlobExcessGasAndPrice::new( next_block_blob_excess_gas, false, @@ -1285,15 +1298,15 @@ latest block number: {latest_block}" // need to update the dev signers and env with the chain id self.set_chain_id(Some(chain_id)); - env.cfg.chain_id = chain_id; - env.tx.chain_id = chain_id.into(); + env.evm_env.cfg_env.chain_id = chain_id; + env.tx.base.chain_id = chain_id.into(); chain_id }; let override_chain_id = self.chain_id; // apply changes such as difficulty -> prevrandao and chain specifics for current chain id - apply_chain_and_block_specific_env_changes::(env, &block); + apply_chain_and_block_specific_env_changes::(env.as_env_mut(), &block); - let meta = BlockchainDbMeta::new(*env.env.clone(), eth_rpc_url.clone()); + let meta = BlockchainDbMeta::new(env.evm_env.block_env.clone(), eth_rpc_url.clone()); let block_chain_db = if self.fork_chain_id.is_some() { BlockchainDb::new_skip_check(meta, self.block_cache_path(fork_block_number)) } else { @@ -1324,10 +1337,12 @@ latest block number: {latest_block}" compute_units_per_second: self.compute_units_per_second, total_difficulty: block.header.total_difficulty.unwrap_or_default(), blob_gas_used: block.header.blob_gas_used.map(|g| g as u128), - blob_excess_gas_and_price: env.block.blob_excess_gas_and_price.clone(), + blob_excess_gas_and_price: env.evm_env.block_env.blob_excess_gas_and_price, force_transactions, }; + debug!(target: "node", fork_number=config.block_number, fork_hash=%config.block_hash, "set up fork db"); + let mut db = ForkedDatabase::new(backend, block_chain_db); // need to insert the forked block's hash @@ -1342,24 +1357,24 @@ latest block number: {latest_block}" pub(crate) fn fork_gas_limit( &self, block: &Block, - ) -> u128 { + ) -> u64 { if !self.disable_block_gas_limit { if let Some(gas_limit) = self.gas_limit { return gas_limit; } else if block.header.gas_limit() > 0 { - return block.header.gas_limit() as u128; + return block.header.gas_limit(); } } - u64::MAX as u128 + u64::MAX } /// Returns the gas limit for a non forked anvil instance /// /// Checks the config for the `disable_block_gas_limit` flag - pub(crate) fn gas_limit(&self) -> u128 { + pub(crate) fn gas_limit(&self) -> u64 { if self.disable_block_gas_limit { - return u64::MAX as u128; + return u64::MAX; } self.gas_limit.unwrap_or(DEFAULT_GAS_LIMIT) @@ -1378,7 +1393,7 @@ async fn derive_block_and_transactions( ForkChoice::Block(block_number) => { let block_number = *block_number; if block_number >= 0 { - return Ok((block_number as u64, None)) + return Ok((block_number as u64, None)); } // subtract from latest block number let latest = provider.get_block_number().await?; diff --git a/crates/anvil/src/eth/api.rs b/crates/anvil/src/eth/api.rs index 406024c270672..e4947472141d4 100644 --- a/crates/anvil/src/eth/api.rs +++ b/crates/anvil/src/eth/api.rs @@ -1,5 +1,8 @@ use super::{ - backend::mem::{state, BlockRequest, State}, + backend::{ + db::MaybeFullDatabase, + mem::{state, BlockRequest, State}, + }, sign::build_typed_transaction, }; use crate::{ @@ -27,7 +30,6 @@ use crate::{ }, filter::{EthFilter, Filters, LogsFilter}, mem::transaction_build, - revm::primitives::{BlobExcessGasAndPrice, Output}, ClientFork, LoggingManager, Miner, MiningMode, StorageInfo, }; use alloy_consensus::{ @@ -42,7 +44,7 @@ use alloy_network::{ }; use alloy_primitives::{ map::{HashMap, HashSet}, - Address, Bytes, PrimitiveSignature as Signature, TxHash, TxKind, B256, B64, U256, U64, + Address, Bytes, Signature, TxHash, TxKind, B256, B64, U256, U64, }; use alloy_provider::utils::{ eip1559_default_estimator, EIP1559_FEE_ESTIMATION_PAST_BLOCKS, @@ -54,7 +56,7 @@ use alloy_rpc_types::{ }, request::TransactionRequest, simulate::{SimulatePayload, SimulatedBlock}, - state::StateOverride, + state::{AccountOverride, EvmOverrides, StateOverridesBuilder}, trace::{ filter::TraceFilter, geth::{GethDebugTracingCallOptions, GethDebugTracingOptions, GethTrace}, @@ -62,9 +64,10 @@ use alloy_rpc_types::{ }, txpool::{TxpoolContent, TxpoolInspect, TxpoolInspectSummary, TxpoolStatus}, AccessList, AccessListResult, BlockId, BlockNumberOrTag as BlockNumber, BlockTransactions, - EIP1186AccountProofResponse, FeeHistory, Filter, FilteredParams, Index, Log, + EIP1186AccountProofResponse, FeeHistory, Filter, FilteredParams, Index, Log, Work, }; use alloy_serde::WithOtherFields; +use alloy_sol_types::{sol, SolCall, SolValue}; use alloy_transport::TransportErrorKind; use anvil_core::{ eth::{ @@ -76,23 +79,26 @@ use anvil_core::{ wallet::{WalletCapabilities, WalletError}, EthRequest, }, - types::{ReorgOptions, TransactionData, Work}, + types::{ReorgOptions, TransactionData}, }; use anvil_rpc::{error::RpcError, response::ResponseResult}; use foundry_common::provider::ProviderBuilder; -use foundry_evm::{ - backend::DatabaseError, - decode::RevertDecoder, - revm::{ - db::DatabaseRef, - interpreter::{return_ok, return_revert, InstructionResult}, - primitives::BlockEnv, - }, +use foundry_evm::{backend::DatabaseError, decode::RevertDecoder}; +use futures::{ + channel::{mpsc::Receiver, oneshot}, + StreamExt, }; -use futures::channel::{mpsc::Receiver, oneshot}; use parking_lot::RwLock; -use revm::primitives::Bytecode; +use revm::{ + bytecode::Bytecode, + context::BlockEnv, + context_interface::{block::BlobExcessGasAndPrice, result::Output}, + database::{CacheDB, DatabaseRef}, + interpreter::{return_ok, return_revert, InstructionResult}, + primitives::eip7702::PER_EMPTY_ACCOUNT_COST, +}; use std::{future::Future, sync::Arc, time::Duration}; +use tokio::sync::mpsc::{unbounded_channel, UnboundedReceiver}; /// The client version: `anvil/v{major}.{minor}.{patch}` pub const CLIENT_VERSION: &str = concat!("anvil/v", env!("CARGO_PKG_VERSION")); @@ -171,6 +177,9 @@ impl EthApi { EthRequest::EthGetAccount(addr, block) => { self.get_account(addr, block).await.to_rpc_result() } + EthRequest::EthGetAccountInfo(addr, block) => { + self.get_account_info(addr, block).await.to_rpc_result() + } EthRequest::EthGetBalance(addr, block) => { self.balance(addr, block).await.to_rpc_result() } @@ -247,18 +256,20 @@ impl EthApi { EthRequest::EthSendRawTransaction(tx) => { self.send_raw_transaction(tx).await.to_rpc_result() } - EthRequest::EthCall(call, block, overrides) => { - self.call(call, block, overrides).await.to_rpc_result() - } + EthRequest::EthCall(call, block, state_override, block_overrides) => self + .call(call, block, EvmOverrides::new(state_override, block_overrides)) + .await + .to_rpc_result(), EthRequest::EthSimulateV1(simulation, block) => { self.simulate_v1(simulation, block).await.to_rpc_result() } EthRequest::EthCreateAccessList(call, block) => { self.create_access_list(call, block).await.to_rpc_result() } - EthRequest::EthEstimateGas(call, block, overrides) => { - self.estimate_gas(call, block, overrides).await.to_rpc_result() - } + EthRequest::EthEstimateGas(call, block, state_override, block_overrides) => self + .estimate_gas(call, block, EvmOverrides::new(state_override, block_overrides)) + .await + .to_rpc_result(), EthRequest::EthGetRawTransactionByHash(hash) => { self.raw_transaction(hash).await.to_rpc_result() } @@ -345,6 +356,16 @@ impl EthApi { EthRequest::SetBalance(addr, val) => { self.anvil_set_balance(addr, val).await.to_rpc_result() } + EthRequest::AddBalance(addr, val) => { + self.anvil_add_balance(addr, val).await.to_rpc_result() + } + EthRequest::DealERC20(addr, token_addr, val) => { + self.anvil_deal_erc20(addr, token_addr, val).await.to_rpc_result() + } + EthRequest::SetERC20Allowance(owner, spender, token_addr, val) => self + .anvil_set_erc20_allowance(owner, spender, token_addr, val) + .await + .to_rpc_result(), EthRequest::SetCode(addr, code) => { self.anvil_set_code(addr, code).await.to_rpc_result() } @@ -723,6 +744,25 @@ impl EthApi { self.backend.get_account_at_block(address, Some(block_request)).await } + /// Returns the account information including balance, nonce, code and storage + pub async fn get_account_info( + &self, + address: Address, + block_number: Option, + ) -> Result { + node_info!("eth_getAccountInfo"); + let account = self + .backend + .get_account_at_block(address, Some(self.block_request(block_number).await?)) + .await?; + let code = + self.backend.get_code(address, Some(self.block_request(block_number).await?)).await?; + Ok(alloy_rpc_types::eth::AccountInfo { + balance: account.balance, + nonce: account.nonce, + code, + }) + } /// Returns content of the storage at given address. /// /// Handler for ETH RPC call: `eth_getStorageAt` @@ -962,14 +1002,15 @@ impl EthApi { node_info!("eth_signTransaction"); let from = request.from.map(Ok).unwrap_or_else(|| { - self.accounts()?.first().cloned().ok_or(BlockchainError::NoSignerAvailable) + self.accounts()?.first().copied().ok_or(BlockchainError::NoSignerAvailable) })?; let (nonce, _) = self.request_nonce(&request, from).await?; if request.gas.is_none() { // estimate if not provided - if let Ok(gas) = self.estimate_gas(request.clone(), None, None).await { + if let Ok(gas) = self.estimate_gas(request.clone(), None, EvmOverrides::default()).await + { request.gas = Some(gas.to()); } } @@ -990,13 +1031,14 @@ impl EthApi { node_info!("eth_sendTransaction"); let from = request.from.map(Ok).unwrap_or_else(|| { - self.accounts()?.first().cloned().ok_or(BlockchainError::NoSignerAvailable) + self.accounts()?.first().copied().ok_or(BlockchainError::NoSignerAvailable) })?; let (nonce, on_chain_nonce) = self.request_nonce(&request, from).await?; if request.gas.is_none() { // estimate if not provided - if let Ok(gas) = self.estimate_gas(request.clone(), None, None).await { + if let Ok(gas) = self.estimate_gas(request.clone(), None, EvmOverrides::default()).await + { request.gas = Some(gas.to()); } } @@ -1070,7 +1112,7 @@ impl EthApi { &self, request: WithOtherFields, block_number: Option, - overrides: Option, + overrides: EvmOverrides, ) -> Result { node_info!("eth_call"); let block_request = self.block_request(block_number).await?; @@ -1078,8 +1120,8 @@ impl EthApi { if let BlockRequest::Number(number) = block_request { if let Some(fork) = self.get_fork() { if fork.predates_fork(number) { - if overrides.is_some() { - return Err(BlockchainError::StateOverrideError( + if overrides.has_state() || overrides.has_block() { + return Err(BlockchainError::EvmOverrideError( "not available on past forked blocks".to_string(), )); } @@ -1201,7 +1243,7 @@ impl EthApi { &self, request: WithOtherFields, block_number: Option, - overrides: Option, + overrides: EvmOverrides, ) -> Result { node_info!("eth_estimateGas"); self.do_estimate_gas( @@ -1738,15 +1780,18 @@ impl EthApi { return Ok(()); } - // mine all the blocks - for _ in 0..blocks.to::() { - // If we have an interval, jump forwards in time to the "next" timestamp - if let Some(interval) = interval { - self.backend.time().increase_time(interval); + self.on_blocking_task(|this| async move { + // mine all the blocks + for _ in 0..blocks.to::() { + // If we have an interval, jump forwards in time to the "next" timestamp + if let Some(interval) = interval { + this.backend.time().increase_time(interval); + } + this.mine_one().await; } - - self.mine_one().await; - } + Ok(()) + }) + .await?; Ok(()) } @@ -1818,6 +1863,158 @@ impl EthApi { Ok(()) } + /// Increases the balance of an account. + /// + /// Handler for RPC call: `anvil_addBalance` + pub async fn anvil_add_balance(&self, address: Address, balance: U256) -> Result<()> { + node_info!("anvil_addBalance"); + let current_balance = self.backend.get_balance(address, None).await?; + self.backend.set_balance(address, current_balance + balance).await?; + Ok(()) + } + + /// Helper function to find the storage slot for an ERC20 function call by testing slots + /// from an access list until one produces the expected result. + /// + /// Rather than trying to reverse-engineer the storage layout, this function uses a + /// "trial and error" approach: try overriding each slot that the function accesses, + /// and see which one actually affects the function's return value. + /// + /// ## Parameters + /// - `token_address`: The ERC20 token contract address + /// - `calldata`: The encoded function call (e.g., `balanceOf(user)` or `allowance(owner, + /// spender)`) + /// - `expected_value`: The value we want to set (balance or allowance amount) + /// + /// ## Returns + /// The storage slot (B256) that contains the target ERC20 data, or an error if no slot is + /// found. + async fn find_erc20_storage_slot( + &self, + token_address: Address, + calldata: Bytes, + expected_value: U256, + ) -> Result { + let tx = TransactionRequest::default().with_to(token_address).with_input(calldata.clone()); + + // first collect all the slots that are used by the function call + let access_list_result = + self.create_access_list(WithOtherFields::new(tx.clone()), None).await?; + let access_list = access_list_result.access_list; + + // iterate over all the accessed slots and try to find the one that contains the + // target value by overriding the slot and checking the function call result + for item in access_list.0 { + if item.address != token_address { + continue; + }; + for slot in &item.storage_keys { + let account_override = AccountOverride::default().with_state_diff(std::iter::once( + (*slot, B256::from(expected_value.to_be_bytes())), + )); + + let state_override = StateOverridesBuilder::default() + .append(token_address, account_override) + .build(); + + let evm_override = EvmOverrides::state(Some(state_override)); + + let Ok(result) = + self.call(WithOtherFields::new(tx.clone()), None, evm_override).await + else { + // overriding this slot failed + continue; + }; + + let Ok(result_value) = U256::abi_decode(&result) else { + // response returned something other than a U256 + continue; + }; + + if result_value == expected_value { + return Ok(*slot); + } + } + } + + Err(BlockchainError::Message("Unable to find storage slot".to_string())) + } + + /// Deals ERC20 tokens to a address + /// + /// Handler for RPC call: `anvil_dealERC20` + pub async fn anvil_deal_erc20( + &self, + address: Address, + token_address: Address, + balance: U256, + ) -> Result<()> { + node_info!("anvil_dealERC20"); + + sol! { + #[sol(rpc)] + contract IERC20 { + function balanceOf(address target) external view returns (uint256); + } + } + + let calldata = IERC20::balanceOfCall { target: address }.abi_encode().into(); + + // Find the storage slot that contains the balance + let slot = + self.find_erc20_storage_slot(token_address, calldata, balance).await.map_err(|_| { + BlockchainError::Message("Unable to set ERC20 balance, no slot found".to_string()) + })?; + + // Set the storage slot to the desired balance + self.anvil_set_storage_at( + token_address, + U256::from_be_bytes(slot.0), + B256::from(balance.to_be_bytes()), + ) + .await?; + + Ok(()) + } + + /// Sets the ERC20 allowance for a spender + /// + /// Handler for RPC call: `anvil_set_erc20_allowance` + pub async fn anvil_set_erc20_allowance( + &self, + owner: Address, + spender: Address, + token_address: Address, + amount: U256, + ) -> Result<()> { + node_info!("anvil_setERC20Allowance"); + + sol! { + #[sol(rpc)] + contract IERC20 { + function allowance(address owner, address spender) external view returns (uint256); + } + } + + let calldata = IERC20::allowanceCall { owner, spender }.abi_encode().into(); + + // Find the storage slot that contains the allowance + let slot = + self.find_erc20_storage_slot(token_address, calldata, amount).await.map_err(|_| { + BlockchainError::Message("Unable to set ERC20 allowance, no slot found".to_string()) + })?; + + // Set the storage slot to the desired allowance + self.anvil_set_storage_at( + token_address, + U256::from_be_bytes(slot.0), + B256::from(amount.to_be_bytes()), + ) + .await?; + + Ok(()) + } + /// Sets the code of a contract. /// /// Handler for RPC call: `anvil_setCode` @@ -1936,11 +2133,11 @@ impl EthApi { let env = self.backend.env().read(); let fork_config = self.backend.get_fork(); let tx_order = self.transaction_order.read(); - let hard_fork: &str = env.handler_cfg.spec_id.into(); + let hard_fork: &str = env.evm_env.cfg_env.spec.into(); Ok(NodeInfo { current_block_number: self.backend.best_number(), - current_block_timestamp: env.block.timestamp.try_into().unwrap_or(u64::MAX), + current_block_timestamp: env.evm_env.block_env.timestamp, current_block_hash: self.backend.best_hash(), hard_fork: hard_fork.to_string(), transaction_order: match *tx_order { @@ -2051,55 +2248,61 @@ impl EthApi { for pair in pairs { let (tx_data, block_index) = pair; - let mut tx_req = match tx_data { - TransactionData::JSON(req) => WithOtherFields::new(req), + let pending = match tx_data { TransactionData::Raw(bytes) => { let mut data = bytes.as_ref(); let decoded = TypedTransaction::decode_2718(&mut data) .map_err(|_| BlockchainError::FailedToDecodeSignedTransaction)?; - let request = - TransactionRequest::try_from(decoded.clone()).map_err(|_| { - BlockchainError::RpcError(RpcError::invalid_params( - "Failed to convert raw transaction", - )) - })?; - WithOtherFields::new(request) + PendingTransaction::new(decoded)? } - }; - let from = tx_req.from.map(Ok).unwrap_or_else(|| { - self.accounts()?.first().cloned().ok_or(BlockchainError::NoSignerAvailable) - })?; - - // Get the nonce at the common block - let curr_nonce = nonces.entry(from).or_insert( - self.get_transaction_count(from, Some(common_block.header.number.into())) - .await?, - ); + TransactionData::JSON(req) => { + let mut tx_req = WithOtherFields::new(req); + let from = tx_req.from.map(Ok).unwrap_or_else(|| { + self.accounts()? + .first() + .copied() + .ok_or(BlockchainError::NoSignerAvailable) + })?; + + // Get the nonce at the common block + let curr_nonce = nonces.entry(from).or_insert( + self.get_transaction_count( + from, + Some(common_block.header.number.into()), + ) + .await?, + ); + + // Estimate gas + if tx_req.gas.is_none() { + if let Ok(gas) = self + .estimate_gas(tx_req.clone(), None, EvmOverrides::default()) + .await + { + tx_req.gas = Some(gas.to()); + } + } - // Estimate gas - if tx_req.gas.is_none() { - if let Ok(gas) = self.estimate_gas(tx_req.clone(), None, None).await { - tx_req.gas = Some(gas.to()); + // Build typed transaction request + let typed = self.build_typed_tx_request(tx_req, *curr_nonce)?; + + // Increment nonce + *curr_nonce += 1; + + // Handle signer and convert to pending transaction + if self.is_impersonated(from) { + let bypass_signature = self.impersonated_signature(&typed); + let transaction = + sign::build_typed_transaction(typed, bypass_signature)?; + self.ensure_typed_transaction_supported(&transaction)?; + PendingTransaction::with_impersonated(transaction, from) + } else { + let transaction = self.sign_request(&from, typed)?; + self.ensure_typed_transaction_supported(&transaction)?; + PendingTransaction::new(transaction)? + } } - } - - // Build typed transaction request - let typed = self.build_typed_tx_request(tx_req, *curr_nonce)?; - - // Increment nonce - *curr_nonce += 1; - - // Handle signer and convert to pending transaction - let pending = if self.is_impersonated(from) { - let bypass_signature = self.impersonated_signature(&typed); - let transaction = sign::build_typed_transaction(typed, bypass_signature)?; - self.ensure_typed_transaction_supported(&transaction)?; - PendingTransaction::with_impersonated(transaction, from) - } else { - let transaction = self.sign_request(&from, typed)?; - self.ensure_typed_transaction_supported(&transaction)?; - PendingTransaction::new(transaction)? }; let pooled = PoolTransaction::new(pending); @@ -2295,7 +2498,7 @@ impl EthApi { /// Sets the reported block number /// /// Handler for ETH RPC call: `anvil_setBlock` - pub fn anvil_set_block(&self, block_number: U256) -> Result<()> { + pub fn anvil_set_block(&self, block_number: u64) -> Result<()> { node_info!("anvil_setBlock"); self.backend.set_block_number(block_number); Ok(()) @@ -2547,7 +2750,8 @@ impl EthApi { request.from = Some(from); - let gas_limit_fut = self.estimate_gas(request.clone(), Some(BlockId::latest()), None); + let gas_limit_fut = + self.estimate_gas(request.clone(), Some(BlockId::latest()), EvmOverrides::default()); let fees_fut = self.fee_history( U256::from(EIP1559_FEE_ESTIMATION_PAST_BLOCKS), @@ -2630,10 +2834,16 @@ impl EthApi { } } - // mine all the blocks - for _ in 0..blocks_to_mine { - self.mine_one().await; - } + // this can be blocking for a bit, especially in forking mode + // + self.on_blocking_task(|this| async move { + // mine all the blocks + for _ in 0..blocks_to_mine { + this.mine_one().await; + } + Ok(()) + }) + .await?; Ok(blocks_to_mine) } @@ -2642,15 +2852,15 @@ impl EthApi { &self, request: WithOtherFields, block_number: Option, - overrides: Option, + overrides: EvmOverrides, ) -> Result { let block_request = self.block_request(block_number).await?; // check if the number predates the fork, if in fork mode if let BlockRequest::Number(number) = block_request { if let Some(fork) = self.get_fork() { if fork.predates_fork(number) { - if overrides.is_some() { - return Err(BlockchainError::StateOverrideError( + if overrides.has_state() || overrides.has_block() { + return Err(BlockchainError::EvmOverrideError( "not available on past forked blocks".to_string(), )); } @@ -2663,14 +2873,18 @@ impl EthApi { // self.on_blocking_task(|this| async move { this.backend - .with_database_at(Some(block_request), |mut state, block| { - if let Some(overrides) = overrides { - state = Box::new(state::apply_state_override( - overrides.into_iter().collect(), - state, - )?); + .with_database_at(Some(block_request), |state, mut block| { + let mut cache_db = CacheDB::new(state); + if let Some(state_overrides) = overrides.state { + state::apply_state_overrides( + state_overrides.into_iter().collect(), + &mut cache_db, + )?; } - this.do_estimate_gas_with_state(request, &state, block) + if let Some(block_overrides) = overrides.block { + state::apply_block_overrides(*block_overrides, &mut cache_db, &mut block); + } + this.do_estimate_gas_with_state(request, cache_db.as_dyn(), block) }) .await? }) @@ -2691,7 +2905,9 @@ impl EthApi { let to = request.to.as_ref().and_then(TxKind::to); // check certain fields to see if the request could be a simple transfer - let maybe_transfer = request.input.input().is_none() && + let maybe_transfer = (request.input.input().is_none() || + request.input.input().is_some_and(|data| data.is_empty())) && + request.authorization_list.is_none() && request.access_list.is_none() && request.blob_versioned_hashes.is_none(); @@ -2715,8 +2931,7 @@ impl EthApi { // get the highest possible gas limit, either the request's set value or the currently // configured gas limit - let mut highest_gas_limit = - request.gas.map_or(block_env.gas_limit.to::(), |g| g as u128); + let mut highest_gas_limit = request.gas.map_or(block_env.gas_limit.into(), |g| g as u128); let gas_price = fees.gas_price.unwrap_or_default(); // If we have non-zero gas price, cap gas limit by sender balance @@ -2854,6 +3069,26 @@ impl EthApi { self.pool.add_ready_listener() } + /// Returns a listener for pending transactions, yielding full transactions + pub fn full_pending_transactions(&self) -> UnboundedReceiver { + let (tx, rx) = unbounded_channel(); + let mut hashes = self.new_ready_transactions(); + + let this = self.clone(); + + tokio::spawn(async move { + while let Some(hash) = hashes.next().await { + if let Ok(Some(txn)) = this.transaction_by_hash(hash).await { + if tx.send(txn).is_err() { + break; + } + } + } + }); + + rx + } + /// Returns a new accessor for certain storage elements pub fn storage_info(&self) -> StorageInfo { StorageInfo::new(Arc::clone(&self.backend)) @@ -2950,6 +3185,15 @@ impl EthApi { } TypedTransactionRequest::EIP1559(m) } + Some(TypedTransactionRequest::EIP7702(mut m)) => { + m.nonce = nonce; + m.chain_id = chain_id; + m.gas_limit = gas_limit; + if max_fee_per_gas.is_none() { + m.max_fee_per_gas = self.gas_price(); + } + TypedTransactionRequest::EIP7702(m) + } Some(TypedTransactionRequest::EIP4844(m)) => { TypedTransactionRequest::EIP4844(match m { // We only accept the TxEip4844 variant which has the sidecar. @@ -3017,6 +3261,7 @@ impl EthApi { ), TypedTransactionRequest::EIP2930(_) | TypedTransactionRequest::EIP1559(_) | + TypedTransactionRequest::EIP7702(_) | TypedTransactionRequest::EIP4844(_) | TypedTransactionRequest::Deposit(_) => Signature::from_scalars_and_parity( B256::with_last_byte(1), @@ -3140,6 +3385,10 @@ fn determine_base_gas_by_kind(request: &WithOtherFields) -> TxKind::Call(_) => MIN_TRANSACTION_GAS, TxKind::Create => MIN_CREATE_GAS, }, + TypedTransactionRequest::EIP7702(req) => { + MIN_TRANSACTION_GAS + + req.authorization_list.len() as u128 * PER_EMPTY_ACCOUNT_COST as u128 + } TypedTransactionRequest::EIP2930(req) => match req.to { TxKind::Call(_) => MIN_TRANSACTION_GAS, TxKind::Create => MIN_CREATE_GAS, @@ -3165,6 +3414,8 @@ enum GasEstimationCallResult { } /// Converts the result of a call to revm EVM into a [`GasEstimationCallResult`]. +/// +/// Expected to stay up to date with: impl TryFrom, u128, State)>> for GasEstimationCallResult { type Error = BlockchainError; @@ -3178,19 +3429,26 @@ impl TryFrom, u128, State)>> for GasEs Ok((exit, output, gas, _)) => match exit { return_ok!() | InstructionResult::CallOrCreate => Ok(Self::Success(gas)), + // Revert opcodes: InstructionResult::Revert => Ok(Self::Revert(output.map(|o| o.into_data()))), + InstructionResult::CallTooDeep | + InstructionResult::OutOfFunds | + InstructionResult::CreateInitCodeStartingEF00 | + InstructionResult::InvalidEOFInitCode | + InstructionResult::InvalidExtDelegateCallTarget => Ok(Self::EvmError(exit)), + // Out of gas errors: InstructionResult::OutOfGas | InstructionResult::MemoryOOG | InstructionResult::MemoryLimitOOG | InstructionResult::PrecompileOOG | - InstructionResult::InvalidOperandOOG => Ok(Self::OutOfGas), + InstructionResult::InvalidOperandOOG | + InstructionResult::ReentrancySentryOOG => Ok(Self::OutOfGas), + // Other errors: InstructionResult::OpcodeNotFound | InstructionResult::CallNotAllowedInsideStatic | InstructionResult::StateChangeDuringStaticCall | - InstructionResult::InvalidExtDelegateCallTarget | - InstructionResult::InvalidEXTCALLTarget | InstructionResult::InvalidFEOpcode | InstructionResult::InvalidJump | InstructionResult::NotActivated | @@ -3205,17 +3463,12 @@ impl TryFrom, u128, State)>> for GasEs InstructionResult::CreateContractStartingWithEF | InstructionResult::CreateInitCodeSizeLimit | InstructionResult::FatalExternalError | - InstructionResult::OutOfFunds | - InstructionResult::CallTooDeep => Ok(Self::EvmError(exit)), - - // Handle Revm EOF InstructionResults: Not supported yet InstructionResult::ReturnContractInNotInitEOF | InstructionResult::EOFOpcodeDisabledInLegacy | - InstructionResult::EOFFunctionStackOverflow | - InstructionResult::CreateInitCodeStartingEF00 | - InstructionResult::InvalidEOFInitCode | + InstructionResult::SubRoutineStackOverflow | InstructionResult::EofAuxDataOverflow | - InstructionResult::EofAuxDataTooSmall => Ok(Self::EvmError(exit)), + InstructionResult::EofAuxDataTooSmall | + InstructionResult::InvalidEXTCALLTarget => Ok(Self::EvmError(exit)), }, } } diff --git a/crates/anvil/src/eth/backend/db.rs b/crates/anvil/src/eth/backend/db.rs index ecf1759e63a52..e39baee9193df 100644 --- a/crates/anvil/src/eth/backend/db.rs +++ b/crates/anvil/src/eth/backend/db.rs @@ -1,24 +1,24 @@ //! Helper types for working with [revm](foundry_evm::revm) -use crate::{mem::storage::MinedTransaction, revm::primitives::AccountInfo}; +use crate::mem::storage::MinedTransaction; use alloy_consensus::Header; -use alloy_primitives::{keccak256, Address, Bytes, B256, U256, U64}; +use alloy_primitives::{keccak256, map::HashMap, Address, Bytes, B256, U256}; use alloy_rpc_types::BlockId; use anvil_core::eth::{ block::Block, transaction::{MaybeImpersonatedTransaction, TransactionInfo, TypedReceipt, TypedTransaction}, }; use foundry_common::errors::FsPathError; -use foundry_evm::{ - backend::{ - BlockchainDb, DatabaseError, DatabaseResult, MemDb, RevertStateSnapshotAction, - StateSnapshot, - }, - revm::{ - db::{CacheDB, DatabaseRef, DbAccount}, - primitives::{BlockEnv, Bytecode, HashMap, KECCAK_EMPTY}, - Database, DatabaseCommit, - }, +use foundry_evm::backend::{ + BlockchainDb, DatabaseError, DatabaseResult, MemDb, RevertStateSnapshotAction, StateSnapshot, +}; +use revm::{ + bytecode::Bytecode, + context::BlockEnv, + database::{CacheDB, DatabaseRef, DbAccount}, + primitives::KECCAK_EMPTY, + state::AccountInfo, + Database, DatabaseCommit, }; use serde::{ de::{MapAccess, Visitor}, @@ -139,7 +139,7 @@ pub trait Db: fn dump_state( &self, at: BlockEnv, - best_number: U64, + best_number: u64, blocks: Vec, transactions: Vec, historical_states: Option, @@ -215,13 +215,13 @@ impl + Send + Sync + Clone + fmt::Debug> D } fn insert_block_hash(&mut self, number: U256, hash: B256) { - self.block_hashes.insert(number, hash); + self.cache.block_hashes.insert(number, hash); } fn dump_state( &self, _at: BlockEnv, - _best_number: U64, + _best_number: u64, _blocks: Vec, _transaction: Vec, _historical_states: Option, @@ -248,37 +248,37 @@ impl> MaybeFullDatabase for CacheDB { } fn maybe_as_full_db(&self) -> Option<&HashMap> { - Some(&self.accounts) + Some(&self.cache.accounts) } fn clear_into_state_snapshot(&mut self) -> StateSnapshot { - let db_accounts = std::mem::take(&mut self.accounts); + let db_accounts = std::mem::take(&mut self.cache.accounts); let mut accounts = HashMap::default(); let mut account_storage = HashMap::default(); for (addr, mut acc) in db_accounts { account_storage.insert(addr, std::mem::take(&mut acc.storage)); let mut info = acc.info; - info.code = self.contracts.remove(&info.code_hash); + info.code = self.cache.contracts.remove(&info.code_hash); accounts.insert(addr, info); } - let block_hashes = std::mem::take(&mut self.block_hashes); + let block_hashes = std::mem::take(&mut self.cache.block_hashes); StateSnapshot { accounts, storage: account_storage, block_hashes } } fn read_as_state_snapshot(&self) -> StateSnapshot { - let db_accounts = self.accounts.clone(); + let db_accounts = self.cache.accounts.clone(); let mut accounts = HashMap::default(); let mut account_storage = HashMap::default(); for (addr, acc) in db_accounts { account_storage.insert(addr, acc.storage.clone()); let mut info = acc.info; - info.code = self.contracts.get(&info.code_hash).cloned(); + info.code = self.cache.contracts.get(&info.code_hash).cloned(); accounts.insert(addr, info); } - let block_hashes = self.block_hashes.clone(); + let block_hashes = self.cache.block_hashes.clone(); StateSnapshot { accounts, storage: account_storage, block_hashes } } @@ -291,9 +291,9 @@ impl> MaybeFullDatabase for CacheDB { for (addr, mut acc) in accounts { if let Some(code) = acc.code.take() { - self.contracts.insert(acc.code_hash, code); + self.cache.contracts.insert(acc.code_hash, code); } - self.accounts.insert( + self.cache.accounts.insert( addr, DbAccount { info: acc, @@ -302,7 +302,7 @@ impl> MaybeFullDatabase for CacheDB { }, ); } - self.block_hashes = block_hashes; + self.cache.block_hashes = block_hashes; } } @@ -388,7 +388,7 @@ pub struct SerializableState { pub block: Option, pub accounts: BTreeMap, /// The best block number of the state, can be different from block number (Arbitrum chain). - pub best_block_number: Option, + pub best_block_number: Option, #[serde(default)] pub blocks: Vec, #[serde(default)] diff --git a/crates/anvil/src/eth/backend/env.rs b/crates/anvil/src/eth/backend/env.rs new file mode 100644 index 0000000000000..d4b8de797023d --- /dev/null +++ b/crates/anvil/src/eth/backend/env.rs @@ -0,0 +1,30 @@ +use alloy_evm::EvmEnv; +use foundry_evm::EnvMut; +use foundry_evm_core::AsEnvMut; +use op_revm::OpTransaction; +use revm::context::{BlockEnv, CfgEnv, TxEnv}; + +/// Helper container type for [`EvmEnv`] and [`OpTransaction`]. +#[derive(Clone, Debug, Default)] +pub struct Env { + pub evm_env: EvmEnv, + pub tx: OpTransaction, + pub is_optimism: bool, +} + +/// Helper container type for [`EvmEnv`] and [`OpTransaction`]. +impl Env { + pub fn new(cfg: CfgEnv, block: BlockEnv, tx: OpTransaction, is_optimism: bool) -> Self { + Self { evm_env: EvmEnv { cfg_env: cfg, block_env: block }, tx, is_optimism } + } +} + +impl AsEnvMut for Env { + fn as_env_mut(&mut self) -> EnvMut<'_> { + EnvMut { + block: &mut self.evm_env.block_env, + cfg: &mut self.evm_env.cfg_env, + tx: &mut self.tx.base, + } + } +} diff --git a/crates/anvil/src/eth/backend/executor.rs b/crates/anvil/src/eth/backend/executor.rs index 0b2e118020628..2ae228e39dd54 100644 --- a/crates/anvil/src/eth/backend/executor.rs +++ b/crates/anvil/src/eth/backend/executor.rs @@ -1,36 +1,42 @@ use crate::{ eth::{ - backend::{db::Db, validate::TransactionValidator}, + backend::{ + db::Db, env::Env, mem::op_haltreason_to_instruction_result, + validate::TransactionValidator, + }, error::InvalidTransactionError, pool::transactions::PoolTransaction, }, inject_precompiles, - mem::inspector::Inspector, + mem::inspector::AnvilInspector, PrecompileFactory, }; -use alloy_consensus::{constants::EMPTY_WITHDRAWALS, Receipt, ReceiptWithBloom}; -use alloy_eips::{eip2718::Encodable2718, eip7685::EMPTY_REQUESTS_HASH}; +use alloy_consensus::{ + constants::EMPTY_WITHDRAWALS, proofs::calculate_receipt_root, Receipt, ReceiptWithBloom, +}; +use alloy_eips::{eip7685::EMPTY_REQUESTS_HASH, eip7840::BlobParams}; +use alloy_evm::{eth::EthEvmContext, precompiles::PrecompilesMap, EthEvm, Evm}; +use alloy_op_evm::OpEvm; use alloy_primitives::{Bloom, BloomInput, Log, B256}; use anvil_core::eth::{ block::{Block, BlockInfo, PartialHeader}, transaction::{ DepositReceipt, PendingTransaction, TransactionInfo, TypedReceipt, TypedTransaction, }, - trie, }; -use foundry_evm::{ - backend::DatabaseError, - revm::{ - interpreter::InstructionResult, - primitives::{ - BlockEnv, CfgEnvWithHandlerCfg, EVMError, EnvWithHandlerCfg, ExecutionResult, Output, - SpecId, - }, - }, - traces::CallTraceNode, - utils::odyssey_handler_register, +use foundry_evm::{backend::DatabaseError, traces::CallTraceNode}; +use foundry_evm_core::either_evm::EitherEvm; +use op_revm::{precompiles::OpPrecompiles, L1BlockInfo, OpContext}; +use revm::{ + context::{Block as RevmBlock, BlockEnv, CfgEnv, Evm as RevmEvm, JournalTr, LocalContext}, + context_interface::result::{EVMError, ExecutionResult, Output}, + database::WrapDatabaseRef, + handler::{instructions::EthInstructions, EthPrecompiles}, + interpreter::InstructionResult, + precompile::{secp256r1::P256VERIFY, PrecompileSpecId, Precompiles}, + primitives::hardfork::SpecId, + Database, DatabaseRef, Inspector, Journal, }; -use revm::db::WrapDatabaseRef; use std::sync::Arc; /// Represents an executed transaction (transacted on the DB) @@ -68,9 +74,9 @@ impl ExecutedTransaction { TypedTransaction::EIP1559(_) => TypedReceipt::EIP1559(receipt_with_bloom), TypedTransaction::EIP4844(_) => TypedReceipt::EIP4844(receipt_with_bloom), TypedTransaction::EIP7702(_) => TypedReceipt::EIP7702(receipt_with_bloom), - TypedTransaction::Deposit(tx) => TypedReceipt::Deposit(DepositReceipt { + TypedTransaction::Deposit(_tx) => TypedReceipt::Deposit(DepositReceipt { inner: receipt_with_bloom, - deposit_nonce: Some(tx.nonce), + deposit_nonce: Some(0), deposit_receipt_version: Some(1), }), } @@ -99,7 +105,7 @@ pub struct TransactionExecutor<'a, Db: ?Sized, V: TransactionValidator> { pub pending: std::vec::IntoIter>, pub block_env: BlockEnv, /// The configuration environment and spec id - pub cfg_env: CfgEnvWithHandlerCfg, + pub cfg_env: CfgEnv, pub parent_hash: B256, /// Cumulative gas used by all executed transactions pub gas_used: u64, @@ -107,10 +113,12 @@ pub struct TransactionExecutor<'a, Db: ?Sized, V: TransactionValidator> { pub blob_gas_used: u64, pub enable_steps_tracing: bool, pub odyssey: bool, + pub optimism: bool, pub print_logs: bool, pub print_traces: bool, /// Precompiles to inject to the EVM. pub precompile_factory: Option>, + pub blob_params: BlobParams, } impl TransactionExecutor<'_, DB, V> { @@ -123,22 +131,22 @@ impl TransactionExecutor<'_, DB, V> { let mut cumulative_gas_used = 0u64; let mut invalid = Vec::new(); let mut included = Vec::new(); - let gas_limit = self.block_env.gas_limit.to::(); + let gas_limit = self.block_env.gas_limit; let parent_hash = self.parent_hash; - let block_number = self.block_env.number.to::(); + let block_number = self.block_env.number; let difficulty = self.block_env.difficulty; - let beneficiary = self.block_env.coinbase; - let timestamp = self.block_env.timestamp.to::(); - let base_fee = if self.cfg_env.handler_cfg.spec_id.is_enabled_in(SpecId::LONDON) { - Some(self.block_env.basefee.to::()) + let beneficiary = self.block_env.beneficiary; + let timestamp = self.block_env.timestamp; + let base_fee = if self.cfg_env.spec.is_enabled_in(SpecId::LONDON) { + Some(self.block_env.basefee) } else { None }; - let is_shanghai = self.cfg_env.handler_cfg.spec_id >= SpecId::SHANGHAI; - let is_cancun = self.cfg_env.handler_cfg.spec_id >= SpecId::CANCUN; - let is_prague = self.cfg_env.handler_cfg.spec_id >= SpecId::PRAGUE; - let excess_blob_gas = if is_cancun { self.block_env.get_blob_excess_gas() } else { None }; + let is_shanghai = self.cfg_env.spec >= SpecId::SHANGHAI; + let is_cancun = self.cfg_env.spec >= SpecId::CANCUN; + let is_prague = self.cfg_env.spec >= SpecId::PRAGUE; + let excess_blob_gas = if is_cancun { self.block_env.blob_excess_gas() } else { None }; let mut cumulative_blob_gas_used = if is_cancun { Some(0u64) } else { None }; for tx in self.into_iter() { @@ -211,8 +219,7 @@ impl TransactionExecutor<'_, DB, V> { transactions.push(transaction.pending_transaction.transaction.clone()); } - let receipts_root = - trie::ordered_trie_root(receipts.iter().map(Encodable2718::encoded_2718)); + let receipts_root = calculate_receipt_root(&receipts); let partial_header = PartialHeader { parent_hash, @@ -241,14 +248,14 @@ impl TransactionExecutor<'_, DB, V> { ExecutedTransactions { block, included, invalid } } - fn env_for(&self, tx: &PendingTransaction) -> EnvWithHandlerCfg { + fn env_for(&self, tx: &PendingTransaction) -> Env { let mut tx_env = tx.to_revm_tx_env(); - if self.cfg_env.handler_cfg.is_optimism { - tx_env.optimism.enveloped_tx = - Some(alloy_rlp::encode(&tx.transaction.transaction).into()); + + if self.optimism { + tx_env.enveloped_tx = Some(alloy_rlp::encode(&tx.transaction.transaction).into()); } - EnvWithHandlerCfg::new_with_cfg_env(self.cfg_env.clone(), self.block_env.clone(), tx_env) + Env::new(self.cfg_env.clone(), self.block_env.clone(), tx_env, self.optimism) } } @@ -280,8 +287,9 @@ impl Iterator for &mut TransactionExec let env = self.env_for(&transaction.pending_transaction); // check that we comply with the block's gas limit, if not disabled - let max_gas = self.gas_used.saturating_add(env.tx.gas_limit); - if !env.cfg.disable_block_gas_limit && max_gas > env.block.gas_limit.to::() { + let max_gas = self.gas_used.saturating_add(env.tx.base.gas_limit); + if !env.evm_env.cfg_env.disable_block_gas_limit && max_gas > env.evm_env.block_env.gas_limit + { return Some(TransactionExecutionOutcome::Exhausted(transaction)) } @@ -289,7 +297,7 @@ impl Iterator for &mut TransactionExec let max_blob_gas = self.blob_gas_used.saturating_add( transaction.pending_transaction.transaction.transaction.blob_gas().unwrap_or(0), ); - if max_blob_gas > alloy_eips::eip4844::MAX_DATA_GAS_PER_BLOCK { + if max_blob_gas > self.blob_params.max_blob_gas_per_block() { return Some(TransactionExecutionOutcome::BlobGasExhausted(transaction)) } @@ -300,13 +308,12 @@ impl Iterator for &mut TransactionExec &env, ) { warn!(target: "backend", "Skipping invalid tx execution [{:?}] {}", transaction.hash(), err); - return Some(TransactionExecutionOutcome::Invalid(transaction, err)) + return Some(TransactionExecutionOutcome::Invalid(transaction, err)); } let nonce = account.nonce; - // records all call and step traces - let mut inspector = Inspector::default().with_tracing(); + let mut inspector = AnvilInspector::default().with_tracing(); if self.enable_steps_tracing { inspector = inspector.with_steps_tracing(); } @@ -318,14 +325,19 @@ impl Iterator for &mut TransactionExec } let exec_result = { - let mut evm = new_evm_with_inspector(&mut *self.db, env, &mut inspector, self.odyssey); + let mut evm = new_evm_with_inspector(&mut *self.db, &env, &mut inspector); + + if self.odyssey { + inject_precompiles(&mut evm, vec![P256VERIFY]); + } + if let Some(factory) = &self.precompile_factory { inject_precompiles(&mut evm, factory.precompiles()); } trace!(target: "backend", "[{:?}] executing", transaction.hash()); // transact and commit the transaction - match evm.transact_commit() { + match evm.transact_commit(env.tx) { Ok(exec_result) => exec_result, Err(err) => { warn!(target: "backend", "[{:?}] failed to execute: {:?}", transaction.hash(), err); @@ -362,7 +374,9 @@ impl Iterator for &mut TransactionExec ExecutionResult::Revert { gas_used, output } => { (InstructionResult::Revert, gas_used, Some(Output::Call(output)), None) } - ExecutionResult::Halt { reason, gas_used } => (reason.into(), gas_used, None, None), + ExecutionResult::Halt { reason, gas_used } => { + (op_haltreason_to_instruction_result(reason), gas_used, None, None) + } }; if exit_reason == InstructionResult::OutOfGas { @@ -407,35 +421,88 @@ fn build_logs_bloom(logs: Vec, bloom: &mut Bloom) { } /// Creates a database with given database and inspector, optionally enabling odyssey features. -pub fn new_evm_with_inspector( +pub fn new_evm_with_inspector( db: DB, - env: EnvWithHandlerCfg, - inspector: &mut dyn revm::Inspector, - odyssey: bool, -) -> revm::Evm<'_, &mut dyn revm::Inspector, DB> { - let EnvWithHandlerCfg { env, handler_cfg } = env; + env: &Env, + inspector: I, +) -> EitherEvm +where + DB: Database, + I: Inspector> + Inspector>, +{ + if env.is_optimism { + let op_cfg = env.evm_env.cfg_env.clone().with_spec(op_revm::OpSpecId::ISTHMUS); + let op_context = OpContext { + journaled_state: { + let mut journal = Journal::new(db); + // Converting SpecId into OpSpecId + journal.set_spec_id(env.evm_env.cfg_env.spec); + journal + }, + block: env.evm_env.block_env.clone(), + cfg: op_cfg.clone(), + tx: env.tx.clone(), + chain: L1BlockInfo::default(), + local: LocalContext::default(), + error: Ok(()), + }; - let mut handler = revm::Handler::new(handler_cfg); + let op_precompiles = OpPrecompiles::new_with_spec(op_cfg.spec).precompiles(); + let op_evm = op_revm::OpEvm(RevmEvm::new_with_inspector( + op_context, + inspector, + EthInstructions::default(), + PrecompilesMap::from_static(op_precompiles), + )); + + let op = OpEvm::new(op_evm, true); + + EitherEvm::Op(op) + } else { + let spec = env.evm_env.cfg_env.spec; + let eth_context = EthEvmContext { + journaled_state: { + let mut journal = Journal::new(db); + journal.set_spec_id(spec); + journal + }, + block: env.evm_env.block_env.clone(), + cfg: env.evm_env.cfg_env.clone(), + tx: env.tx.base.clone(), + chain: (), + local: LocalContext::default(), + error: Ok(()), + }; - handler.append_handler_register_plain(revm::inspector_handle_register); - if odyssey { - handler.append_handler_register_plain(odyssey_handler_register); - } + let eth_precompiles = EthPrecompiles { + precompiles: Precompiles::new(PrecompileSpecId::from_spec_id(spec)), + spec, + } + .precompiles; + let eth_evm = RevmEvm::new_with_inspector( + eth_context, + inspector, + EthInstructions::default(), + PrecompilesMap::from_static(eth_precompiles), + ); - let context = revm::Context::new(revm::EvmContext::new_with_env(db, env), inspector); + let eth = EthEvm::new(eth_evm, true); - revm::Evm::new(context, handler) + EitherEvm::Eth(eth) + } } /// Creates a new EVM with the given inspector and wraps the database in a `WrapDatabaseRef`. -pub fn new_evm_with_inspector_ref<'a, DB>( - db: DB, - env: EnvWithHandlerCfg, - inspector: &mut dyn revm::Inspector>, - odyssey: bool, -) -> revm::Evm<'a, &mut dyn revm::Inspector>, WrapDatabaseRef> +pub fn new_evm_with_inspector_ref<'db, DB, I>( + db: &'db DB, + env: &Env, + inspector: &'db mut I, +) -> EitherEvm, &'db mut I, PrecompilesMap> where - DB: revm::DatabaseRef, + DB: DatabaseRef + 'db + ?Sized, + I: Inspector>> + + Inspector>>, + WrapDatabaseRef<&'db DB>: Database, { - new_evm_with_inspector(WrapDatabaseRef(db), env, inspector, odyssey) + new_evm_with_inspector(WrapDatabaseRef(db), env, inspector) } diff --git a/crates/anvil/src/eth/backend/fork.rs b/crates/anvil/src/eth/backend/fork.rs index ea8a9894cdf55..841d740abc44f 100644 --- a/crates/anvil/src/eth/backend/fork.rs +++ b/crates/anvil/src/eth/backend/fork.rs @@ -30,7 +30,7 @@ use parking_lot::{ lock_api::{RwLockReadGuard, RwLockWriteGuard}, RawRwLock, RwLock, }; -use revm::primitives::BlobExcessGasAndPrice; +use revm::context_interface::block::BlobExcessGasAndPrice; use std::{sync::Arc, time::Duration}; use tokio::sync::RwLock as AsyncRwLock; diff --git a/crates/anvil/src/eth/backend/genesis.rs b/crates/anvil/src/eth/backend/genesis.rs index 649717bd1cdf8..597dd1ee2d179 100644 --- a/crates/anvil/src/eth/backend/genesis.rs +++ b/crates/anvil/src/eth/backend/genesis.rs @@ -3,10 +3,8 @@ use crate::eth::backend::db::Db; use alloy_genesis::{Genesis, GenesisAccount}; use alloy_primitives::{Address, U256}; -use foundry_evm::{ - backend::DatabaseResult, - revm::primitives::{AccountInfo, Bytecode, KECCAK_EMPTY}, -}; +use foundry_evm::backend::DatabaseResult; +use revm::{bytecode::Bytecode, primitives::KECCAK_EMPTY, state::AccountInfo}; use tokio::sync::RwLockWriteGuard; /// Genesis settings diff --git a/crates/anvil/src/eth/backend/mem/fork_db.rs b/crates/anvil/src/eth/backend/mem/fork_db.rs index be5c3bcd7b32e..56a4944ada5a8 100644 --- a/crates/anvil/src/eth/backend/mem/fork_db.rs +++ b/crates/anvil/src/eth/backend/mem/fork_db.rs @@ -1,20 +1,20 @@ -use crate::{ - eth::backend::db::{ - Db, MaybeForkedDatabase, MaybeFullDatabase, SerializableAccountRecord, SerializableBlock, - SerializableHistoricalStates, SerializableState, SerializableTransaction, StateDb, - }, - revm::primitives::AccountInfo, +use crate::eth::backend::db::{ + Db, MaybeForkedDatabase, MaybeFullDatabase, SerializableAccountRecord, SerializableBlock, + SerializableHistoricalStates, SerializableState, SerializableTransaction, StateDb, }; -use alloy_primitives::{map::HashMap, Address, B256, U256, U64}; +use alloy_primitives::{map::HashMap, Address, B256, U256}; use alloy_rpc_types::BlockId; use foundry_evm::{ backend::{ BlockchainDb, DatabaseError, DatabaseResult, RevertStateSnapshotAction, StateSnapshot, }, fork::database::ForkDbStateSnapshot, - revm::{primitives::BlockEnv, Database}, }; -use revm::{db::DbAccount, DatabaseRef}; +use revm::{ + context::BlockEnv, + database::{Database, DatabaseRef, DbAccount}, + state::AccountInfo, +}; pub use foundry_evm::fork::database::ForkedDatabase; @@ -36,7 +36,7 @@ impl Db for ForkedDatabase { fn dump_state( &self, at: BlockEnv, - best_number: U64, + best_number: u64, blocks: Vec, transactions: Vec, historical_states: Option, @@ -44,6 +44,7 @@ impl Db for ForkedDatabase { let mut db = self.database().clone(); let accounts = self .database() + .cache .accounts .clone() .into_iter() @@ -93,7 +94,7 @@ impl MaybeFullDatabase for ForkedDatabase { } fn maybe_as_full_db(&self) -> Option<&HashMap> { - Some(&self.database().accounts) + Some(&self.database().cache.accounts) } fn clear_into_state_snapshot(&mut self) -> StateSnapshot { @@ -132,7 +133,7 @@ impl MaybeFullDatabase for ForkDbStateSnapshot { } fn maybe_as_full_db(&self) -> Option<&HashMap> { - Some(&self.local.accounts) + Some(&self.local.cache.accounts) } fn clear_into_state_snapshot(&mut self) -> StateSnapshot { diff --git a/crates/anvil/src/eth/backend/mem/in_memory_db.rs b/crates/anvil/src/eth/backend/mem/in_memory_db.rs index adc9c6f1f44b7..08a78b43406c7 100644 --- a/crates/anvil/src/eth/backend/mem/in_memory_db.rs +++ b/crates/anvil/src/eth/backend/mem/in_memory_db.rs @@ -6,15 +6,19 @@ use crate::{ SerializableHistoricalStates, SerializableState, SerializableTransaction, StateDb, }, mem::state::state_root, - revm::{db::DbAccount, primitives::AccountInfo}, }; -use alloy_primitives::{map::HashMap, Address, B256, U256, U64}; +use alloy_primitives::{map::HashMap, Address, B256, U256}; use alloy_rpc_types::BlockId; use foundry_evm::backend::{BlockchainDb, DatabaseResult, StateSnapshot}; +use revm::{ + context::BlockEnv, + database::{DatabaseRef, DbAccount}, + state::AccountInfo, +}; // reexport for convenience -pub use foundry_evm::{backend::MemDb, revm::db::DatabaseRef}; -use foundry_evm::{backend::RevertStateSnapshotAction, revm::primitives::BlockEnv}; +pub use foundry_evm::backend::MemDb; +use foundry_evm::backend::RevertStateSnapshotAction; impl Db for MemDb { fn insert_account(&mut self, address: Address, account: AccountInfo) { @@ -26,19 +30,20 @@ impl Db for MemDb { } fn insert_block_hash(&mut self, number: U256, hash: B256) { - self.inner.block_hashes.insert(number, hash); + self.inner.cache.block_hashes.insert(number, hash); } fn dump_state( &self, at: BlockEnv, - best_number: U64, + best_number: u64, blocks: Vec, transactions: Vec, historical_states: Option, ) -> DatabaseResult> { let accounts = self .inner + .cache .accounts .clone() .into_iter() @@ -92,7 +97,7 @@ impl Db for MemDb { } fn maybe_state_root(&self) -> Option { - Some(state_root(&self.inner.accounts)) + Some(state_root(&self.inner.cache.accounts)) } fn current_state(&self) -> StateDb { @@ -106,7 +111,7 @@ impl MaybeFullDatabase for MemDb { } fn maybe_as_full_db(&self) -> Option<&HashMap> { - Some(&self.inner.accounts) + Some(&self.inner.cache.accounts) } fn clear_into_state_snapshot(&mut self) -> StateSnapshot { @@ -144,7 +149,7 @@ impl MaybeForkedDatabase for MemDb { mod tests { use super::*; use alloy_primitives::{address, Bytes}; - use foundry_evm::revm::primitives::{Bytecode, KECCAK_EMPTY}; + use revm::{bytecode::Bytecode, primitives::KECCAK_EMPTY}; use std::collections::BTreeMap; // verifies that all substantial aspects of a loaded account remain the same after an account @@ -171,7 +176,7 @@ mod tests { // blocks dumping/loading tested in storage.rs let state = dump_db - .dump_state(Default::default(), U64::ZERO, Vec::new(), Vec::new(), Default::default()) + .dump_state(Default::default(), 0, Vec::new(), Vec::new(), Default::default()) .unwrap() .unwrap(); diff --git a/crates/anvil/src/eth/backend/mem/inspector.rs b/crates/anvil/src/eth/backend/mem/inspector.rs index 9a68b70612461..465febdcd28a4 100644 --- a/crates/anvil/src/eth/backend/mem/inspector.rs +++ b/crates/anvil/src/eth/backend/mem/inspector.rs @@ -1,33 +1,37 @@ //! Anvil specific [`revm::Inspector`] implementation -use crate::{eth::macros::node_info, revm::Database}; -use alloy_primitives::{Address, Log}; +use crate::eth::macros::node_info; +use alloy_evm::eth::EthEvmContext; +use alloy_primitives::{Address, Log, U256}; use foundry_evm::{ + backend::DatabaseError, call_inspectors, decode::decode_console_logs, inspectors::{LogCollector, TracingInspector}, - revm::{ - interpreter::{ - CallInputs, CallOutcome, CreateInputs, CreateOutcome, EOFCreateInputs, Interpreter, - }, - primitives::U256, - EvmContext, - }, traces::{ render_trace_arena_inner, CallTraceDecoder, SparsedTraceArena, TracingInspectorConfig, }, }; +use revm::{ + context::ContextTr, + inspector::JournalExt, + interpreter::{ + interpreter::EthInterpreter, CallInputs, CallOutcome, CreateInputs, CreateOutcome, + Interpreter, + }, + Database, Inspector, +}; /// The [`revm::Inspector`] used when transacting in the evm #[derive(Clone, Debug, Default)] -pub struct Inspector { +pub struct AnvilInspector { /// Collects all traces pub tracer: Option, /// Collects all `console.sol` logs pub log_collector: Option, } -impl Inspector { +impl AnvilInspector { /// Called after the inspecting the evm /// /// This will log all `console.sol` logs @@ -104,32 +108,38 @@ fn print_traces(tracer: TracingInspector) { node_info!("{}", render_trace_arena_inner(&traces, false, true)); } -impl revm::Inspector for Inspector { - fn initialize_interp(&mut self, interp: &mut Interpreter, ecx: &mut EvmContext) { +impl Inspector for AnvilInspector +where + D: Database, + CTX: ContextTr, + CTX::Journal: JournalExt, +{ + fn initialize_interp(&mut self, interp: &mut Interpreter, ecx: &mut CTX) { call_inspectors!([&mut self.tracer], |inspector| { inspector.initialize_interp(interp, ecx); }); } - fn step(&mut self, interp: &mut Interpreter, ecx: &mut EvmContext) { + fn step(&mut self, interp: &mut Interpreter, ecx: &mut CTX) { call_inspectors!([&mut self.tracer], |inspector| { inspector.step(interp, ecx); }); } - fn step_end(&mut self, interp: &mut Interpreter, ecx: &mut EvmContext) { + fn step_end(&mut self, interp: &mut Interpreter, ecx: &mut CTX) { call_inspectors!([&mut self.tracer], |inspector| { inspector.step_end(interp, ecx); }); } - fn log(&mut self, interp: &mut Interpreter, ecx: &mut EvmContext, log: &Log) { + fn log(&mut self, interp: &mut Interpreter, ecx: &mut CTX, log: Log) { call_inspectors!([&mut self.tracer, &mut self.log_collector], |inspector| { - inspector.log(interp, ecx, log); + // TODO: rm the log.clone + inspector.log(interp, ecx, log.clone()); }); } - fn call(&mut self, ecx: &mut EvmContext, inputs: &mut CallInputs) -> Option { + fn call(&mut self, ecx: &mut CTX, inputs: &mut CallInputs) -> Option { call_inspectors!( #[ret] [&mut self.tracer, &mut self.log_collector], @@ -138,24 +148,13 @@ impl revm::Inspector for Inspector { None } - fn call_end( - &mut self, - ecx: &mut EvmContext, - inputs: &CallInputs, - outcome: CallOutcome, - ) -> CallOutcome { + fn call_end(&mut self, ecx: &mut CTX, inputs: &CallInputs, outcome: &mut CallOutcome) { if let Some(tracer) = &mut self.tracer { - return tracer.call_end(ecx, inputs, outcome); + tracer.call_end(ecx, inputs, outcome); } - - outcome } - fn create( - &mut self, - ecx: &mut EvmContext, - inputs: &mut CreateInputs, - ) -> Option { + fn create(&mut self, ecx: &mut CTX, inputs: &mut CreateInputs) -> Option { if let Some(tracer) = &mut self.tracer { if let Some(out) = tracer.create(ecx, inputs) { return Some(out); @@ -164,51 +163,16 @@ impl revm::Inspector for Inspector { None } - fn create_end( - &mut self, - ecx: &mut EvmContext, - inputs: &CreateInputs, - outcome: CreateOutcome, - ) -> CreateOutcome { + fn create_end(&mut self, ecx: &mut CTX, inputs: &CreateInputs, outcome: &mut CreateOutcome) { if let Some(tracer) = &mut self.tracer { - return tracer.create_end(ecx, inputs, outcome); + tracer.create_end(ecx, inputs, outcome); } - - outcome - } - - #[inline] - fn eofcreate( - &mut self, - ecx: &mut EvmContext, - inputs: &mut EOFCreateInputs, - ) -> Option { - if let Some(tracer) = &mut self.tracer { - if let Some(out) = tracer.eofcreate(ecx, inputs) { - return Some(out); - } - } - None - } - - #[inline] - fn eofcreate_end( - &mut self, - ecx: &mut EvmContext, - inputs: &EOFCreateInputs, - outcome: CreateOutcome, - ) -> CreateOutcome { - if let Some(tracer) = &mut self.tracer { - return tracer.eofcreate_end(ecx, inputs, outcome); - } - - outcome } #[inline] fn selfdestruct(&mut self, contract: Address, target: Address, value: U256) { if let Some(tracer) = &mut self.tracer { - revm::Inspector::::selfdestruct(tracer, contract, target, value); + Inspector::>::selfdestruct(tracer, contract, target, value); } } } diff --git a/crates/anvil/src/eth/backend/mem/mod.rs b/crates/anvil/src/eth/backend/mem/mod.rs index 2f295ed3311b9..d0cc3b8795404 100644 --- a/crates/anvil/src/eth/backend/mem/mod.rs +++ b/crates/anvil/src/eth/backend/mem/mod.rs @@ -1,13 +1,13 @@ //! In-memory blockchain backend. use self::state::trie_storage; -use super::executor::new_evm_with_inspector_ref; use crate::{ config::PruneStateHistoryConfig, eth::{ backend::{ cheats::CheatsManager, db::{Db, MaybeFullDatabase, SerializableState}, + env::Env, executor::{ExecutedTransactions, TransactionExecutor}, fork::ClientFork, genesis::GenesisConfig, @@ -28,31 +28,41 @@ use crate::{ }, inject_precompiles, mem::{ - inspector::Inspector, + inspector::AnvilInspector, storage::{BlockchainStorage, InMemoryBlockStates, MinedBlockOutcome}, }, - revm::{db::DatabaseRef, primitives::AccountInfo}, ForkChoice, NodeConfig, PrecompileFactory, }; use alloy_chains::NamedChain; use alloy_consensus::{ - transaction::Recovered, Account, BlockHeader, Header, Receipt, ReceiptWithBloom, Signed, + proofs::{calculate_receipt_root, calculate_transaction_root}, + transaction::Recovered, + Account, BlockHeader, EnvKzgSettings, Header, Receipt, ReceiptWithBloom, Signed, Transaction as TransactionTrait, TxEnvelope, }; -use alloy_eips::{eip1559::BaseFeeParams, eip4844::MAX_BLOBS_PER_BLOCK}; +use alloy_eips::{ + eip1559::BaseFeeParams, + eip2718::{ + EIP1559_TX_TYPE_ID, EIP2930_TX_TYPE_ID, EIP4844_TX_TYPE_ID, EIP7702_TX_TYPE_ID, + LEGACY_TX_TYPE_ID, + }, + eip7840::BlobParams, +}; +use alloy_evm::{eth::EthEvmContext, precompiles::PrecompilesMap, Database, Evm}; use alloy_network::{ AnyHeader, AnyRpcBlock, AnyRpcHeader, AnyRpcTransaction, AnyTxEnvelope, AnyTxType, EthereumWallet, UnknownTxEnvelope, UnknownTypedTransaction, }; use alloy_primitives::{ - address, hex, keccak256, utils::Unit, Address, Bytes, TxHash, TxKind, B256, U256, U64, + address, hex, keccak256, logs_bloom, map::HashMap, utils::Unit, Address, Bytes, TxHash, TxKind, + B256, U256, U64, }; use alloy_rpc_types::{ anvil::Forking, request::TransactionRequest, serde_helpers::JsonStorageKey, simulate::{SimBlock, SimCallResult, SimulatePayload, SimulatedBlock}, - state::StateOverride, + state::EvmOverrides, trace::{ filter::TraceFilter, geth::{ @@ -63,7 +73,7 @@ use alloy_rpc_types::{ }, AccessList, Block as AlloyBlock, BlockId, BlockNumberOrTag as BlockNumber, BlockTransactions, EIP1186AccountProofResponse as AccountProof, EIP1186StorageProof as StorageProof, Filter, - FilteredParams, Header as AlloyHeader, Index, Log, Transaction, TransactionReceipt, + Header as AlloyHeader, Index, Log, Transaction, TransactionReceipt, }; use alloy_serde::{OtherFields, WithOtherFields}; use alloy_signer::Signature; @@ -72,7 +82,7 @@ use alloy_trie::{proof::ProofRetainer, HashBuilder, Nibbles}; use anvil_core::eth::{ block::{Block, BlockInfo}, transaction::{ - optimism::DepositTransaction, transaction_request_to_typed, DepositReceipt, + has_optimism_fields, transaction_request_to_typed, DepositReceipt, MaybeImpersonatedTransaction, PendingTransaction, ReceiptResponse, TransactionInfo, TypedReceipt, TypedTransaction, }, @@ -87,24 +97,27 @@ use foundry_evm::{ constants::DEFAULT_CREATE2_DEPLOYER_RUNTIME_CODE, decode::RevertDecoder, inspectors::AccessListInspector, - revm::{ - db::CacheDB, - interpreter::InstructionResult, - primitives::{ - BlockEnv, CfgEnvWithHandlerCfg, EnvWithHandlerCfg, ExecutionResult, Output, SpecId, - TxEnv, KECCAK_EMPTY, - }, - }, traces::TracingInspectorConfig, }; +use foundry_evm_core::either_evm::EitherEvm; use futures::channel::mpsc::{unbounded, UnboundedSender}; -use op_alloy_consensus::{TxDeposit, DEPOSIT_TX_TYPE_ID}; +use op_alloy_consensus::DEPOSIT_TX_TYPE_ID; +use op_revm::{ + transaction::deposit::DepositTransactionParts, OpContext, OpHaltReason, OpTransaction, +}; use parking_lot::{Mutex, RwLock}; use revm::{ - db::WrapDatabaseRef, - interpreter::Host, - primitives::{BlobExcessGasAndPrice, HashMap, OptimismFields, ResultAndState}, - DatabaseCommit, + context::{Block as RevmBlock, BlockEnv, TxEnv}, + context_interface::{ + block::BlobExcessGasAndPrice, + result::{ExecutionResult, Output, ResultAndState}, + }, + database::{CacheDB, DatabaseRef, WrapDatabaseRef}, + interpreter::InstructionResult, + precompile::secp256r1::P256VERIFY, + primitives::{hardfork::SpecId, KECCAK_EMPTY}, + state::AccountInfo, + DatabaseCommit, Inspector, }; use revm_inspectors::transfer::TransferInspector; use std::{ @@ -118,6 +131,8 @@ use std::{ use storage::{Blockchain, MinedTransaction, DEFAULT_HISTORY_LIMIT}; use tokio::sync::RwLock as AsyncRwLock; +use super::executor::new_evm_with_inspector_ref; + pub mod cache; pub mod fork_db; pub mod in_memory_db; @@ -165,29 +180,30 @@ impl BlockRequest { pub struct Backend { /// Access to [`revm::Database`] abstraction. /// - /// This will be used in combination with [`revm::Evm`] and is responsible for feeding data to - /// the evm during its execution. + /// This will be used in combination with [`alloy_evm::Evm`] and is responsible for feeding + /// data to the evm during its execution. /// /// At time of writing, there are two different types of `Db`: /// - [`MemDb`](crate::mem::in_memory_db::MemDb): everything is stored in memory /// - [`ForkDb`](crate::mem::fork_db::ForkedDatabase): forks off a remote client, missing /// data is retrieved via RPC-calls /// - /// In order to commit changes to the [`revm::Database`], the [`revm::Evm`] requires mutable - /// access, which requires a write-lock from this `db`. In forking mode, the time during - /// which the write-lock is active depends on whether the `ForkDb` can provide all requested - /// data from memory or whether it has to retrieve it via RPC calls first. This means that it - /// potentially blocks for some time, even taking into account the rate limits of RPC - /// endpoints. Therefore the `Db` is guarded by a `tokio::sync::RwLock` here so calls that - /// need to read from it, while it's currently written to, don't block. E.g. a new block is - /// currently mined and a new [`Self::set_storage_at()`] request is being executed. + /// In order to commit changes to the [`revm::Database`], the [`alloy_evm::Evm`] requires + /// mutable access, which requires a write-lock from this `db`. In forking mode, the time + /// during which the write-lock is active depends on whether the `ForkDb` can provide all + /// requested data from memory or whether it has to retrieve it via RPC calls first. This + /// means that it potentially blocks for some time, even taking into account the rate + /// limits of RPC endpoints. Therefore the `Db` is guarded by a `tokio::sync::RwLock` here + /// so calls that need to read from it, while it's currently written to, don't block. E.g. + /// a new block is currently mined and a new [`Self::set_storage_at()`] request is being + /// executed. db: Arc>>, /// stores all block related data in memory. blockchain: Blockchain, /// Historic states of previous blocks. states: Arc>, /// Env data of the chain - env: Arc>, + env: Arc>, /// This is set if this is currently forked off another client. fork: Arc>>, /// Provides time related info, like timestamp. @@ -227,7 +243,7 @@ impl Backend { #[expect(clippy::too_many_arguments)] pub async fn with_genesis( db: Arc>>, - env: Arc>, + env: Arc>, genesis: GenesisConfig, fees: FeeManager, fork: Arc>>, @@ -250,7 +266,7 @@ impl Backend { let env = env.read(); Blockchain::new( &env, - env.handler_cfg.spec_id, + env.evm_env.cfg_env.spec, fees.is_eip1559().then(|| fees.base_fee()), genesis.timestamp, genesis.number, @@ -307,7 +323,7 @@ impl Backend { let mut capabilities = WalletCapabilities::default(); - let chain_id = env.read().cfg.chain_id; + let chain_id = env.read().evm_env.cfg_env.chain_id; capabilities.insert( chain_id, Capabilities { @@ -386,7 +402,7 @@ impl Backend { /// Adds an address to the [`DelegationCapability`] of the wallet. pub(crate) fn add_capability(&self, address: Address) { - let chain_id = self.env.read().cfg.chain_id; + let chain_id = self.env.read().evm_env.cfg_env.chain_id; let mut capabilities = self.capabilities.write(); let mut capability = capabilities.get(chain_id).cloned().unwrap_or_default(); capability.delegation.addresses.push(address); @@ -449,6 +465,9 @@ impl Backend { let db = self.db.write().await; // apply the genesis.json alloc self.genesis.apply_genesis_json_alloc(db)?; + + trace!(target: "backend", "set genesis balances"); + Ok(()) } @@ -461,7 +480,7 @@ impl Backend { } // Ensure EIP-3607 is disabled let mut env = self.env.write(); - env.cfg.disable_eip3607 = true; + env.evm_env.cfg_env.disable_eip3607 = true; self.cheats.impersonate(addr) } @@ -498,7 +517,7 @@ impl Backend { } pub fn precompiles(&self) -> Vec
{ - get_precompiles_for(self.env.read().handler_cfg.spec_id) + get_precompiles_for(self.env.read().evm_env.cfg_env.spec) } /// Resets the fork to a fresh state @@ -558,23 +577,23 @@ impl Backend { let gas_limit = self.node_config.read().await.fork_gas_limit(&fork_block); let mut env = self.env.write(); - env.cfg.chain_id = fork.chain_id(); - env.block = BlockEnv { - number: U256::from(fork_block_number), - timestamp: U256::from(fork_block.header.timestamp), - gas_limit: U256::from(gas_limit), + env.evm_env.cfg_env.chain_id = fork.chain_id(); + env.evm_env.block_env = BlockEnv { + number: fork_block_number, + timestamp: fork_block.header.timestamp, + gas_limit, difficulty: fork_block.header.difficulty, prevrandao: Some(fork_block.header.mix_hash.unwrap_or_default()), - // Keep previous `coinbase` and `basefee` value - coinbase: env.block.coinbase, - basefee: env.block.basefee, - ..env.block.clone() + // Keep previous `beneficiary` and `basefee` value + beneficiary: env.evm_env.block_env.beneficiary, + basefee: env.evm_env.block_env.basefee, + ..env.evm_env.block_env.clone() }; // this is the base fee of the current block, but we need the base fee of // the next block let next_block_base_fee = self.fees.get_next_block_base_fee_per_gas( - fork_block.header.gas_used as u128, + fork_block.header.gas_used, gas_limit, fork_block.header.base_fee_per_gas.unwrap_or_default(), ); @@ -599,6 +618,8 @@ impl Backend { self.apply_genesis().await?; + trace!(target: "backend", "reset fork"); + Ok(()) } else { Err(RpcError::invalid_params("Forking not enabled").into()) @@ -648,7 +669,7 @@ impl Backend { } /// The env data of the blockchain - pub fn env(&self) -> &Arc> { + pub fn env(&self) -> &Arc> { &self.env } @@ -659,27 +680,27 @@ impl Backend { /// Returns the current best number of the chain pub fn best_number(&self) -> u64 { - self.blockchain.storage.read().best_number.try_into().unwrap_or(u64::MAX) + self.blockchain.storage.read().best_number } /// Sets the block number - pub fn set_block_number(&self, number: U256) { + pub fn set_block_number(&self, number: u64) { let mut env = self.env.write(); - env.block.number = number; + env.evm_env.block_env.number = number; } /// Returns the client coinbase address. pub fn coinbase(&self) -> Address { - self.env.read().block.coinbase + self.env.read().evm_env.block_env.beneficiary } /// Returns the client coinbase address. pub fn chain_id(&self) -> U256 { - U256::from(self.env.read().cfg.chain_id) + U256::from(self.env.read().evm_env.cfg_env.chain_id) } pub fn set_chain_id(&self, chain_id: u64) { - self.env.write().cfg.chain_id = chain_id; + self.env.write().evm_env.cfg_env.chain_id = chain_id; } /// Returns balance of the given account. @@ -694,7 +715,7 @@ impl Backend { /// Sets the coinbase address pub fn set_coinbase(&self, address: Address) { - self.env.write().block.coinbase = address; + self.env.write().evm_env.block_env.beneficiary = address; } /// Sets the nonce of the given address @@ -724,7 +745,7 @@ impl Backend { /// Returns the configured specid pub fn spec_id(&self) -> SpecId { - self.env.read().handler_cfg.spec_id + self.env.read().evm_env.cfg_env.spec } /// Returns true for post London @@ -754,7 +775,22 @@ impl Backend { /// Returns true if op-stack deposits are active pub fn is_optimism(&self) -> bool { - self.env.read().handler_cfg.is_optimism + self.env.read().is_optimism + } + + /// Returns [`BlobParams`] corresponding to the current spec. + pub fn blob_params(&self) -> BlobParams { + let spec_id = self.env.read().evm_env.cfg_env.spec; + + if spec_id >= SpecId::OSAKA { + return BlobParams::osaka(); + } + + if spec_id >= SpecId::PRAGUE { + return BlobParams::prague(); + } + + BlobParams::cancun() } /// Returns an error if EIP1559 is not active (pre Berlin) @@ -797,12 +833,12 @@ impl Backend { /// Returns the block gas limit pub fn gas_limit(&self) -> u64 { - self.env.read().block.gas_limit.saturating_to() + self.env.read().evm_env.block_env.gas_limit } /// Sets the block gas limit pub fn set_gas_limit(&self, gas_limit: u64) { - self.env.write().block.gas_limit = U256::from(gas_limit); + self.env.write().evm_env.block_env.gas_limit = gas_limit; } /// Returns the current base fee @@ -864,7 +900,6 @@ impl Backend { for n in ((num + 1)..=current_height).rev() { trace!(target: "backend", "reverting block {}", n); - let n = U64::from(n); if let Some(hash) = storage.hashes.remove(&n) { if let Some(block) = storage.blocks.remove(&hash) { for tx in block.transactions { @@ -874,7 +909,7 @@ impl Backend { } } - storage.best_number = U64::from(num); + storage.best_number = num; storage.best_hash = hash; hash }; @@ -885,18 +920,18 @@ impl Backend { self.time.reset(reset_time); let mut env = self.env.write(); - env.block = BlockEnv { - number: U256::from(num), - timestamp: U256::from(block.header.timestamp), + env.evm_env.block_env = BlockEnv { + number: num, + timestamp: block.header.timestamp, difficulty: block.header.difficulty, // ensures prevrandao is set prevrandao: Some(block.header.mix_hash.unwrap_or_default()), - gas_limit: U256::from(block.header.gas_limit), - // Keep previous `coinbase` and `basefee` value - coinbase: env.block.coinbase, - basefee: env.block.basefee, + gas_limit: block.header.gas_limit, + // Keep previous `beneficiary` and `basefee` value + beneficiary: env.evm_env.block_env.beneficiary, + basefee: env.evm_env.block_env.basefee, ..Default::default() - }; + } } Ok(self.db.write().await.revert_state(id, RevertStateSnapshotAction::RevertRemove)) } @@ -910,7 +945,7 @@ impl Backend { &self, preserve_historical_states: bool, ) -> Result { - let at = self.env.read().block.clone(); + let at = self.env.read().evm_env.block_env.clone(); let best_number = self.blockchain.storage.read().best_number; let blocks = self.blockchain.storage.read().serialized_blocks(); let transactions = self.blockchain.storage.read().serialized_transactions(); @@ -953,19 +988,19 @@ impl Backend { self.blockchain.storage.write().load_transactions(state.transactions.clone()); // reset the block env if let Some(block) = state.block.clone() { - self.env.write().block = block.clone(); + self.env.write().evm_env.block_env = block.clone(); // Set the current best block number. // Defaults to block number for compatibility with existing state files. let fork_num_and_hash = self.get_fork().map(|f| (f.block_number(), f.block_hash())); + let best_number = state.best_block_number.unwrap_or(block.number); if let Some((number, hash)) = fork_num_and_hash { - let best_number = state.best_block_number.unwrap_or(block.number.to::()); trace!(target: "backend", state_block_number=?best_number, fork_block_number=?number); // If the state.block_number is greater than the fork block number, set best number // to the state block number. // Ref: https://github.com/foundry-rs/foundry/issues/9539 - if best_number.to::() > number { + if best_number > number { self.blockchain.storage.write().best_number = best_number; let best_hash = self.blockchain.storage.read().hash(best_number.into()).ok_or_else( @@ -979,11 +1014,10 @@ impl Backend { } else { // If loading state file on a fork, set best number to the fork block number. // Ref: https://github.com/foundry-rs/foundry/pull/9215#issue-2618681838 - self.blockchain.storage.write().best_number = U64::from(number); + self.blockchain.storage.write().best_number = number; self.blockchain.storage.write().best_hash = hash; } } else { - let best_number = state.best_block_number.unwrap_or(block.number.to::()); self.blockchain.storage.write().best_number = best_number; // Set the current best block hash; @@ -998,6 +1032,26 @@ impl Backend { } } + if let Some(latest) = state.blocks.iter().max_by_key(|b| b.header.number) { + let header = &latest.header; + let next_block_base_fee = self.fees.get_next_block_base_fee_per_gas( + header.gas_used, + header.gas_limit, + header.base_fee_per_gas.unwrap_or_default(), + ); + let next_block_excess_blob_gas = self.fees.get_next_block_blob_excess_gas( + header.excess_blob_gas.unwrap_or_default(), + header.blob_gas_used.unwrap_or_default(), + ); + + // update next base fee + self.fees.set_base_fee(next_block_base_fee); + self.fees.set_blob_excess_gas_and_price(BlobExcessGasAndPrice::new( + next_block_excess_blob_gas, + false, + )); + } + if !self.db.write().await.load_state(state.clone())? { return Err(RpcError::invalid_params( "Loading state not supported with the current configuration", @@ -1032,33 +1086,42 @@ impl Backend { } /// Returns the environment for the next block - fn next_env(&self) -> EnvWithHandlerCfg { + fn next_env(&self) -> Env { let mut env = self.env.read().clone(); // increase block number for this block - env.block.number = env.block.number.saturating_add(U256::from(1)); - env.block.basefee = U256::from(self.base_fee()); - env.block.timestamp = U256::from(self.time.current_call_timestamp()); + env.evm_env.block_env.number = env.evm_env.block_env.number.saturating_add(1); + env.evm_env.block_env.basefee = self.base_fee(); + env.evm_env.block_env.timestamp = self.time.current_call_timestamp(); env } /// Creates an EVM instance with optionally injected precompiles. - #[expect(clippy::type_complexity)] - fn new_evm_with_inspector_ref<'i, 'db>( + fn new_evm_with_inspector_ref<'db, I>( &self, db: &'db dyn DatabaseRef, - env: EnvWithHandlerCfg, - inspector: &'i mut dyn revm::Inspector< - WrapDatabaseRef<&'db dyn DatabaseRef>, - >, - ) -> revm::Evm< - '_, - &'i mut dyn revm::Inspector>>, + env: &Env, + inspector: &'db mut I, + ) -> EitherEvm< WrapDatabaseRef<&'db dyn DatabaseRef>, - > { - let mut evm = new_evm_with_inspector_ref(db, env, inspector, self.odyssey); + &'db mut I, + PrecompilesMap, + > + where + I: Inspector>>> + + Inspector>>>, + WrapDatabaseRef<&'db dyn DatabaseRef>: + Database, + { + let mut evm = new_evm_with_inspector_ref(db, env, inspector); + + if self.odyssey { + inject_precompiles(&mut evm, vec![P256VERIFY]); + } + if let Some(factory) = &self.precompile_factory { inject_precompiles(&mut evm, factory.precompiles()); } + evm } @@ -1073,15 +1136,15 @@ impl Backend { let mut env = self.next_env(); env.tx = tx.pending_transaction.to_revm_tx_env(); - if env.handler_cfg.is_optimism { - env.tx.optimism.enveloped_tx = + if env.is_optimism { + env.tx.enveloped_tx = Some(alloy_rlp::encode(&tx.pending_transaction.transaction.transaction).into()); } let db = self.db.read().await; let mut inspector = self.build_inspector(); - let mut evm = self.new_evm_with_inspector_ref(db.as_dyn(), env, &mut inspector); - let ResultAndState { result, state } = evm.transact()?; + let mut evm = self.new_evm_with_inspector_ref(db.as_dyn(), &env, &mut inspector); + let ResultAndState { result, state } = evm.transact(env.tx)?; let (exit_reason, gas_used, out, logs) = match result { ExecutionResult::Success { reason, gas_used, logs, output, .. } => { (reason.into(), gas_used, Some(output), Some(logs)) @@ -1089,7 +1152,10 @@ impl Backend { ExecutionResult::Revert { gas_used, output } => { (InstructionResult::Revert, gas_used, Some(Output::Call(output)), None) } - ExecutionResult::Halt { reason, gas_used } => (reason.into(), gas_used, None, None), + ExecutionResult::Halt { reason, gas_used } => { + let eth_reason = op_haltreason_to_instruction_result(reason); + (eth_reason, gas_used, None, None) + } }; drop(evm); @@ -1127,13 +1193,12 @@ impl Backend { let storage = self.blockchain.storage.read(); - let cfg_env = CfgEnvWithHandlerCfg::new(env.cfg.clone(), env.handler_cfg); let executor = TransactionExecutor { db: &mut cache_db, validator: self, pending: pool_transactions.into_iter(), - block_env: env.block.clone(), - cfg_env, + block_env: env.evm_env.block_env.clone(), + cfg_env: env.evm_env.cfg_env, parent_hash: storage.best_hash, gas_used: 0, blob_gas_used: 0, @@ -1142,6 +1207,8 @@ impl Backend { print_traces: self.print_traces, precompile_factory: self.precompile_factory.clone(), odyssey: self.odyssey, + optimism: self.is_optimism(), + blob_params: self.blob_params(), }; // create a new pending block @@ -1173,28 +1240,27 @@ impl Backend { let mut env = self.env.read().clone(); - if env.block.basefee.is_zero() { + if env.evm_env.block_env.basefee == 0 { // this is an edge case because the evm fails if `tx.effective_gas_price < base_fee` // 0 is only possible if it's manually set - env.cfg.disable_base_fee = true; + env.evm_env.cfg_env.disable_base_fee = true; } - let block_number = - self.blockchain.storage.read().best_number.saturating_add(U64::from(1)); + let block_number = self.blockchain.storage.read().best_number.saturating_add(1); // increase block number for this block - if is_arbitrum(env.cfg.chain_id) { + if is_arbitrum(env.evm_env.cfg_env.chain_id) { // Temporary set `env.block.number` to `block_number` for Arbitrum chains. - env.block.number = block_number.to(); + env.evm_env.block_env.number = block_number; } else { - env.block.number = env.block.number.saturating_add(U256::from(1)); + env.evm_env.block_env.number = env.evm_env.block_env.number.saturating_add(1); } - env.block.basefee = U256::from(current_base_fee); - env.block.blob_excess_gas_and_price = current_excess_blob_gas_and_price; + env.evm_env.block_env.basefee = current_base_fee; + env.evm_env.block_env.blob_excess_gas_and_price = current_excess_blob_gas_and_price; // pick a random value for prevrandao - env.block.prevrandao = Some(B256::random()); + env.evm_env.block_env.prevrandao = Some(B256::random()); let best_hash = self.blockchain.storage.read().best_hash; @@ -1210,14 +1276,14 @@ impl Backend { // finally set the next block timestamp, this is done just before execution, because // there can be concurrent requests that can delay acquiring the db lock and we want // to ensure the timestamp is as close as possible to the actual execution. - env.block.timestamp = U256::from(self.time.next_timestamp()); + env.evm_env.block_env.timestamp = self.time.next_timestamp(); let executor = TransactionExecutor { db: &mut **db, validator: self, pending: pool_transactions.into_iter(), - block_env: env.block.clone(), - cfg_env: CfgEnvWithHandlerCfg::new(env.cfg.clone(), env.handler_cfg), + block_env: env.evm_env.block_env.clone(), + cfg_env: env.evm_env.cfg_env.clone(), parent_hash: best_hash, gas_used: 0, blob_gas_used: 0, @@ -1226,6 +1292,8 @@ impl Backend { print_traces: self.print_traces, odyssey: self.odyssey, precompile_factory: self.precompile_factory.clone(), + optimism: self.is_optimism(), + blob_params: self.blob_params(), }; let executed_tx = executor.execute(); @@ -1281,12 +1349,7 @@ impl Backend { } node_info!(""); - let mined_tx = MinedTransaction { - info, - receipt, - block_hash, - block_number: block_number.to::(), - }; + let mined_tx = MinedTransaction { info, receipt, block_hash, block_number }; storage.transactions.insert(mined_tx.info.transaction_hash, mined_tx); } @@ -1294,14 +1357,13 @@ impl Backend { if let Some(transaction_block_keeper) = self.transaction_block_keeper { if storage.blocks.len() > transaction_block_keeper { let to_clear = block_number - .to::() .saturating_sub(transaction_block_keeper.try_into().unwrap_or(u64::MAX)); storage.remove_block_transactions_by_number(to_clear) } } // we intentionally set the difficulty to `0` for newer blocks - env.block.difficulty = U256::from(0); + env.evm_env.block_env.difficulty = U256::from(0); // update env with new values *self.env.write() = env; @@ -1322,13 +1384,13 @@ impl Backend { (outcome, header, block_hash) }; let next_block_base_fee = self.fees.get_next_block_base_fee_per_gas( - header.gas_used as u128, - header.gas_limit as u128, + header.gas_used, + header.gas_limit, header.base_fee_per_gas.unwrap_or_default(), ); let next_block_excess_blob_gas = self.fees.get_next_block_blob_excess_gas( - header.excess_blob_gas.map(|g| g as u128).unwrap_or_default(), - header.blob_gas_used.map(|g| g as u128).unwrap_or_default(), + header.excess_blob_gas.unwrap_or_default(), + header.blob_gas_used.unwrap_or_default(), ); // update next base fee @@ -1354,16 +1416,19 @@ impl Backend { request: WithOtherFields, fee_details: FeeDetails, block_request: Option, - overrides: Option, + overrides: EvmOverrides, ) -> Result<(InstructionResult, Option, u128, State), BlockchainError> { - self.with_database_at(block_request, |state, block| { - let block_number = block.number.to::(); - let (exit, out, gas, state) = match overrides { - None => self.call_with_state(state.as_dyn(), request, fee_details, block), - Some(overrides) => { - let state = state::apply_state_override(overrides.into_iter().collect(), state)?; - self.call_with_state(state.as_dyn(), request, fee_details, block) - }, + self.with_database_at(block_request, |state, mut block| { + let block_number = block.number; + let (exit, out, gas, state) = { + let mut cache_db = CacheDB::new(state); + if let Some(state_overrides) = overrides.state { + state::apply_state_overrides(state_overrides.into_iter().collect(), &mut cache_db)?; + } + if let Some(block_overrides) = overrides.block { + state::apply_block_overrides(*block_overrides, &mut cache_db, &mut block); + } + self.call_with_state(cache_db.as_dyn(), request, fee_details, block) }?; trace!(target: "backend", "call return {:?} out: {:?} gas {} on block {}", exit, out, gas, block_number); Ok((exit, out, gas, state)) @@ -1376,13 +1441,13 @@ impl Backend { /// /// - `disable_eip3607` is set to `true` /// - `disable_base_fee` is set to `true` - /// - `nonce` is set to `None` + /// - `nonce` check is skipped if `request.nonce` is None fn build_call_env( &self, request: WithOtherFields, fee_details: FeeDetails, block_env: BlockEnv, - ) -> EnvWithHandlerCfg { + ) -> Env { let WithOtherFields:: { inner: TransactionRequest { @@ -1394,16 +1459,31 @@ impl Backend { access_list, blob_versioned_hashes, authorization_list, - // nonce is always ignored for calls - nonce: _, + nonce, sidecar: _, - chain_id: _, - transaction_type: _, + chain_id, + transaction_type, + max_fee_per_gas, + max_priority_fee_per_gas, .. // Rest of the gas fees related fields are taken from `fee_details` }, - .. + other, } = request; + let tx_type = transaction_type.unwrap_or_else(|| { + if authorization_list.is_some() { + EIP7702_TX_TYPE_ID + } else if blob_versioned_hashes.is_some() { + EIP4844_TX_TYPE_ID + } else if max_fee_per_gas.is_some() || max_priority_fee_per_gas.is_some() { + EIP1559_TX_TYPE_ID + } else if access_list.is_some() { + EIP2930_TX_TYPE_ID + } else { + LEGACY_TX_TYPE_ID + } + }); + let FeeDetails { gas_price, max_fee_per_gas, @@ -1411,19 +1491,19 @@ impl Backend { max_fee_per_blob_gas, } = fee_details; - let gas_limit = gas.unwrap_or(block_env.gas_limit.to()); + let gas_limit = gas.unwrap_or(block_env.gas_limit); let mut env = self.env.read().clone(); - env.block = block_env; + env.evm_env.block_env = block_env; // we want to disable this in eth_call, since this is common practice used by other node // impls and providers - env.cfg.disable_block_gas_limit = true; + env.evm_env.cfg_env.disable_block_gas_limit = true; // The basefee should be ignored for calls against state for // - eth_call // - eth_estimateGas // - eth_createAccessList // - tracing - env.cfg.disable_base_fee = true; + env.evm_env.cfg_env.disable_base_fee = true; let gas_price = gas_price.or(max_fee_per_gas).unwrap_or_else(|| { self.fees().raw_gas_price().saturating_add(MIN_SUGGESTED_PRIORITY_FEE) @@ -1431,48 +1511,73 @@ impl Backend { let caller = from.unwrap_or_default(); let to = to.as_ref().and_then(TxKind::to); let blob_hashes = blob_versioned_hashes.unwrap_or_default(); - env.tx = - TxEnv { - caller, - gas_limit, - gas_price: U256::from(gas_price), - gas_priority_fee: max_priority_fee_per_gas.map(U256::from), - max_fee_per_blob_gas: max_fee_per_blob_gas - .or_else(|| { - if !blob_hashes.is_empty() { - env.block.get_blob_gasprice() - } else { - None - } - }) - .map(U256::from), - transact_to: match to { - Some(addr) => TxKind::Call(*addr), - None => TxKind::Create, - }, - value: value.unwrap_or_default(), - data: input.into_input().unwrap_or_default(), - chain_id: None, - // set nonce to None so that the correct nonce is chosen by the EVM - nonce: None, - access_list: access_list.unwrap_or_default().into(), - blob_hashes, - optimism: OptimismFields { enveloped_tx: Some(Bytes::new()), ..Default::default() }, - authorization_list: authorization_list.map(Into::into), - }; + let mut base = TxEnv { + caller, + gas_limit, + gas_price, + gas_priority_fee: max_priority_fee_per_gas, + max_fee_per_blob_gas: max_fee_per_blob_gas + .or_else(|| { + if !blob_hashes.is_empty() { + env.evm_env.block_env.blob_gasprice() + } else { + Some(0) + } + }) + .unwrap_or_default(), + kind: match to { + Some(addr) => TxKind::Call(*addr), + None => TxKind::Create, + }, + tx_type, + value: value.unwrap_or_default(), + data: input.into_input().unwrap_or_default(), + chain_id: Some(chain_id.unwrap_or(self.env.read().evm_env.cfg_env.chain_id)), + access_list: access_list.unwrap_or_default(), + blob_hashes, + ..Default::default() + }; + base.set_signed_authorization(authorization_list.unwrap_or_default()); + env.tx = OpTransaction { base, ..Default::default() }; + + if let Some(nonce) = nonce { + env.tx.base.nonce = nonce; + } else { + // Disable nonce check in revm + env.evm_env.cfg_env.disable_nonce_check = true; + } - if env.block.basefee.is_zero() { + if env.evm_env.block_env.basefee == 0 { // this is an edge case because the evm fails if `tx.effective_gas_price < base_fee` // 0 is only possible if it's manually set - env.cfg.disable_base_fee = true; + env.evm_env.cfg_env.disable_base_fee = true; + } + + // Deposit transaction? + if transaction_type == Some(DEPOSIT_TX_TYPE_ID) && has_optimism_fields(&other) { + let deposit = DepositTransactionParts { + source_hash: other + .get_deserialized::("sourceHash") + .map(|sh| sh.unwrap_or_default()) + .unwrap_or_default(), + mint: other + .get_deserialized::("mint") + .map(|m| m.unwrap_or_default()) + .or(None), + is_system_transaction: other + .get_deserialized::("isSystemTx") + .map(|st| st.unwrap_or_default()) + .unwrap_or_default(), + }; + env.tx.deposit = deposit; } env } /// Builds [`Inspector`] with the configured options. - fn build_inspector(&self) -> Inspector { - let mut inspector = Inspector::default(); + fn build_inspector(&self) -> AnvilInspector { + let mut inspector = AnvilInspector::default(); if self.print_logs { inspector = inspector.with_log_collector(); @@ -1507,34 +1612,14 @@ impl Backend { let mut log_index = 0; let mut gas_used = 0; let mut transactions = Vec::with_capacity(calls.len()); + let mut logs= Vec::new(); + // apply state overrides before executing the transactions if let Some(state_overrides) = state_overrides { - state::apply_cached_db_state_override(state_overrides, &mut cache_db)?; + state::apply_state_overrides(state_overrides, &mut cache_db)?; } - - // apply block overrides - if let Some(overrides) = block_overrides { - if let Some(number) = overrides.number { - block_env.number = number.saturating_to(); - } - if let Some(difficulty) = overrides.difficulty { - block_env.difficulty = difficulty; - } - if let Some(time) = overrides.time { - block_env.timestamp = U256::from(time); - } - if let Some(gas_limit) = overrides.gas_limit { - block_env.gas_limit = U256::from(gas_limit); - } - if let Some(coinbase) = overrides.coinbase { - block_env.coinbase = coinbase; - } - if let Some(random) = overrides.random { - block_env.prevrandao = Some(random); - } - if let Some(base_fee) = overrides.base_fee { - block_env.basefee = base_fee.saturating_to(); - } + if let Some(block_overrides) = block_overrides { + state::apply_block_overrides(block_overrides, &mut cache_db, &mut block_env); } // execute all calls in that block @@ -1554,11 +1639,11 @@ impl Backend { ); // Always disable EIP-3607 - env.cfg.disable_eip3607 = true; + env.evm_env.cfg_env.disable_eip3607 = true; if !validation { - env.cfg.disable_base_fee = !validation; - env.block.basefee = U256::from(0); + env.evm_env.cfg_env.disable_base_fee = !validation; + env.evm_env.block_env.basefee = 0; } // transact @@ -1566,16 +1651,23 @@ impl Backend { // prepare inspector to capture transfer inside the evm so they are // recorded and included in logs let mut inspector = TransferInspector::new(false).with_logs(true); - let mut evm = - self.new_evm_with_inspector_ref(cache_db.as_dyn(), env, &mut inspector); - trace!(target: "backend", env=?evm.context.env(), spec=?evm.spec_id(), "simulate evm env"); - evm.transact()? + let mut evm= self.new_evm_with_inspector_ref( + cache_db.as_dyn(), + &env, + &mut inspector, + ); + + trace!(target: "backend", env=?env.evm_env, spec=?env.evm_env.spec_id(),"simulate evm env"); + evm.transact(env.tx)? } else { let mut inspector = self.build_inspector(); - let mut evm = - self.new_evm_with_inspector_ref(cache_db.as_dyn(), env, &mut inspector); - trace!(target: "backend", env=?evm.context.env(),spec=?evm.spec_id(), "simulate evm env"); - evm.transact()? + let mut evm = self.new_evm_with_inspector_ref( + cache_db.as_dyn(), + &env, + &mut inspector, + ); + trace!(target: "backend", env=?env.evm_env, spec=?env.evm_env.spec_id(),"simulate evm env"); + evm.transact(env.tx)? }; trace!(target: "backend", ?result, ?request, "simulate call"); @@ -1592,12 +1684,13 @@ impl Backend { request, Signature::new(Default::default(), Default::default(), false), )?; + let tx_hash = tx.hash(); let rpc_tx = transaction_build( None, MaybeImpersonatedTransaction::impersonated(tx, from), None, None, - Some(block_env.basefee.to()), + Some(block_env.basefee), ); transactions.push(rpc_tx); @@ -1612,50 +1705,54 @@ impl Backend { message: "execution failed".to_string(), } }), - logs: result + logs: result.clone() .into_logs() .into_iter() .enumerate() .map(|(idx, log)| Log { inner: log, - block_number: Some(block_env.number.to()), - block_timestamp: Some(block_env.timestamp.to()), + block_number: Some(block_env.number), + block_timestamp: Some(block_env.timestamp), transaction_index: Some(req_idx as u64), log_index: Some((idx + log_index) as u64), removed: false, block_hash: None, - transaction_hash: None, + transaction_hash: Some(tx_hash), }) .collect(), }; - + logs.extend(sim_res.logs.clone().iter().map(|log| log.inner.clone())); log_index += sim_res.logs.len(); call_res.push(sim_res); } + let transactions_envelopes: Vec = transactions + .iter() + .map(|tx| AnyTxEnvelope::from(tx.clone())) + .collect(); let header = Header { - logs_bloom: Default::default(), - transactions_root: Default::default(), - receipts_root: Default::default(), + logs_bloom: logs_bloom(logs.iter()), + transactions_root: calculate_transaction_root(&transactions_envelopes), + receipts_root: calculate_receipt_root(&transactions_envelopes), parent_hash: Default::default(), - ommers_hash: Default::default(), - beneficiary: block_env.coinbase, + beneficiary: block_env.beneficiary, state_root: Default::default(), difficulty: Default::default(), - number: block_env.number.to(), - gas_limit: block_env.gas_limit.to(), + number: block_env.number, + gas_limit: block_env.gas_limit, gas_used, - timestamp: block_env.timestamp.to(), + timestamp: block_env.timestamp, extra_data: Default::default(), mix_hash: Default::default(), nonce: Default::default(), - base_fee_per_gas: Some(block_env.basefee.to()), + base_fee_per_gas: Some(block_env.basefee), withdrawals_root: None, blob_gas_used: None, excess_blob_gas: None, parent_beacon_block_root: None, requests_hash: None, + ..Default::default() }; let mut block = alloy_rpc_types::Block { header: AnyRpcHeader { @@ -1673,21 +1770,25 @@ impl Backend { block.transactions.convert_to_hashes(); } + for res in &mut call_res { + res.logs.iter_mut().for_each(|log| { + log.block_hash = Some(block.header.hash); + }); + } + let simulated_block = SimulatedBlock { inner: AnyRpcBlock::new(WithOtherFields::new(block)), calls: call_res, }; // update block env - block_env.number += U256::from(1); - block_env.timestamp += U256::from(12); - block_env.basefee = U256::from( - simulated_block - .inner - .header - .next_block_base_fee(BaseFeeParams::ethereum()) - .unwrap_or_default(), - ); + block_env.number += 1; + block_env.timestamp += 12; + block_env.basefee = simulated_block + .inner + .header + .next_block_base_fee(BaseFeeParams::ethereum()) + .unwrap_or_default(); block_res.push(simulated_block); } @@ -1707,8 +1808,8 @@ impl Backend { let mut inspector = self.build_inspector(); let env = self.build_call_env(request, fee_details, block_env); - let mut evm = self.new_evm_with_inspector_ref(state, env, &mut inspector); - let ResultAndState { result, state } = evm.transact()?; + let mut evm = self.new_evm_with_inspector_ref(state, &env, &mut inspector); + let ResultAndState { result, state } = evm.transact(env.tx)?; let (exit_reason, gas_used, out) = match result { ExecutionResult::Success { reason, gas_used, output, .. } => { (reason.into(), gas_used, Some(output)) @@ -1716,7 +1817,9 @@ impl Backend { ExecutionResult::Revert { gas_used, output } => { (InstructionResult::Revert, gas_used, Some(Output::Call(output))) } - ExecutionResult::Halt { reason, gas_used } => (reason.into(), gas_used, None), + ExecutionResult::Halt { reason, gas_used } => { + (op_haltreason_to_instruction_result(reason), gas_used, None) + } }; drop(evm); inspector.print_logs(); @@ -1735,19 +1838,20 @@ impl Backend { block_request: Option, opts: GethDebugTracingCallOptions, ) -> Result { - let GethDebugTracingCallOptions { tracing_options, block_overrides: _, state_overrides } = + let GethDebugTracingCallOptions { tracing_options, block_overrides, state_overrides } = opts; let GethDebugTracingOptions { config, tracer, tracer_config, .. } = tracing_options; - self.with_database_at(block_request, |state, block| { + self.with_database_at(block_request, |state, mut block| { let block_number = block.number; - let state = if let Some(overrides) = state_overrides { - Box::new(state::apply_state_override(overrides, state)?) - as Box - } else { - state - }; + let mut cache_db = CacheDB::new(state); + if let Some(state_overrides) = state_overrides { + state::apply_state_overrides(state_overrides, &mut cache_db)?; + } + if let Some(block_overrides) = block_overrides { + state::apply_block_overrides(block_overrides, &mut cache_db, &mut block); + } if let Some(tracer) = tracer { return match tracer { @@ -1763,11 +1867,11 @@ impl Backend { let env = self.build_call_env(request, fee_details, block); let mut evm = self.new_evm_with_inspector_ref( - state.as_dyn(), - env, + cache_db.as_dyn(), + &env, &mut inspector, ); - let ResultAndState { result, state: _ } = evm.transact()?; + let ResultAndState { result, state: _ } = evm.transact(env.tx)?; drop(evm); let tracing_inspector = inspector.tracer.expect("tracer disappeared"); @@ -1798,8 +1902,8 @@ impl Backend { .with_tracing_config(TracingInspectorConfig::from_geth_config(&config)); let env = self.build_call_env(request, fee_details, block); - let mut evm = self.new_evm_with_inspector_ref(state.as_dyn(), env, &mut inspector); - let ResultAndState { result, state: _ } = evm.transact()?; + let mut evm = self.new_evm_with_inspector_ref(cache_db.as_dyn(), &env, &mut inspector); + let ResultAndState { result, state: _ } = evm.transact(env.tx)?; let (exit_reason, gas_used, out) = match result { ExecutionResult::Success { reason, gas_used, output, .. } => { @@ -1808,7 +1912,9 @@ impl Backend { ExecutionResult::Revert { gas_used, output } => { (InstructionResult::Revert, gas_used, Some(Output::Call(output))) } - ExecutionResult::Halt { reason, gas_used } => (reason.into(), gas_used, None), + ExecutionResult::Halt { reason, gas_used } => { + (op_haltreason_to_instruction_result(reason), gas_used, None) + } }; drop(evm); @@ -1838,8 +1944,8 @@ impl Backend { AccessListInspector::new(request.access_list.clone().unwrap_or_default()); let env = self.build_call_env(request, fee_details, block_env); - let mut evm = self.new_evm_with_inspector_ref(state, env, &mut inspector); - let ResultAndState { result, state: _ } = evm.transact()?; + let mut evm = self.new_evm_with_inspector_ref(state, &env, &mut inspector); + let ResultAndState { result, state: _ } = evm.transact(env.tx)?; let (exit_reason, gas_used, out) = match result { ExecutionResult::Success { reason, gas_used, output, .. } => { (reason.into(), gas_used, Some(output)) @@ -1847,7 +1953,9 @@ impl Backend { ExecutionResult::Revert { gas_used, output } => { (InstructionResult::Revert, gas_used, Some(Output::Call(output))) } - ExecutionResult::Halt { reason, gas_used } => (reason.into(), gas_used, None), + ExecutionResult::Halt { reason, gas_used } => { + (op_haltreason_to_instruction_result(reason), gas_used, None) + } }; drop(evm); let access_list = inspector.access_list(); @@ -1887,7 +1995,6 @@ impl Backend { /// Returns all `Log`s mined by the node that were emitted in the `block` and match the `Filter` fn mined_logs_for_block(&self, filter: Filter, block: Block) -> Vec { - let params = FilteredParams::new(Some(filter.clone())); let mut all_logs = Vec::new(); let block_hash = block.header.hash_slow(); let mut block_log_index = 0u32; @@ -1898,25 +2005,13 @@ impl Backend { let Some(tx) = storage.transactions.get(&tx.hash()) else { continue; }; + let logs = tx.receipt.logs(); let transaction_hash = tx.info.transaction_hash; for log in logs { - let mut is_match: bool = true; - if !filter.address.is_empty() && filter.has_topics() { - if !params.filter_address(&log.address) || !params.filter_topics(log.topics()) { - is_match = false; - } - } else if !filter.address.is_empty() { - if !params.filter_address(&log.address) { - is_match = false; - } - } else if filter.has_topics() && !params.filter_topics(log.topics()) { - is_match = false; - } - - if is_match { - let log = Log { + if filter.matches(log) { + all_logs.push(Log { inner: log.clone(), block_hash: Some(block_hash), block_number: Some(block.header.number), @@ -1925,13 +2020,11 @@ impl Backend { transaction_index: Some(tx.info.transaction_index), log_index: Some(block_log_index as u64), removed: false, - }; - all_logs.push(log); + }); } block_log_index += 1; } } - all_logs } @@ -2097,12 +2190,12 @@ impl Backend { BlockId::Hash(hash) => hash.block_hash, BlockId::Number(number) => { let storage = self.blockchain.storage.read(); - let slots_in_an_epoch = U64::from(self.slots_in_an_epoch); + let slots_in_an_epoch = self.slots_in_an_epoch; match number { BlockNumber::Latest => storage.best_hash, BlockNumber::Earliest => storage.genesis_hash, BlockNumber::Pending => return None, - BlockNumber::Number(num) => *storage.hashes.get(&U64::from(num))?, + BlockNumber::Number(num) => *storage.hashes.get(&num)?, BlockNumber::Safe => { if storage.best_number > (slots_in_an_epoch) { *storage.hashes.get(&(storage.best_number - (slots_in_an_epoch)))? @@ -2111,10 +2204,8 @@ impl Backend { } } BlockNumber::Finalized => { - if storage.best_number > (slots_in_an_epoch * U64::from(2)) { - *storage - .hashes - .get(&(storage.best_number - (slots_in_an_epoch * U64::from(2))))? + if storage.best_number > (slots_in_an_epoch * 2) { + *storage.hashes.get(&(storage.best_number - (slots_in_an_epoch * 2)))? } else { storage.genesis_hash } @@ -2171,7 +2262,7 @@ impl Backend { let mut block = WithOtherFields::new(block); // If Arbitrum, apply chain specifics to converted block. - if is_arbitrum(self.env.read().cfg.chain_id) { + if is_arbitrum(self.env.read().evm_env.cfg_env.chain_id) { // Set `l1BlockNumber` field. block.other.insert("l1BlockNumber".to_string(), number.into()); } @@ -2199,7 +2290,7 @@ impl Backend { .number } BlockId::Number(num) => match num { - BlockNumber::Latest | BlockNumber::Pending => self.best_number(), + BlockNumber::Latest | BlockNumber::Pending => current, BlockNumber::Earliest => U64::ZERO.to::(), BlockNumber::Number(num) => num, BlockNumber::Safe => current.saturating_sub(self.slots_in_an_epoch), @@ -2240,13 +2331,13 @@ impl Backend { .with_pending_block(pool_transactions, |state, block| { let block = block.block; let block = BlockEnv { - number: U256::from(block.header.number), - coinbase: block.header.beneficiary, - timestamp: U256::from(block.header.timestamp), + number: block.header.number, + beneficiary: block.header.beneficiary, + timestamp: block.header.timestamp, difficulty: block.header.difficulty, prevrandao: Some(block.header.mix_hash), - basefee: U256::from(block.header.base_fee_per_gas.unwrap_or_default()), - gas_limit: U256::from(block.header.gas_limit), + basefee: block.header.base_fee_per_gas.unwrap_or_default(), + gas_limit: block.header.gas_limit, ..Default::default() }; f(state, block) @@ -2257,23 +2348,23 @@ impl Backend { Some(BlockRequest::Number(bn)) => Some(BlockNumber::Number(bn)), None => None, }; - let block_number: U256 = U256::from(self.convert_block_number(block_number)); + let block_number = self.convert_block_number(block_number); - if block_number < self.env.read().block.number { + if block_number < self.env.read().evm_env.block_env.number { if let Some((block_hash, block)) = self - .block_by_number(BlockNumber::Number(block_number.to::())) + .block_by_number(BlockNumber::Number(block_number)) .await? .map(|block| (block.header.hash, block)) { if let Some(state) = self.states.write().get(&block_hash) { let block = BlockEnv { number: block_number, - coinbase: block.header.beneficiary, - timestamp: U256::from(block.header.timestamp), + beneficiary: block.header.beneficiary, + timestamp: block.header.timestamp, difficulty: block.header.difficulty, prevrandao: block.header.mix_hash, - basefee: U256::from(block.header.base_fee_per_gas.unwrap_or_default()), - gas_limit: U256::from(block.header.gas_limit), + basefee: block.header.base_fee_per_gas.unwrap_or_default(), + gas_limit: block.header.gas_limit, ..Default::default() }; return Ok(f(Box::new(state), block)); @@ -2282,13 +2373,13 @@ impl Backend { warn!(target: "backend", "Not historic state found for block={}", block_number); return Err(BlockchainError::BlockOutOfRange( - self.env.read().block.number.to::(), - block_number.to::(), + self.env.read().evm_env.block_env.number, + block_number, )); } let db = self.db.read().await; - let block = self.env.read().block.clone(); + let block = self.env.read().evm_env.block_env.clone(); Ok(f(Box::new(&**db), block)) } @@ -2842,7 +2933,7 @@ impl Backend { .zip(storage_proofs) .map(|(key, proof)| { let storage_key: U256 = key.into(); - let value = account.storage.get(&storage_key).cloned().unwrap_or_default(); + let value = account.storage.get(&storage_key).copied().unwrap_or_default(); StorageProof { key: JsonStorageKey::Hash(key), value, proof } }) .collect(), @@ -2937,13 +3028,13 @@ impl Backend { // Set environment back to common block let mut env = self.env.write(); - env.block.number = U256::from(common_block.header.number); - env.block.timestamp = U256::from(common_block.header.timestamp); - env.block.gas_limit = U256::from(common_block.header.gas_limit); - env.block.difficulty = common_block.header.difficulty; - env.block.prevrandao = Some(common_block.header.mix_hash); + env.evm_env.block_env.number = common_block.header.number; + env.evm_env.block_env.timestamp = common_block.header.timestamp; + env.evm_env.block_env.gas_limit = common_block.header.gas_limit; + env.evm_env.block_env.difficulty = common_block.header.difficulty; + env.evm_env.block_env.prevrandao = Some(common_block.header.mix_hash); - self.time.reset(env.block.timestamp.to::()); + self.time.reset(env.evm_env.block_env.timestamp); } Ok(()) } @@ -2982,7 +3073,7 @@ impl TransactionValidator for Backend { &self, pending: &PendingTransaction, account: &AccountInfo, - env: &EnvWithHandlerCfg, + env: &Env, ) -> Result<(), InvalidTransactionError> { let tx = &pending.transaction; @@ -2991,7 +3082,7 @@ impl TransactionValidator for Backend { if chain_id.to::() != tx_chain_id { if let Some(legacy) = tx.as_legacy() { // - if env.handler_cfg.spec_id >= SpecId::SPURIOUS_DRAGON && + if env.evm_env.cfg_env.spec >= SpecId::SPURIOUS_DRAGON && legacy.tx().chain_id.is_none() { warn!(target: "backend", ?chain_id, ?tx_chain_id, "incompatible EIP155-based V"); @@ -3010,7 +3101,9 @@ impl TransactionValidator for Backend { } // Check gas limit, iff block gas limit is set. - if !env.cfg.disable_block_gas_limit && tx.gas_limit() > env.block.gas_limit.to::() { + if !env.evm_env.cfg_env.disable_block_gas_limit && + tx.gas_limit() > env.evm_env.block_env.gas_limit + { warn!(target: "backend", "[{:?}] gas too high", tx.hash()); return Err(InvalidTransactionError::GasTooHigh(ErrDetail { detail: String::from("tx.gas_limit > env.block.gas_limit"), @@ -3026,9 +3119,9 @@ impl TransactionValidator for Backend { return Err(InvalidTransactionError::NonceTooLow); } - if (env.handler_cfg.spec_id as u8) >= (SpecId::LONDON as u8) { - if tx.gas_price() < env.block.basefee.to::() && !is_deposit_tx { - warn!(target: "backend", "max fee per gas={}, too low, block basefee={}",tx.gas_price(), env.block.basefee); + if env.evm_env.cfg_env.spec >= SpecId::LONDON { + if tx.gas_price() < env.evm_env.block_env.basefee.into() && !is_deposit_tx { + warn!(target: "backend", "max fee per gas={}, too low, block basefee={}",tx.gas_price(), env.evm_env.block_env.basefee); return Err(InvalidTransactionError::FeeCapTooLow); } @@ -3043,10 +3136,10 @@ impl TransactionValidator for Backend { } // EIP-4844 Cancun hard fork validation steps - if env.spec_id() >= SpecId::CANCUN && tx.transaction.is_eip4844() { + if env.evm_env.cfg_env.spec >= SpecId::CANCUN && tx.transaction.is_eip4844() { // Light checks first: see if the blob fee cap is too low. if let Some(max_fee_per_blob_gas) = tx.essentials().max_fee_per_blob_gas { - if let Some(blob_gas_and_price) = &env.block.blob_excess_gas_and_price { + if let Some(blob_gas_and_price) = &env.evm_env.block_env.blob_excess_gas_and_price { if max_fee_per_blob_gas < blob_gas_and_price.blob_gasprice { warn!(target: "backend", "max fee per blob gas={}, too low, block blob gas price={}", max_fee_per_blob_gas, blob_gas_and_price.blob_gasprice); return Err(InvalidTransactionError::BlobFeeCapTooLow); @@ -3068,13 +3161,14 @@ impl TransactionValidator for Backend { } // Ensure the tx does not exceed the max blobs per block. - if blob_count > MAX_BLOBS_PER_BLOCK { - return Err(InvalidTransactionError::TooManyBlobs(blob_count)) + let max_blob_count = self.blob_params().max_blob_count as usize; + if blob_count > max_blob_count { + return Err(InvalidTransactionError::TooManyBlobs(blob_count, max_blob_count)) } // Check for any blob validation errors if not impersonating. if !self.skip_blob_validation(Some(*pending.sender())) { - if let Err(err) = tx.validate(env.cfg.kzg_settings.get()) { + if let Err(err) = tx.validate(EnvKzgSettings::default().get()) { return Err(InvalidTransactionError::BlobTransactionValidationError(err)) } } @@ -3090,14 +3184,14 @@ impl TransactionValidator for Backend { // 1. no gas cost check required since already have prepaid gas from L1 // 2. increment account balance by deposited amount before checking for sufficient // funds `tx.value <= existing account value + deposited value` - if value > account.balance + deposit_tx.mint { - warn!(target: "backend", "[{:?}] insufficient balance={}, required={} account={:?}", tx.hash(), account.balance + deposit_tx.mint, value, *pending.sender()); + if value > account.balance + U256::from(deposit_tx.mint) { + warn!(target: "backend", "[{:?}] insufficient balance={}, required={} account={:?}", tx.hash(), account.balance + U256::from(deposit_tx.mint), value, *pending.sender()); return Err(InvalidTransactionError::InsufficientFunds); } } _ => { // check sufficient funds: `gas * price + value` - let req_funds = max_cost.checked_add(value.to()).ok_or_else(|| { + let req_funds = max_cost.checked_add(value.saturating_to()).ok_or_else(|| { warn!(target: "backend", "[{:?}] cost too high", tx.hash()); InvalidTransactionError::InsufficientFunds })?; @@ -3115,7 +3209,7 @@ impl TransactionValidator for Backend { &self, tx: &PendingTransaction, account: &AccountInfo, - env: &EnvWithHandlerCfg, + env: &Env, ) -> Result<(), InvalidTransactionError> { self.validate_pool_transaction_for(tx, account, env)?; if tx.nonce() > account.nonce { @@ -3134,30 +3228,9 @@ pub fn transaction_build( base_fee: Option, ) -> AnyRpcTransaction { if let TypedTransaction::Deposit(ref deposit_tx) = eth_transaction.transaction { - let DepositTransaction { - nonce, - source_hash, - from: deposit_from, - kind, - mint, - gas_limit, - is_system_tx, - input, - value, - } = deposit_tx.clone(); - - let dep_tx = TxDeposit { - source_hash, - input, - from: deposit_from, - mint: Some(mint.to()), - to: kind, - is_system_transaction: is_system_tx, - value, - gas_limit, - }; + let dep_tx = deposit_tx; - let ser = serde_json::to_value(&dep_tx).expect("could not serialize TxDeposit"); + let ser = serde_json::to_value(dep_tx).expect("could not serialize TxDeposit"); let maybe_deposit_fields = OtherFields::try_from(ser); match maybe_deposit_fields { @@ -3167,10 +3240,7 @@ pub fn transaction_build( fields.insert("v".to_string(), serde_json::to_value("0x0").unwrap()); fields.insert("r".to_string(), serde_json::to_value(B256::ZERO).unwrap()); fields.insert(String::from("s"), serde_json::to_value(B256::ZERO).unwrap()); - fields.insert( - String::from("nonce"), - serde_json::to_value(format!("0x{nonce}")).unwrap(), - ); + fields.insert(String::from("nonce"), serde_json::to_value("0x0").unwrap()); let inner = UnknownTypedTransaction { ty: AnyTxType(DEPOSIT_TX_TYPE_ID), @@ -3184,7 +3254,7 @@ pub fn transaction_build( }); let tx = Transaction { - inner: Recovered::new_unchecked(envelope, deposit_from), + inner: Recovered::new_unchecked(envelope, deposit_tx.from), block_hash: block .as_ref() .map(|block| B256::from(keccak256(alloy_rlp::encode(&block.header)))), @@ -3308,3 +3378,10 @@ pub fn is_arbitrum(chain_id: u64) -> bool { } false } + +pub fn op_haltreason_to_instruction_result(op_reason: OpHaltReason) -> InstructionResult { + match op_reason { + OpHaltReason::Base(eth_h) => eth_h.into(), + OpHaltReason::FailedDeposit => InstructionResult::Stop, + } +} diff --git a/crates/anvil/src/eth/backend/mem/state.rs b/crates/anvil/src/eth/backend/mem/state.rs index aea154a7bd573..7adb49f15512d 100644 --- a/crates/anvil/src/eth/backend/mem/state.rs +++ b/crates/anvil/src/eth/backend/mem/state.rs @@ -1,16 +1,16 @@ //! Support for generating the state root for memdb storage use crate::eth::error::BlockchainError; -use alloy_primitives::{keccak256, Address, B256, U256}; +use alloy_primitives::{keccak256, map::HashMap, Address, B256, U256}; use alloy_rlp::Encodable; -use alloy_rpc_types::state::StateOverride; +use alloy_rpc_types::{state::StateOverride, BlockOverrides}; use alloy_trie::{HashBuilder, Nibbles}; -use foundry_evm::{ - backend::DatabaseError, - revm::{ - db::{CacheDB, DatabaseRef, DbAccount}, - primitives::{AccountInfo, Bytecode, HashMap}, - }, +use foundry_evm::backend::DatabaseError; +use revm::{ + bytecode::Bytecode, + context::BlockEnv, + database::{CacheDB, DatabaseRef, DbAccount}, + state::AccountInfo, }; pub fn build_root(values: impl IntoIterator)>) -> B256 { @@ -70,21 +70,8 @@ pub fn trie_account_rlp(info: &AccountInfo, storage: &HashMap) -> Ve out } -/// Applies the given state overrides to the state, returning a new CacheDB state -pub fn apply_state_override( - overrides: StateOverride, - state: D, -) -> Result, BlockchainError> -where - D: DatabaseRef, -{ - let mut cache_db = CacheDB::new(state); - apply_cached_db_state_override(overrides, &mut cache_db)?; - Ok(cache_db) -} - /// Applies the given state overrides to the given CacheDB -pub fn apply_cached_db_state_override( +pub fn apply_state_overrides( overrides: StateOverride, cache_db: &mut CacheDB, ) -> Result<(), BlockchainError> @@ -134,3 +121,51 @@ where } Ok(()) } + +/// Applies the given block overrides to the env and updates overridden block hashes in the db. +pub fn apply_block_overrides( + overrides: BlockOverrides, + cache_db: &mut CacheDB, + env: &mut BlockEnv, +) { + let BlockOverrides { + number, + difficulty, + time, + gas_limit, + coinbase, + random, + base_fee, + block_hash, + } = overrides; + + if let Some(block_hashes) = block_hash { + // override block hashes + cache_db + .cache + .block_hashes + .extend(block_hashes.into_iter().map(|(num, hash)| (U256::from(num), hash))) + } + + if let Some(number) = number { + env.number = number.saturating_to(); + } + if let Some(difficulty) = difficulty { + env.difficulty = difficulty; + } + if let Some(time) = time { + env.timestamp = time; + } + if let Some(gas_limit) = gas_limit { + env.gas_limit = gas_limit; + } + if let Some(coinbase) = coinbase { + env.beneficiary = coinbase; + } + if let Some(random) = random { + env.prevrandao = Some(random); + } + if let Some(base_fee) = base_fee { + env.basefee = base_fee.saturating_to(); + } +} diff --git a/crates/anvil/src/eth/backend/mem/storage.rs b/crates/anvil/src/eth/backend/mem/storage.rs index e024d97505467..252435291cc68 100644 --- a/crates/anvil/src/eth/backend/mem/storage.rs +++ b/crates/anvil/src/eth/backend/mem/storage.rs @@ -5,6 +5,7 @@ use crate::eth::{ MaybeFullDatabase, SerializableBlock, SerializableHistoricalStates, SerializableTransaction, StateDb, }, + env::Env, mem::cache::DiskStateCache, }, error::BlockchainError, @@ -14,7 +15,7 @@ use alloy_consensus::constants::EMPTY_WITHDRAWALS; use alloy_eips::eip7685::EMPTY_REQUESTS_HASH; use alloy_primitives::{ map::{B256HashMap, HashMap}, - Bytes, B256, U256, U64, + Bytes, B256, U256, }; use alloy_rpc_types::{ trace::{ @@ -34,13 +35,12 @@ use anvil_core::eth::{ use anvil_rpc::error::RpcError; use foundry_evm::{ backend::MemDb, - revm::primitives::Env, traces::{ CallKind, FourByteInspector, GethTraceBuilder, ParityTraceBuilder, TracingInspectorConfig, }, }; use parking_lot::RwLock; -use revm::primitives::SpecId; +use revm::{context::Block as RevmBlock, primitives::hardfork::SpecId}; use std::{collections::VecDeque, fmt, path::PathBuf, sync::Arc, time::Duration}; // use yansi::Paint; @@ -255,11 +255,11 @@ pub struct BlockchainStorage { /// all stored blocks (block hash -> block) pub blocks: B256HashMap, /// mapping from block number -> block hash - pub hashes: HashMap, + pub hashes: HashMap, /// The current best hash pub best_hash: B256, /// The current best block number - pub best_number: U64, + pub best_number: u64, /// genesis hash of the chain pub genesis_hash: B256, /// Mapping from the transaction hash to a tuple containing the transaction as well as the @@ -286,11 +286,11 @@ impl BlockchainStorage { let partial_header = PartialHeader { timestamp, base_fee, - gas_limit: env.block.gas_limit.to::(), - beneficiary: env.block.coinbase, - difficulty: env.block.difficulty, - blob_gas_used: env.block.blob_excess_gas_and_price.as_ref().map(|_| 0), - excess_blob_gas: env.block.get_blob_excess_gas(), + gas_limit: env.evm_env.block_env.gas_limit, + beneficiary: env.evm_env.block_env.beneficiary, + difficulty: env.evm_env.block_env.difficulty, + blob_gas_used: env.evm_env.block_env.blob_excess_gas_and_price.as_ref().map(|_| 0), + excess_blob_gas: env.evm_env.block_env.blob_excess_gas(), number: genesis_number, parent_beacon_block_root: is_cancun.then_some(Default::default()), withdrawals_root: is_shanghai.then_some(EMPTY_WITHDRAWALS), @@ -300,7 +300,7 @@ impl BlockchainStorage { let block = Block::new::(partial_header, vec![]); let genesis_hash = block.header.hash_slow(); let best_hash = genesis_hash; - let best_number: U64 = U64::from(genesis_number); + let best_number = genesis_number; let mut blocks = B256HashMap::default(); blocks.insert(genesis_hash, block); @@ -320,13 +320,13 @@ impl BlockchainStorage { pub fn forked(block_number: u64, block_hash: B256, total_difficulty: U256) -> Self { let mut hashes = HashMap::default(); - hashes.insert(U64::from(block_number), block_hash); + hashes.insert(block_number, block_hash); Self { blocks: B256HashMap::default(), hashes, best_hash: block_hash, - best_number: U64::from(block_number), + best_number: block_number, genesis_hash: Default::default(), transactions: Default::default(), total_difficulty, @@ -338,16 +338,16 @@ impl BlockchainStorage { /// The block identified by `block_number` and `block_hash` is __non-inclusive__, i.e. it will /// remain in the state. pub fn unwind_to(&mut self, block_number: u64, block_hash: B256) { - let best_num: u64 = self.best_number.try_into().unwrap_or(0); + let best_num: u64 = self.best_number; for i in (block_number + 1)..=best_num { - if let Some(hash) = self.hashes.remove(&U64::from(i)) { + if let Some(hash) = self.hashes.remove(&i) { if let Some(block) = self.blocks.remove(&hash) { self.remove_block_transactions_by_number(block.header.number); } } } self.best_hash = block_hash; - self.best_number = U64::from(block_number); + self.best_number = block_number; } pub fn empty() -> Self { @@ -364,7 +364,7 @@ impl BlockchainStorage { /// Removes all stored transactions for the given block number pub fn remove_block_transactions_by_number(&mut self, num: u64) { - if let Some(hash) = self.hashes.get(&(U64::from(num))).copied() { + if let Some(hash) = self.hashes.get(&num).copied() { self.remove_block_transactions(hash); } } @@ -383,12 +383,12 @@ impl BlockchainStorage { impl BlockchainStorage { /// Returns the hash for [BlockNumberOrTag] pub fn hash(&self, number: BlockNumberOrTag) -> Option { - let slots_in_an_epoch = U64::from(32u64); + let slots_in_an_epoch = 32; match number { BlockNumberOrTag::Latest => Some(self.best_hash), BlockNumberOrTag::Earliest => Some(self.genesis_hash), BlockNumberOrTag::Pending => None, - BlockNumberOrTag::Number(num) => self.hashes.get(&U64::from(num)).copied(), + BlockNumberOrTag::Number(num) => self.hashes.get(&num).copied(), BlockNumberOrTag::Safe => { if self.best_number > (slots_in_an_epoch) { self.hashes.get(&(self.best_number - (slots_in_an_epoch))).copied() @@ -397,10 +397,8 @@ impl BlockchainStorage { } } BlockNumberOrTag::Finalized => { - if self.best_number > (slots_in_an_epoch * U64::from(2)) { - self.hashes - .get(&(self.best_number - (slots_in_an_epoch * U64::from(2)))) - .copied() + if self.best_number > (slots_in_an_epoch * 2) { + self.hashes.get(&(self.best_number - (slots_in_an_epoch * 2))).copied() } else { Some(self.genesis_hash) } @@ -423,7 +421,7 @@ impl BlockchainStorage { let block_hash = block.header.hash_slow(); let block_number = block.header.number; self.blocks.insert(block_hash, block); - self.hashes.insert(U64::from(block_number), block_hash); + self.hashes.insert(block_number, block_hash); } } @@ -499,7 +497,7 @@ impl Blockchain { #[derive(Clone, Debug)] pub struct MinedBlockOutcome { /// The block that was mined - pub block_number: U64, + pub block_number: u64, /// All transactions included in the block pub included: Vec>, /// All transactions that were attempted to be included but were invalid at the time of @@ -614,13 +612,7 @@ mod tests { use alloy_primitives::{hex, Address}; use alloy_rlp::Decodable; use anvil_core::eth::transaction::TypedTransaction; - use foundry_evm::{ - backend::MemDb, - revm::{ - db::DatabaseRef, - primitives::{AccountInfo, U256}, - }, - }; + use revm::{database::DatabaseRef, state::AccountInfo}; #[test] fn test_interval_update() { diff --git a/crates/anvil/src/eth/backend/mod.rs b/crates/anvil/src/eth/backend/mod.rs index 48b08e07cd1ec..be2c90b576800 100644 --- a/crates/anvil/src/eth/backend/mod.rs +++ b/crates/anvil/src/eth/backend/mod.rs @@ -8,6 +8,7 @@ pub mod mem; pub mod cheats; pub mod time; +pub mod env; pub mod executor; pub mod fork; pub mod genesis; diff --git a/crates/anvil/src/eth/backend/validate.rs b/crates/anvil/src/eth/backend/validate.rs index eca3fd9e3bfe9..0a22ea5f5a880 100644 --- a/crates/anvil/src/eth/backend/validate.rs +++ b/crates/anvil/src/eth/backend/validate.rs @@ -1,8 +1,11 @@ //! Support for validating transactions at certain stages -use crate::eth::error::{BlockchainError, InvalidTransactionError}; +use crate::eth::{ + backend::env::Env, + error::{BlockchainError, InvalidTransactionError}, +}; use anvil_core::eth::transaction::PendingTransaction; -use foundry_evm::revm::primitives::{AccountInfo, EnvWithHandlerCfg}; +use revm::state::AccountInfo; /// A trait for validating transactions #[async_trait::async_trait] @@ -21,7 +24,7 @@ pub trait TransactionValidator { &self, tx: &PendingTransaction, account: &AccountInfo, - env: &EnvWithHandlerCfg, + env: &Env, ) -> Result<(), InvalidTransactionError>; /// Validates the transaction against a specific account @@ -31,6 +34,6 @@ pub trait TransactionValidator { &self, tx: &PendingTransaction, account: &AccountInfo, - env: &EnvWithHandlerCfg, + env: &Env, ) -> Result<(), InvalidTransactionError>; } diff --git a/crates/anvil/src/eth/error.rs b/crates/anvil/src/eth/error.rs index 6666f8021dcfe..bd0c3b6e798d7 100644 --- a/crates/anvil/src/eth/error.rs +++ b/crates/anvil/src/eth/error.rs @@ -10,13 +10,11 @@ use anvil_rpc::{ error::{ErrorCode, RpcError}, response::ResponseResult, }; -use foundry_evm::{ - backend::DatabaseError, - decode::RevertDecoder, - revm::{ - interpreter::InstructionResult, - primitives::{EVMError, InvalidHeader}, - }, +use foundry_evm::{backend::DatabaseError, decode::RevertDecoder}; +use op_revm::OpTransactionError; +use revm::{ + context_interface::result::{EVMError, InvalidHeader, InvalidTransaction}, + interpreter::InstructionResult, }; use serde::Serialize; @@ -60,6 +58,8 @@ pub enum BlockchainError { AlloyForkProvider(#[from] TransportError), #[error("EVM error {0:?}")] EvmError(InstructionResult), + #[error("Evm override error: {0}")] + EvmOverrideError(String), #[error("Invalid url {0:?}")] InvalidUrl(String), #[error("Internal error: {0:?}")] @@ -122,7 +122,31 @@ where InvalidHeader::PrevrandaoNotSet => Self::PrevrandaoNotSet, }, EVMError::Database(err) => err.into(), - EVMError::Precompile(err) => Self::Message(err), + EVMError::Custom(err) => Self::Message(err), + } + } +} + +impl From> for BlockchainError +where + T: Into, +{ + fn from(err: EVMError) -> Self { + match err { + EVMError::Transaction(err) => match err { + OpTransactionError::Base(err) => InvalidTransactionError::from(err).into(), + OpTransactionError::DepositSystemTxPostRegolith => { + Self::DepositTransactionUnsupported + } + OpTransactionError::HaltedDepositPostRegolith => { + Self::DepositTransactionUnsupported + } + }, + EVMError::Header(err) => match err { + InvalidHeader::ExcessBlobGasNotSet => Self::ExcessBlobGasNotSet, + InvalidHeader::PrevrandaoNotSet => Self::PrevrandaoNotSet, + }, + EVMError::Database(err) => err.into(), EVMError::Custom(err) => Self::Message(err), } } @@ -246,8 +270,8 @@ pub enum InvalidTransactionError { /// Thrown when there are no `blob_hashes` in the transaction, and it is an EIP-4844 tx. #[error("`blob_hashes` are required for EIP-4844 transactions")] NoBlobHashes, - #[error("too many blobs in one transaction, have: {0}")] - TooManyBlobs(usize), + #[error("too many blobs in one transaction, have: {0}, max: {1}")] + TooManyBlobs(usize, usize), /// Thrown when there's a blob validation error #[error(transparent)] BlobTransactionValidationError(#[from] alloy_consensus::BlobTransactionValidationError), @@ -265,12 +289,14 @@ pub enum InvalidTransactionError { AuthorizationListNotSupported, /// Forwards error from the revm #[error(transparent)] - Revm(revm::primitives::InvalidTransaction), + Revm(revm::context_interface::result::InvalidTransaction), + /// Deposit transaction error post regolith + #[error("op-deposit failure post regolith")] + DepositTxErrorPostRegolith, } -impl From for InvalidTransactionError { - fn from(err: revm::primitives::InvalidTransaction) -> Self { - use revm::primitives::InvalidTransaction; +impl From for InvalidTransactionError { + fn from(err: InvalidTransaction) -> Self { match err { InvalidTransaction::InvalidChainId => Self::InvalidChainId, InvalidTransaction::PriorityFeeGreaterThanMaxFee => Self::TipAboveFeeCap, @@ -278,10 +304,10 @@ impl From for InvalidTransactionError { InvalidTransaction::CallerGasLimitMoreThanBlock => { Self::GasTooHigh(ErrDetail { detail: String::from("CallerGasLimitMoreThanBlock") }) } - InvalidTransaction::CallGasCostMoreThanGasLimit => { + InvalidTransaction::CallGasCostMoreThanGasLimit { .. } => { Self::GasTooHigh(ErrDetail { detail: String::from("CallGasCostMoreThanGasLimit") }) } - InvalidTransaction::GasFloorMoreThanGasLimit => { + InvalidTransaction::GasFloorMoreThanGasLimit { .. } => { Self::GasTooHigh(ErrDetail { detail: String::from("CallGasCostMoreThanGasLimit") }) } InvalidTransaction::RejectCallerWithCode => Self::SenderNoEOA, @@ -300,18 +326,32 @@ impl From for InvalidTransactionError { InvalidTransaction::BlobCreateTransaction => Self::BlobCreateTransaction, InvalidTransaction::BlobVersionNotSupported => Self::BlobVersionNotSupported, InvalidTransaction::EmptyBlobs => Self::EmptyBlobs, - InvalidTransaction::TooManyBlobs { have } => Self::TooManyBlobs(have), + InvalidTransaction::TooManyBlobs { have, max } => Self::TooManyBlobs(have, max), InvalidTransaction::AuthorizationListNotSupported => { Self::AuthorizationListNotSupported } InvalidTransaction::AuthorizationListInvalidFields | - InvalidTransaction::OptimismError(_) | - InvalidTransaction::EofCrateShouldHaveToAddress | - InvalidTransaction::EmptyAuthorizationList => Self::Revm(err), + InvalidTransaction::Eip1559NotSupported | + InvalidTransaction::Eip2930NotSupported | + InvalidTransaction::Eip4844NotSupported | + InvalidTransaction::Eip7702NotSupported | + InvalidTransaction::EofCreateShouldHaveToAddress | + InvalidTransaction::EmptyAuthorizationList | + InvalidTransaction::Eip7873NotSupported | + InvalidTransaction::Eip7873MissingTarget => Self::Revm(err), } } } +impl From for InvalidTransactionError { + fn from(value: OpTransactionError) -> Self { + match value { + OpTransactionError::Base(err) => err.into(), + OpTransactionError::DepositSystemTxPostRegolith | + OpTransactionError::HaltedDepositPostRegolith => Self::DepositTxErrorPostRegolith, + } + } +} /// Helper trait to easily convert results to rpc results pub(crate) trait ToRpcResponseResult { fn to_rpc_result(self) -> ResponseResult; @@ -428,6 +468,9 @@ impl ToRpcResponseResult for Result { err @ BlockchainError::EvmError(_) => { RpcError::internal_error_with(err.to_string()) } + err @ BlockchainError::EvmOverrideError(_) => { + RpcError::invalid_params(err.to_string()) + } err @ BlockchainError::InvalidUrl(_) => RpcError::invalid_params(err.to_string()), BlockchainError::Internal(err) => RpcError::internal_error_with(err), err @ BlockchainError::BlockOutOfRange(_, _) => { diff --git a/crates/anvil/src/eth/fees.rs b/crates/anvil/src/eth/fees.rs index bb405f62d1ef1..b9d5a2d9170d7 100644 --- a/crates/anvil/src/eth/fees.rs +++ b/crates/anvil/src/eth/fees.rs @@ -1,24 +1,26 @@ -use crate::eth::{ - backend::{info::StorageInfo, notifications::NewBlockNotifications}, - error::BlockchainError, +use std::{ + collections::BTreeMap, + fmt, + future::Future, + pin::Pin, + sync::Arc, + task::{Context, Poll}, }; + use alloy_consensus::Header; use alloy_eips::{ - calc_next_block_base_fee, eip1559::BaseFeeParams, eip4844::MAX_DATA_GAS_PER_BLOCK, + calc_next_block_base_fee, eip1559::BaseFeeParams, eip7691::MAX_BLOBS_PER_BLOCK_ELECTRA, eip7840::BlobParams, }; use alloy_primitives::B256; use anvil_core::eth::transaction::TypedTransaction; -use foundry_evm::revm::primitives::{BlobExcessGasAndPrice, SpecId}; use futures::StreamExt; use parking_lot::{Mutex, RwLock}; -use std::{ - collections::BTreeMap, - fmt, - future::Future, - pin::Pin, - sync::Arc, - task::{Context, Poll}, +use revm::{context_interface::block::BlobExcessGasAndPrice, primitives::hardfork::SpecId}; + +use crate::eth::{ + backend::{info::StorageInfo, notifications::NewBlockNotifications}, + error::BlockchainError, }; /// Maximum number of entries in the fee history cache @@ -54,7 +56,7 @@ pub struct FeeManager { /// Tracks the excess blob gas, and the base fee, for the next block post Cancun /// /// This value will be updated after a new block was mined - blob_excess_gas_and_price: Arc>, + blob_excess_gas_and_price: Arc>, /// The base price to use Pre London /// /// This will be constant value unless changed manually @@ -121,7 +123,7 @@ impl FeeManager { pub fn excess_blob_gas_and_price(&self) -> Option { if self.is_eip4844() { - Some(self.blob_excess_gas_and_price.read().clone()) + Some(*self.blob_excess_gas_and_price.read()) } else { None } @@ -158,8 +160,8 @@ impl FeeManager { /// Calculates the base fee for the next block pub fn get_next_block_base_fee_per_gas( &self, - gas_used: u128, - gas_limit: u128, + gas_used: u64, + gas_limit: u64, last_fee_per_gas: u64, ) -> u64 { // It's naturally impossible for base fee to be 0; @@ -178,22 +180,20 @@ impl FeeManager { /// Calculates the next block blob excess gas, using the provided parent blob gas used and /// parent blob excess gas - pub fn get_next_block_blob_excess_gas( - &self, - blob_gas_used: u128, - blob_excess_gas: u128, - ) -> u64 { - alloy_eips::eip4844::calc_excess_blob_gas(blob_gas_used as u64, blob_excess_gas as u64) + pub fn get_next_block_blob_excess_gas(&self, blob_gas_used: u64, blob_excess_gas: u64) -> u64 { + alloy_eips::eip4844::calc_excess_blob_gas(blob_gas_used, blob_excess_gas) } } /// Calculate base fee for next block. [EIP-1559](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-1559.md) spec -pub fn calculate_next_block_base_fee(gas_used: u128, gas_limit: u128, base_fee: u64) -> u64 { - calc_next_block_base_fee(gas_used as u64, gas_limit as u64, base_fee, BaseFeeParams::ethereum()) +pub fn calculate_next_block_base_fee(gas_used: u64, gas_limit: u64, base_fee: u64) -> u64 { + calc_next_block_base_fee(gas_used, gas_limit, base_fee, BaseFeeParams::ethereum()) } /// An async service that takes care of the `FeeHistory` cache pub struct FeeHistoryService { + /// blob parameters for the current spec + blob_params: BlobParams, /// incoming notifications about new blocks new_blocks: NewBlockNotifications, /// contains all fee history related entries @@ -206,11 +206,18 @@ pub struct FeeHistoryService { impl FeeHistoryService { pub fn new( + blob_params: BlobParams, new_blocks: NewBlockNotifications, cache: FeeHistoryCache, storage_info: StorageInfo, ) -> Self { - Self { new_blocks, cache, fee_history_limit: MAX_FEE_HISTORY_CACHE_SIZE, storage_info } + Self { + blob_params, + new_blocks, + cache, + fee_history_limit: MAX_FEE_HISTORY_CACHE_SIZE, + storage_info, + } } /// Returns the configured history limit @@ -247,7 +254,8 @@ impl FeeHistoryService { let base_fee = header.base_fee_per_gas.map(|g| g as u128).unwrap_or_default(); let excess_blob_gas = header.excess_blob_gas.map(|g| g as u128); let blob_gas_used = header.blob_gas_used.map(|g| g as u128); - let base_fee_per_blob_gas = header.blob_fee(BlobParams::cancun()); + let base_fee_per_blob_gas = header.blob_fee(self.blob_params); + let mut item = FeeHistoryCacheItem { base_fee, gas_used_ratio: 0f64, @@ -268,7 +276,7 @@ impl FeeHistoryService { let blob_gas_used = block.header.blob_gas_used.map(|g| g as f64); item.gas_used_ratio = gas_used / block.header.gas_limit as f64; item.blob_gas_used_ratio = - blob_gas_used.map(|g| g / MAX_DATA_GAS_PER_BLOCK as f64).unwrap_or(0 as f64); + blob_gas_used.map(|g| g / MAX_BLOBS_PER_BLOCK_ELECTRA as f64).unwrap_or(0 as f64); // extract useful tx info (gas_used, effective_reward) let mut transactions: Vec<(_, _)> = receipts @@ -315,7 +323,7 @@ impl FeeHistoryService { .filter_map(|p| { let target_gas = (p * gas_used / 100f64) as u64; let mut sum_gas = 0; - for (gas_used, effective_reward) in transactions.iter().cloned() { + for (gas_used, effective_reward) in transactions.iter().copied() { sum_gas += gas_used; if target_gas <= sum_gas { return Some(effective_reward) diff --git a/crates/anvil/src/eth/otterscan/api.rs b/crates/anvil/src/eth/otterscan/api.rs index 227a8e421a260..66322bd21a392 100644 --- a/crates/anvil/src/eth/otterscan/api.rs +++ b/crates/anvil/src/eth/otterscan/api.rs @@ -73,7 +73,7 @@ pub fn batch_build_ots_traces(traces: Vec) -> Vec BlockTransactions::Hashes( - txs.iter().skip(page * page_size).take(page_size).cloned().collect(), + txs.iter().skip(page * page_size).take(page_size).copied().collect(), ), BlockTransactions::Uncle => unreachable!(), }; diff --git a/crates/anvil/src/eth/pool/mod.rs b/crates/anvil/src/eth/pool/mod.rs index 544d7eac9df37..2133ea083df24 100644 --- a/crates/anvil/src/eth/pool/mod.rs +++ b/crates/anvil/src/eth/pool/mod.rs @@ -36,7 +36,7 @@ use crate::{ }, mem::storage::MinedBlockOutcome, }; -use alloy_primitives::{Address, TxHash, U64}; +use alloy_primitives::{Address, TxHash}; use alloy_rpc_types::txpool::TxpoolStatus; use anvil_core::eth::transaction::PendingTransaction; use futures::channel::mpsc::{channel, Receiver, Sender}; @@ -102,7 +102,7 @@ impl Pool { /// directly or are a dependency of the transaction associated with that marker. pub fn prune_markers( &self, - block_number: U64, + block_number: u64, markers: impl IntoIterator, ) -> PruneResult { debug!(target: "txpool", ?block_number, "pruning transactions"); diff --git a/crates/anvil/src/eth/pool/transactions.rs b/crates/anvil/src/eth/pool/transactions.rs index 36e421d7a50e1..293c7ce3b6e7b 100644 --- a/crates/anvil/src/eth/pool/transactions.rs +++ b/crates/anvil/src/eth/pool/transactions.rs @@ -98,6 +98,11 @@ impl PoolTransaction { pub fn gas_price(&self) -> u128 { self.pending_transaction.transaction.gas_price() } + + /// Returns the type of the transaction + pub fn tx_type(&self) -> u8 { + self.pending_transaction.transaction.r#type().unwrap_or_default() + } } impl fmt::Debug for PoolTransaction { @@ -516,7 +521,7 @@ impl ReadyTransactions { } } - unlocked_tx.extend(to_remove.unlocks.iter().cloned()) + unlocked_tx.extend(to_remove.unlocks.iter().copied()) } } diff --git a/crates/anvil/src/eth/sign.rs b/crates/anvil/src/eth/sign.rs index e2ea036a0cafb..3dc07f347f16c 100644 --- a/crates/anvil/src/eth/sign.rs +++ b/crates/anvil/src/eth/sign.rs @@ -2,13 +2,10 @@ use crate::eth::error::BlockchainError; use alloy_consensus::SignableTransaction; use alloy_dyn_abi::TypedData; use alloy_network::TxSignerSync; -use alloy_primitives::{map::AddressHashMap, Address, PrimitiveSignature as Signature, B256, U256}; +use alloy_primitives::{map::AddressHashMap, Address, Signature, B256}; use alloy_signer::Signer as AlloySigner; use alloy_signer_local::PrivateKeySigner; -use anvil_core::eth::transaction::{ - optimism::DepositTransaction, TypedTransaction, TypedTransactionRequest, -}; -use op_alloy_consensus::TxDeposit; +use anvil_core::eth::transaction::{TypedTransaction, TypedTransactionRequest}; /// A transaction signer #[async_trait::async_trait] @@ -52,7 +49,7 @@ pub struct DevSigner { impl DevSigner { pub fn new(accounts: Vec) -> Self { let addresses = accounts.iter().map(|wallet| wallet.address()).collect::>(); - let accounts = addresses.iter().cloned().zip(accounts).collect(); + let accounts = addresses.iter().copied().zip(accounts).collect(); Self { addresses, accounts } } } @@ -104,6 +101,7 @@ impl Signer for DevSigner { TypedTransactionRequest::Legacy(mut tx) => Ok(signer.sign_transaction_sync(&mut tx)?), TypedTransactionRequest::EIP2930(mut tx) => Ok(signer.sign_transaction_sync(&mut tx)?), TypedTransactionRequest::EIP1559(mut tx) => Ok(signer.sign_transaction_sync(&mut tx)?), + TypedTransactionRequest::EIP7702(mut tx) => Ok(signer.sign_transaction_sync(&mut tx)?), TypedTransactionRequest::EIP4844(mut tx) => Ok(signer.sign_transaction_sync(&mut tx)?), TypedTransactionRequest::Deposit(_) => { unreachable!("op deposit txs should not be signed") @@ -129,33 +127,13 @@ pub fn build_typed_transaction( TypedTransactionRequest::EIP1559(tx) => { TypedTransaction::EIP1559(tx.into_signed(signature)) } + TypedTransactionRequest::EIP7702(tx) => { + TypedTransaction::EIP7702(tx.into_signed(signature)) + } TypedTransactionRequest::EIP4844(tx) => { TypedTransaction::EIP4844(tx.into_signed(signature)) } - TypedTransactionRequest::Deposit(tx) => { - let TxDeposit { - from, - gas_limit, - to, - value, - input, - source_hash, - mint, - is_system_transaction, - .. - } = tx; - TypedTransaction::Deposit(DepositTransaction { - from, - gas_limit, - kind: to, - value, - input, - source_hash, - mint: mint.map_or(U256::ZERO, U256::from), - is_system_tx: is_system_transaction, - nonce: 0, - }) - } + TypedTransactionRequest::Deposit(tx) => TypedTransaction::Deposit(tx), }; Ok(tx) diff --git a/crates/anvil/src/eth/util.rs b/crates/anvil/src/eth/util.rs index ca66f2ed3d3c5..beb73276c3b4d 100644 --- a/crates/anvil/src/eth/util.rs +++ b/crates/anvil/src/eth/util.rs @@ -1,7 +1,7 @@ use alloy_primitives::Address; -use foundry_evm::revm::{ +use revm::{ precompile::{PrecompileSpecId, Precompiles}, - primitives::SpecId, + primitives::hardfork::SpecId, }; use std::fmt; diff --git a/crates/anvil/src/evm.rs b/crates/anvil/src/evm.rs index eef0800eae119..0d70250c4cb6e 100644 --- a/crates/anvil/src/evm.rs +++ b/crates/anvil/src/evm.rs @@ -1,81 +1,280 @@ -use alloy_primitives::Address; -use foundry_evm::revm::precompile::Precompile; -use std::{fmt::Debug, sync::Arc}; +use std::fmt::Debug; + +use alloy_evm::{ + eth::EthEvmContext, + precompiles::{DynPrecompile, PrecompilesMap}, + Database, Evm, +}; +use foundry_evm_core::either_evm::EitherEvm; +use op_revm::OpContext; +use revm::{precompile::PrecompileWithAddress, Inspector}; /// Object-safe trait that enables injecting extra precompiles when using /// `anvil` as a library. pub trait PrecompileFactory: Send + Sync + Unpin + Debug { /// Returns a set of precompiles to extend the EVM with. - fn precompiles(&self) -> Vec<(Address, Precompile)>; + fn precompiles(&self) -> Vec; } -/// Appends a handler register to `evm` that injects the given `precompiles`. -/// -/// This will add an additional handler that extends the default precompiles with the given set of -/// precompiles. -pub fn inject_precompiles( - evm: &mut revm::Evm<'_, I, DB>, - precompiles: Vec<(Address, Precompile)>, -) { - evm.handler.append_handler_register_box(Box::new(move |handler| { - let precompiles = precompiles.clone(); - let prev = handler.pre_execution.load_precompiles.clone(); - handler.pre_execution.load_precompiles = Arc::new(move || { - let mut cx = prev(); - cx.extend(precompiles.iter().cloned().map(|(a, b)| (a, b.into()))); - cx - }); - })); +/// Inject precompiles into the EVM dynamically. +pub fn inject_precompiles( + evm: &mut EitherEvm, + precompiles: Vec, +) where + DB: Database, + I: Inspector> + Inspector>, +{ + for p in precompiles { + evm.precompiles_mut() + .apply_precompile(p.address(), |_| Some(DynPrecompile::from(*p.precompile()))); + } } #[cfg(test)] mod tests { - use crate::{evm::inject_precompiles, PrecompileFactory}; - use alloy_primitives::Address; - use foundry_evm::revm::primitives::{address, Bytes, Precompile, PrecompileResult, SpecId}; - use revm::primitives::PrecompileOutput; + use std::convert::Infallible; - #[test] - fn build_evm_with_extra_precompiles() { - const PRECOMPILE_ADDR: Address = address!("0x0000000000000000000000000000000000000071"); + use alloy_evm::{eth::EthEvmContext, precompiles::PrecompilesMap, EthEvm, Evm, EvmEnv}; + use alloy_op_evm::OpEvm; + use alloy_primitives::{address, Address, Bytes, TxKind, U256}; + use foundry_evm_core::either_evm::EitherEvm; + use itertools::Itertools; + use op_revm::{precompiles::OpPrecompiles, L1BlockInfo, OpContext, OpSpecId, OpTransaction}; + use revm::{ + context::{CfgEnv, Evm as RevmEvm, JournalTr, LocalContext, TxEnv}, + database::{EmptyDB, EmptyDBTyped}, + handler::{instructions::EthInstructions, EthPrecompiles}, + inspector::NoOpInspector, + interpreter::interpreter::EthInterpreter, + precompile::{ + PrecompileOutput, PrecompileResult, PrecompileSpecId, PrecompileWithAddress, + Precompiles, + }, + primitives::hardfork::SpecId, + Journal, + }; + + use crate::{inject_precompiles, PrecompileFactory}; + + // A precompile activated in the `Prague` spec. + const ETH_PRAGUE_PRECOMPILE: Address = address!("0x0000000000000000000000000000000000000011"); + + // A precompile activated in the `Isthmus` spec. + const OP_ISTHMUS_PRECOMPILE: Address = address!("0x0000000000000000000000000000000000000100"); + + // A custom precompile address and payload for testing. + const PRECOMPILE_ADDR: Address = address!("0x0000000000000000000000000000000000000071"); + const PAYLOAD: &[u8] = &[0xde, 0xad, 0xbe, 0xef]; + + #[derive(Debug)] + struct CustomPrecompileFactory; - fn my_precompile(_bytes: &Bytes, _gas_limit: u64) -> PrecompileResult { - Ok(PrecompileOutput { bytes: Bytes::new(), gas_used: 0 }) + impl PrecompileFactory for CustomPrecompileFactory { + fn precompiles(&self) -> Vec { + vec![PrecompileWithAddress::from(( + PRECOMPILE_ADDR, + custom_echo_precompile as fn(&[u8], u64) -> PrecompileResult, + ))] } + } + + /// Custom precompile that echoes the input data. + /// In this example it uses `0xdeadbeef` as the input data, returning it as output. + fn custom_echo_precompile(input: &[u8], _gas_limit: u64) -> PrecompileResult { + Ok(PrecompileOutput { bytes: Bytes::copy_from_slice(input), gas_used: 0 }) + } + + /// Creates a new EVM instance with the custom precompile factory. + fn create_eth_evm( + spec: SpecId, + ) -> (foundry_evm::Env, EitherEvm, NoOpInspector, PrecompilesMap>) + { + let eth_env = foundry_evm::Env { + evm_env: EvmEnv { block_env: Default::default(), cfg_env: CfgEnv::new_with_spec(spec) }, + tx: TxEnv { + kind: TxKind::Call(PRECOMPILE_ADDR), + data: PAYLOAD.into(), + ..Default::default() + }, + }; + + let eth_evm_context = EthEvmContext { + journaled_state: Journal::new(EmptyDB::default()), + block: eth_env.evm_env.block_env.clone(), + cfg: eth_env.evm_env.cfg_env.clone(), + tx: eth_env.tx.clone(), + chain: (), + local: LocalContext::default(), + error: Ok(()), + }; + + let eth_precompiles = EthPrecompiles { + precompiles: Precompiles::new(PrecompileSpecId::from_spec_id(spec)), + spec, + } + .precompiles; + let eth_evm = EitherEvm::Eth(EthEvm::new( + RevmEvm::new_with_inspector( + eth_evm_context, + NoOpInspector, + EthInstructions::>::default(), + PrecompilesMap::from_static(eth_precompiles), + ), + true, + )); + + (eth_env, eth_evm) + } + + /// Creates a new OP EVM instance with the custom precompile factory. + fn create_op_evm( + spec: SpecId, + op_spec: OpSpecId, + ) -> ( + crate::eth::backend::env::Env, + EitherEvm, NoOpInspector, PrecompilesMap>, + ) { + let op_env = crate::eth::backend::env::Env { + evm_env: EvmEnv { block_env: Default::default(), cfg_env: CfgEnv::new_with_spec(spec) }, + tx: OpTransaction:: { + base: TxEnv { + kind: TxKind::Call(PRECOMPILE_ADDR), + data: PAYLOAD.into(), + ..Default::default() + }, + ..Default::default() + }, + is_optimism: true, + }; - #[derive(Debug)] - struct CustomPrecompileFactory; + let mut chain = L1BlockInfo::default(); - impl PrecompileFactory for CustomPrecompileFactory { - fn precompiles(&self) -> Vec<(Address, Precompile)> { - vec![(PRECOMPILE_ADDR, Precompile::Standard(my_precompile))] - } + if op_spec == OpSpecId::ISTHMUS { + chain.operator_fee_constant = Some(U256::from(0)); + chain.operator_fee_scalar = Some(U256::from(0)); } - let db = revm::db::EmptyDB::default(); - let env = Box::::default(); - let spec = SpecId::LATEST; - let handler_cfg = revm::primitives::HandlerCfg::new(spec); - let inspector = revm::inspectors::NoOpInspector; - let context = revm::Context::new(revm::EvmContext::new_with_env(db, env), inspector); - let handler = revm::Handler::new(handler_cfg); - let mut evm = revm::Evm::new(context, handler); - assert!(!evm - .handler - .pre_execution() - .load_precompiles() - .addresses() - .any(|&addr| addr == PRECOMPILE_ADDR)); + let op_cfg = op_env.evm_env.cfg_env.clone().with_spec(op_spec); + let op_evm_context = OpContext { + journaled_state: { + let mut journal = Journal::new(EmptyDB::default()); + // Converting SpecId into OpSpecId + journal.set_spec_id(op_env.evm_env.cfg_env.spec); + journal + }, + block: op_env.evm_env.block_env.clone(), + cfg: op_cfg.clone(), + tx: op_env.tx.clone(), + chain, + local: LocalContext::default(), + error: Ok(()), + }; + + let op_precompiles = OpPrecompiles::new_with_spec(op_cfg.spec).precompiles(); + let op_evm = EitherEvm::Op(OpEvm::new( + op_revm::OpEvm(RevmEvm::new_with_inspector( + op_evm_context, + NoOpInspector, + EthInstructions::>::default(), + PrecompilesMap::from_static(op_precompiles), + )), + true, + )); + + (op_env, op_evm) + } + + #[test] + fn build_eth_evm_with_extra_precompiles_default_spec() { + let (env, mut evm) = create_eth_evm(SpecId::default()); + + // Check that the Prague precompile IS present when using the default spec. + assert!(evm.precompiles().addresses().contains(Ð_PRAGUE_PRECOMPILE)); + + assert!(!evm.precompiles().addresses().contains(&PRECOMPILE_ADDR)); inject_precompiles(&mut evm, CustomPrecompileFactory.precompiles()); - assert!(evm - .handler - .pre_execution() - .load_precompiles() - .addresses() - .any(|&addr| addr == PRECOMPILE_ADDR)); - - let result = evm.transact().unwrap(); + + assert!(evm.precompiles().addresses().contains(&PRECOMPILE_ADDR)); + + let result = match &mut evm { + EitherEvm::Eth(eth_evm) => eth_evm.transact(env.tx).unwrap(), + _ => unreachable!(), + }; + + assert!(result.result.is_success()); + assert_eq!(result.result.output(), Some(&PAYLOAD.into())); + } + + #[test] + fn build_eth_evm_with_extra_precompiles_london_spec() { + let (env, mut evm) = create_eth_evm(SpecId::LONDON); + + // Check that the Prague precompile IS NOT present when using the London spec. + assert!(!evm.precompiles().addresses().contains(Ð_PRAGUE_PRECOMPILE)); + + assert!(!evm.precompiles().addresses().contains(&PRECOMPILE_ADDR)); + + inject_precompiles(&mut evm, CustomPrecompileFactory.precompiles()); + + assert!(evm.precompiles().addresses().contains(&PRECOMPILE_ADDR)); + + let result = match &mut evm { + EitherEvm::Eth(eth_evm) => eth_evm.transact(env.tx).unwrap(), + _ => unreachable!(), + }; + + assert!(result.result.is_success()); + assert_eq!(result.result.output(), Some(&PAYLOAD.into())); + } + + #[test] + fn build_op_evm_with_extra_precompiles_default_spec() { + let (env, mut evm) = create_op_evm(SpecId::default(), OpSpecId::default()); + + // Check that the Isthmus precompile IS present when using the default spec. + assert!(evm.precompiles().addresses().contains(&OP_ISTHMUS_PRECOMPILE)); + + // Check that the Prague precompile IS present when using the default spec. + assert!(evm.precompiles().addresses().contains(Ð_PRAGUE_PRECOMPILE)); + + assert!(!evm.precompiles().addresses().contains(&PRECOMPILE_ADDR)); + + inject_precompiles(&mut evm, CustomPrecompileFactory.precompiles()); + + assert!(evm.precompiles().addresses().contains(&PRECOMPILE_ADDR)); + + let result = match &mut evm { + EitherEvm::Op(op_evm) => op_evm.transact(env.tx).unwrap(), + _ => unreachable!(), + }; + + assert!(result.result.is_success()); + assert_eq!(result.result.output(), Some(&PAYLOAD.into())); + } + + #[test] + fn build_op_evm_with_extra_precompiles_bedrock_spec() { + let (env, mut evm) = create_op_evm(SpecId::default(), OpSpecId::BEDROCK); + + // Check that the Isthmus precompile IS NOT present when using the `OpSpecId::BEDROCK` spec. + assert!(!evm.precompiles().addresses().contains(&OP_ISTHMUS_PRECOMPILE)); + + // Check that the Prague precompile IS NOT present when using the `OpSpecId::BEDROCK` spec. + assert!(!evm.precompiles().addresses().contains(Ð_PRAGUE_PRECOMPILE)); + + assert!(!evm.precompiles().addresses().contains(&PRECOMPILE_ADDR)); + + inject_precompiles(&mut evm, CustomPrecompileFactory.precompiles()); + + assert!(evm.precompiles().addresses().contains(&PRECOMPILE_ADDR)); + + let result = match &mut evm { + EitherEvm::Op(op_evm) => op_evm.transact(env.tx).unwrap(), + _ => unreachable!(), + }; + assert!(result.result.is_success()); + assert_eq!(result.result.output(), Some(&PAYLOAD.into())); } } diff --git a/crates/anvil/src/hardfork.rs b/crates/anvil/src/hardfork.rs index d85e12f7decfa..f5e063aafc625 100644 --- a/crates/anvil/src/hardfork.rs +++ b/crates/anvil/src/hardfork.rs @@ -1,12 +1,14 @@ +use alloy_hardforks::EthereumHardfork; +use alloy_op_hardforks::OpHardfork::{self}; use alloy_rpc_types::BlockNumberOrTag; -use eyre::bail; -use foundry_evm::revm::primitives::SpecId; -use std::str::FromStr; + +use op_revm::OpSpecId; +use revm::primitives::hardfork::SpecId; #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] pub enum ChainHardfork { Ethereum(EthereumHardfork), - Optimism(OptimismHardfork), + Optimism(OpHardfork), } impl From for ChainHardfork { @@ -15,8 +17,8 @@ impl From for ChainHardfork { } } -impl From for ChainHardfork { - fn from(value: OptimismHardfork) -> Self { +impl From for ChainHardfork { + fn from(value: OpHardfork) -> Self { Self::Optimism(value) } } @@ -24,215 +26,99 @@ impl From for ChainHardfork { impl From for SpecId { fn from(fork: ChainHardfork) -> Self { match fork { - ChainHardfork::Ethereum(hardfork) => hardfork.into(), - ChainHardfork::Optimism(hardfork) => hardfork.into(), - } - } -} - -#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub enum EthereumHardfork { - Frontier, - Homestead, - Dao, - Tangerine, - SpuriousDragon, - Byzantium, - Constantinople, - Petersburg, - Istanbul, - Muirglacier, - Berlin, - London, - ArrowGlacier, - GrayGlacier, - Paris, - Shanghai, - Cancun, - Prague, - PragueEOF, - #[default] - Latest, -} - -impl EthereumHardfork { - /// Get the first block number of the hardfork. - pub fn fork_block(&self) -> u64 { - match *self { - Self::Frontier => 0, - Self::Homestead => 1150000, - Self::Dao => 1920000, - Self::Tangerine => 2463000, - Self::SpuriousDragon => 2675000, - Self::Byzantium => 4370000, - Self::Constantinople | Self::Petersburg => 7280000, - Self::Istanbul => 9069000, - Self::Muirglacier => 9200000, - Self::Berlin => 12244000, - Self::London => 12965000, - Self::ArrowGlacier => 13773000, - Self::GrayGlacier => 15050000, - Self::Paris => 15537394, - Self::Shanghai => 17034870, - Self::Cancun | Self::Latest => 19426587, - // TODO: add block after activation - Self::Prague | Self::PragueEOF => unreachable!(), + ChainHardfork::Ethereum(hardfork) => spec_id_from_ethereum_hardfork(hardfork), + ChainHardfork::Optimism(hardfork) => spec_id_from_optimism_hardfork(hardfork).into(), } } } -impl FromStr for EthereumHardfork { - type Err = eyre::Report; - - fn from_str(s: &str) -> Result { - let s = s.to_lowercase(); - let hardfork = match s.as_str() { - "frontier" | "1" => Self::Frontier, - "homestead" | "2" => Self::Homestead, - "dao" | "3" => Self::Dao, - "tangerine" | "4" => Self::Tangerine, - "spuriousdragon" | "5" => Self::SpuriousDragon, - "byzantium" | "6" => Self::Byzantium, - "constantinople" | "7" => Self::Constantinople, - "petersburg" | "8" => Self::Petersburg, - "istanbul" | "9" => Self::Istanbul, - "muirglacier" | "10" => Self::Muirglacier, - "berlin" | "11" => Self::Berlin, - "london" | "12" => Self::London, - "arrowglacier" | "13" => Self::ArrowGlacier, - "grayglacier" | "14" => Self::GrayGlacier, - "paris" | "merge" | "15" => Self::Paris, - "shanghai" | "16" => Self::Shanghai, - "cancun" | "17" => Self::Cancun, - "prague" | "18" => Self::Prague, - "pragueeof" | "19" | "prague-eof" => Self::PragueEOF, - "latest" => Self::Latest, - _ => bail!("Unknown hardfork {s}"), - }; - Ok(hardfork) +/// Map an EthereumHardfork enum into its corresponding SpecId. +pub fn spec_id_from_ethereum_hardfork(hardfork: EthereumHardfork) -> SpecId { + match hardfork { + EthereumHardfork::Frontier => SpecId::FRONTIER, + EthereumHardfork::Homestead => SpecId::HOMESTEAD, + EthereumHardfork::Dao => SpecId::DAO_FORK, + EthereumHardfork::Tangerine => SpecId::TANGERINE, + EthereumHardfork::SpuriousDragon => SpecId::SPURIOUS_DRAGON, + EthereumHardfork::Byzantium => SpecId::BYZANTIUM, + EthereumHardfork::Constantinople => SpecId::CONSTANTINOPLE, + EthereumHardfork::Petersburg => SpecId::PETERSBURG, + EthereumHardfork::Istanbul => SpecId::ISTANBUL, + EthereumHardfork::MuirGlacier => SpecId::MUIR_GLACIER, + EthereumHardfork::Berlin => SpecId::BERLIN, + EthereumHardfork::London => SpecId::LONDON, + EthereumHardfork::ArrowGlacier => SpecId::ARROW_GLACIER, + EthereumHardfork::GrayGlacier => SpecId::GRAY_GLACIER, + EthereumHardfork::Paris => SpecId::MERGE, + EthereumHardfork::Shanghai => SpecId::SHANGHAI, + EthereumHardfork::Cancun => SpecId::CANCUN, + EthereumHardfork::Prague => SpecId::PRAGUE, + EthereumHardfork::Osaka => SpecId::OSAKA, } } -impl From for SpecId { - fn from(fork: EthereumHardfork) -> Self { - match fork { - EthereumHardfork::Frontier => Self::FRONTIER, - EthereumHardfork::Homestead => Self::HOMESTEAD, - EthereumHardfork::Dao => Self::HOMESTEAD, - EthereumHardfork::Tangerine => Self::TANGERINE, - EthereumHardfork::SpuriousDragon => Self::SPURIOUS_DRAGON, - EthereumHardfork::Byzantium => Self::BYZANTIUM, - EthereumHardfork::Constantinople => Self::CONSTANTINOPLE, - EthereumHardfork::Petersburg => Self::PETERSBURG, - EthereumHardfork::Istanbul => Self::ISTANBUL, - EthereumHardfork::Muirglacier => Self::MUIR_GLACIER, - EthereumHardfork::Berlin => Self::BERLIN, - EthereumHardfork::London => Self::LONDON, - EthereumHardfork::ArrowGlacier => Self::LONDON, - EthereumHardfork::GrayGlacier => Self::GRAY_GLACIER, - EthereumHardfork::Paris => Self::MERGE, - EthereumHardfork::Shanghai => Self::SHANGHAI, - EthereumHardfork::Cancun | EthereumHardfork::Latest => Self::CANCUN, - EthereumHardfork::Prague => Self::PRAGUE, - // TODO: switch to latest after activation - // EOF is included in OSAKA from Revm 16.0.0 - EthereumHardfork::PragueEOF => Self::OSAKA, - } +/// Map an OptimismHardfork enum into its corresponding OpSpecId. +pub fn spec_id_from_optimism_hardfork(hardfork: OpHardfork) -> OpSpecId { + match hardfork { + OpHardfork::Bedrock => OpSpecId::BEDROCK, + OpHardfork::Regolith => OpSpecId::REGOLITH, + OpHardfork::Canyon => OpSpecId::CANYON, + OpHardfork::Ecotone => OpSpecId::ECOTONE, + OpHardfork::Fjord => OpSpecId::FJORD, + OpHardfork::Granite => OpSpecId::GRANITE, + OpHardfork::Holocene => OpSpecId::HOLOCENE, + OpHardfork::Isthmus => OpSpecId::ISTHMUS, + OpHardfork::Interop => OpSpecId::INTEROP, } } -impl> From for EthereumHardfork { - fn from(block: T) -> Self { - let num = match block.into() { - BlockNumberOrTag::Earliest => 0, - BlockNumberOrTag::Number(num) => num, - _ => u64::MAX, - }; - - match num { - _i if num < 1_150_000 => Self::Frontier, - _i if num < 1_920_000 => Self::Dao, - _i if num < 2_463_000 => Self::Homestead, - _i if num < 2_675_000 => Self::Tangerine, - _i if num < 4_370_000 => Self::SpuriousDragon, - _i if num < 7_280_000 => Self::Byzantium, - _i if num < 9_069_000 => Self::Constantinople, - _i if num < 9_200_000 => Self::Istanbul, - _i if num < 12_244_000 => Self::Muirglacier, - _i if num < 12_965_000 => Self::Berlin, - _i if num < 13_773_000 => Self::London, - _i if num < 15_050_000 => Self::ArrowGlacier, - _i if num < 17_034_870 => Self::Paris, - _i if num < 19_426_587 => Self::Shanghai, - _ => Self::Latest, - } - } -} +/// Convert a `BlockNumberOrTag` into an `EthereumHardfork`. +pub fn ethereum_hardfork_from_block_tag(block: impl Into) -> EthereumHardfork { + let num = match block.into() { + BlockNumberOrTag::Earliest => 0, + BlockNumberOrTag::Number(num) => num, + _ => u64::MAX, + }; -#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub enum OptimismHardfork { - Bedrock, - Regolith, - Canyon, - Ecotone, - Fjord, - Granite, - Holocene, - Isthmus, - #[default] - Latest, + EthereumHardfork::from_mainnet_block_number(num) } -impl FromStr for OptimismHardfork { - type Err = eyre::Report; +#[cfg(test)] +mod tests { + use super::*; + use alloy_hardforks::ethereum::mainnet::*; + #[allow(unused_imports)] + use alloy_rpc_types::BlockNumberOrTag; - fn from_str(s: &str) -> Result { - let s = s.to_lowercase(); - let hardfork = match s.as_str() { - "bedrock" => Self::Bedrock, - "regolith" => Self::Regolith, - "canyon" => Self::Canyon, - "ecotone" => Self::Ecotone, - "fjord" => Self::Fjord, - "granite" => Self::Granite, - "holocene" => Self::Holocene, - "isthmus" => Self::Isthmus, - "latest" => Self::Latest, - _ => bail!("Unknown hardfork {s}"), - }; - Ok(hardfork) - } -} + #[test] + fn test_ethereum_spec_id_mapping() { + assert_eq!(spec_id_from_ethereum_hardfork(EthereumHardfork::Frontier), SpecId::FRONTIER); + assert_eq!(spec_id_from_ethereum_hardfork(EthereumHardfork::Homestead), SpecId::HOMESTEAD); -impl From for SpecId { - fn from(fork: OptimismHardfork) -> Self { - match fork { - OptimismHardfork::Bedrock => Self::BEDROCK, - OptimismHardfork::Regolith => Self::REGOLITH, - OptimismHardfork::Canyon => Self::CANYON, - OptimismHardfork::Ecotone => Self::ECOTONE, - OptimismHardfork::Fjord => Self::FJORD, - OptimismHardfork::Granite => Self::GRANITE, - OptimismHardfork::Holocene => Self::HOLOCENE, - OptimismHardfork::Isthmus => Self::ISTHMUS, - OptimismHardfork::Latest => Self::LATEST, - } + // Test latest hardforks + assert_eq!(spec_id_from_ethereum_hardfork(EthereumHardfork::Cancun), SpecId::CANCUN); + assert_eq!(spec_id_from_ethereum_hardfork(EthereumHardfork::Prague), SpecId::PRAGUE); } -} - -#[cfg(test)] -mod tests { - use crate::EthereumHardfork; #[test] - fn test_hardfork_blocks() { - let hf: EthereumHardfork = 12_965_000u64.into(); - assert_eq!(hf, EthereumHardfork::London); + fn test_optimism_spec_id_mapping() { + assert_eq!(spec_id_from_optimism_hardfork(OpHardfork::Bedrock), OpSpecId::BEDROCK); + assert_eq!(spec_id_from_optimism_hardfork(OpHardfork::Regolith), OpSpecId::REGOLITH); - let hf: EthereumHardfork = 4370000u64.into(); - assert_eq!(hf, EthereumHardfork::Byzantium); + // Test latest hardforks + assert_eq!(spec_id_from_optimism_hardfork(OpHardfork::Holocene), OpSpecId::HOLOCENE); + assert_eq!(spec_id_from_optimism_hardfork(OpHardfork::Interop), OpSpecId::INTEROP); + } - let hf: EthereumHardfork = 12244000u64.into(); - assert_eq!(hf, EthereumHardfork::Berlin); + #[test] + fn test_hardfork_from_block_tag_numbers() { + assert_eq!( + ethereum_hardfork_from_block_tag(MAINNET_HOMESTEAD_BLOCK - 1), + EthereumHardfork::Frontier + ); + assert_eq!( + ethereum_hardfork_from_block_tag(MAINNET_LONDON_BLOCK + 1), + EthereumHardfork::London + ); } } diff --git a/crates/anvil/src/lib.rs b/crates/anvil/src/lib.rs index d5576596e1ea0..c5ae402917b53 100644 --- a/crates/anvil/src/lib.rs +++ b/crates/anvil/src/lib.rs @@ -1,6 +1,7 @@ //! Anvil is a fast local Ethereum development node. #![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] +#![cfg_attr(not(test), warn(unused_crate_dependencies))] use crate::{ eth::{ @@ -18,14 +19,15 @@ use crate::{ shutdown::Signal, tasks::TaskManager, }; +use alloy_eips::eip7840::BlobParams; use alloy_primitives::{Address, U256}; use alloy_signer_local::PrivateKeySigner; use eth::backend::fork::ClientFork; use eyre::Result; use foundry_common::provider::{ProviderBuilder, RetryProvider}; -use foundry_evm::revm; use futures::{FutureExt, TryFutureExt}; use parking_lot::Mutex; +use revm::primitives::hardfork::SpecId; use server::try_spawn_ipc; use std::{ future::Future, @@ -48,13 +50,13 @@ pub use config::{ }; mod hardfork; -pub use hardfork::EthereumHardfork; - +pub use alloy_hardforks::EthereumHardfork; /// ethereum related implementations pub mod eth; /// Evm related abstractions mod evm; pub use evm::{inject_precompiles, PrecompileFactory}; + /// support for polling filters pub mod filter; /// commandline output @@ -198,6 +200,11 @@ pub async fn try_spawn(mut config: NodeConfig) -> Result<(EthApi, NodeHandle)> { let fee_history_cache = Arc::new(Mutex::new(Default::default())); let fee_history_service = FeeHistoryService::new( + match backend.spec_id() { + SpecId::OSAKA => BlobParams::osaka(), + SpecId::PRAGUE => BlobParams::prague(), + _ => BlobParams::cancun(), + }, backend.new_block_notifications(), Arc::clone(&fee_history_cache), StorageInfo::new(Arc::clone(&backend)), diff --git a/crates/anvil/src/pubsub.rs b/crates/anvil/src/pubsub.rs index b28cdba8ae84c..69f7896c72f78 100644 --- a/crates/anvil/src/pubsub.rs +++ b/crates/anvil/src/pubsub.rs @@ -2,6 +2,7 @@ use crate::{ eth::{backend::notifications::NewBlockNotifications, error::to_rpc_result}, StorageInfo, }; +use alloy_network::AnyRpcTransaction; use alloy_primitives::{TxHash, B256}; use alloy_rpc_types::{pubsub::SubscriptionResult, FilteredParams, Log, Transaction}; use anvil_core::eth::{block::Block, subscription::SubscriptionId, transaction::TypedReceipt}; @@ -13,6 +14,7 @@ use std::{ pin::Pin, task::{Context, Poll}, }; +use tokio::sync::mpsc::UnboundedReceiver; /// Listens for new blocks and matching logs emitted in that block #[derive(Debug)] @@ -86,6 +88,7 @@ pub enum EthSubscription { Logs(Box), Header(NewBlockNotifications, StorageInfo, SubscriptionId), PendingTransactions(Receiver, SubscriptionId), + FullPendingTransactions(UnboundedReceiver, SubscriptionId), } impl EthSubscription { @@ -120,6 +123,13 @@ impl EthSubscription { }); Poll::Ready(res) } + Self::FullPendingTransactions(tx, id) => { + let res = ready!(tx.poll_recv(cx)).map(to_rpc_result).map(|result| { + let params = EthSubscriptionParams { subscription: id.clone(), result }; + EthSubscriptionResponse::new(params) + }); + Poll::Ready(res) + } } } } diff --git a/crates/anvil/src/server/handler.rs b/crates/anvil/src/server/handler.rs index 79adb87df7bcb..3c474e9ee4b23 100644 --- a/crates/anvil/src/server/handler.rs +++ b/crates/anvil/src/server/handler.rs @@ -58,20 +58,22 @@ impl PubSubEthRpcHandler { let canceled = cx.remove_subscription(&id).is_some(); ResponseResult::Success(canceled.into()) } - EthPubSub::EthSubscribe(kind, params) => { - let filter = match *params { + EthPubSub::EthSubscribe(kind, raw_params) => { + let filter = match &*raw_params { Params::None => None, - Params::Logs(filter) => Some(*filter), - Params::Bool(_) => { - return ResponseResult::Error(RpcError::invalid_params( - "Expected params for logs subscription", - )) - } + Params::Logs(filter) => Some(filter.clone()), + Params::Bool(_) => None, }; - let params = FilteredParams::new(filter); + let params = FilteredParams::new(filter.map(|b| *b)); let subscription = match kind { SubscriptionKind::Logs => { + if raw_params.is_bool() { + return ResponseResult::Error(RpcError::invalid_params( + "Expected params for logs subscription", + )) + } + trace!(target: "rpc::ws", "received logs subscription {:?}", params); let blocks = self.api.new_block_notifications(); let storage = self.api.storage_info(); @@ -91,10 +93,23 @@ impl PubSubEthRpcHandler { } SubscriptionKind::NewPendingTransactions => { trace!(target: "rpc::ws", "received pending transactions subscription"); - EthSubscription::PendingTransactions( - self.api.new_ready_transactions(), - id.clone(), - ) + match *raw_params { + Params::Bool(true) => EthSubscription::FullPendingTransactions( + self.api.full_pending_transactions(), + id.clone(), + ), + Params::Bool(false) | Params::None => { + EthSubscription::PendingTransactions( + self.api.new_ready_transactions(), + id.clone(), + ) + } + _ => { + return ResponseResult::Error(RpcError::invalid_params( + "Expected boolean parameter for newPendingTransactions", + )) + } + } } SubscriptionKind::Syncing => { return RpcError::internal_error_with("Not implemented").into() diff --git a/crates/anvil/test-data/state-dump-legacy-stress.json b/crates/anvil/test-data/state-dump-legacy-stress.json index f6605d5add4e3..b7a18c94cad40 100644 --- a/crates/anvil/test-data/state-dump-legacy-stress.json +++ b/crates/anvil/test-data/state-dump-legacy-stress.json @@ -1 +1 @@ -{"block":{"number":"0x5","coinbase":"0x0000000000000000000000000000000000000000","timestamp":"0x66b200cb","gas_limit":"0x1c9c380","basefee":"0x12e09c7a","difficulty":"0x0","prevrandao":"0xe7ef87fc7c2090741a6749a087e4ca8092cb4d07136008799e4ebeac3b69e34a","blob_excess_gas_and_price":{"excess_blob_gas":0,"blob_gasprice":1}},"accounts":{"0x0000000000000000000000000000000000000000":{"nonce":0,"balance":"0x1088aa62285a00","code":"0x","storage":{}},"0x108f53faf774d7c4c56f5bce9ca6e605ce8aeadd":{"nonce":1,"balance":"0x0","code":"0x6080604052600080357fffffffff0000000000000000000000000000000000000000000000000000000016905060008160e01c610251565b60006379ba509782101561015e5781631627540c811461009857632a952b2d81146100b457633659cfe681146100d0576350c946fe81146100ec576353a47bb781146101085763625ca21c81146101245763718fe928811461014057610158565b7347d08dad17ccb558b3ea74b1a0e73a9cc804a9dc9150610158565b738138ef7cf908021d117e542120b7a390650161079150610158565b7347d08dad17ccb558b3ea74b1a0e73a9cc804a9dc9150610158565b738138ef7cf908021d117e542120b7a390650161079150610158565b7347d08dad17ccb558b3ea74b1a0e73a9cc804a9dc9150610158565b738138ef7cf908021d117e542120b7a390650161079150610158565b7347d08dad17ccb558b3ea74b1a0e73a9cc804a9dc91505b5061024c565b816379ba509781146101a657638da5cb5b81146101c25763aaf10f4281146101de5763c7f62cda81146101fa5763daa250be81146102165763deba1b9881146102325761024a565b7347d08dad17ccb558b3ea74b1a0e73a9cc804a9dc915061024a565b7347d08dad17ccb558b3ea74b1a0e73a9cc804a9dc915061024a565b7347d08dad17ccb558b3ea74b1a0e73a9cc804a9dc915061024a565b7347d08dad17ccb558b3ea74b1a0e73a9cc804a9dc915061024a565b738138ef7cf908021d117e542120b7a39065016107915061024a565b738138ef7cf908021d117e542120b7a3906501610791505b505b919050565b61025a81610037565b915050600073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff16036102ce57816040517fc2a825f50000000000000000000000000000000000000000000000000000000081526004016102c5919061032f565b60405180910390fd5b3660008037600080366000845af43d6000803e80600081146102ef573d6000f35b3d6000fd5b60007fffffffff0000000000000000000000000000000000000000000000000000000082169050919050565b610329816102f4565b82525050565b60006020820190506103446000830184610320565b9291505056fea264697066735822122017a4b7fdaaab3897a7b47abaed8d2ee92d558883d3bb2a8454f9601b2ab2c3db64736f6c63430008150033","storage":{}},"0x14dc79964da2c08b23698b3d3cc7ca32193d9955":{"nonce":0,"balance":"0x21e19e0c9bab2400000","code":"0x","storage":{}},"0x15d34aaf54267db7d7c367839aaf71a00a2c6a65":{"nonce":0,"balance":"0x21e19e0c9bab2400000","code":"0x","storage":{}},"0x19ba1fac55eea44d12a01372a8eb0c2ebbf9ca21":{"nonce":1,"balance":"0x21e19df7c2963f0ac6b","code":"0x","storage":{}},"0x19c6ab860dbe2bc433574193a4409770a8748bf6":{"nonce":1,"balance":"0x21e19df8da6b7bdc410","code":"0x","storage":{}},"0x23618e81e3f5cdf7f54c3d65f7fbc0abf5b21e8f":{"nonce":0,"balance":"0x21e19e0c9bab2400000","code":"0x","storage":{}},"0x3c44cdddb6a900fa2b585dd299e03d12fa4293bc":{"nonce":0,"balance":"0x21e19e0c9bab2400000","code":"0x","storage":{}},"0x40567ec443c1d1872af5155755ac3803cc3fe61e":{"nonce":1,"balance":"0x21e19da82562f921b40","code":"0x","storage":{}},"0x47d08dad17ccb558b3ea74b1a0e73a9cc804a9dc":{"nonce":1,"balance":"0x0","code":"0x608060405234801561001057600080fd5b50600436106100885760003560e01c806379ba50971161005b57806379ba5097146100ed5780638da5cb5b146100f7578063aaf10f4214610115578063c7f62cda1461013357610088565b80631627540c1461008d5780633659cfe6146100a957806353a47bb7146100c5578063718fe928146100e3575b600080fd5b6100a760048036038101906100a29190610d25565b61014f565b005b6100c360048036038101906100be9190610d25565b6102d0565b005b6100cd6102e4565b6040516100da9190610d61565b60405180910390f35b6100eb610317565b005b6100f56103fe565b005b6100ff61058b565b60405161010c9190610d61565b60405180910390f35b61011d6105be565b60405161012a9190610d61565b60405180910390f35b61014d60048036038101906101489190610d25565b6105f1565b005b61015761084c565b600061016161081b565b9050600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff16036101c9576040517fd92e233d00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b8060010160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1603610252576040517fa88ee57700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b818160010160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055507f906a1c6bd7e3091ea86693dd029a831c19049ce77f1dce2ce0bab1cacbabce22826040516102c49190610d61565b60405180910390a15050565b6102d861084c565b6102e1816108c5565b50565b60006102ee61081b565b60010160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16905090565b600061032161081b565b90503373ffffffffffffffffffffffffffffffffffffffff168160010160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16146103b757336040517fa0e5a0d70000000000000000000000000000000000000000000000000000000081526004016103ae9190610d61565b60405180910390fd5b60008160010160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555050565b600061040861081b565b905060008160010160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1690508073ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16146104a357336040517fa0e5a0d700000000000000000000000000000000000000000000000000000000815260040161049a9190610d61565b60405180910390fd5b7fb532073b38c83145e3e5135377a08bf9aab55bc0fd7c1179cd4fb995d2a5159c8260000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16826040516104f8929190610d7c565b60405180910390a1808260000160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555060008260010160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055505050565b600061059561081b565b60000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16905090565b60006105c8610b05565b60000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16905090565b60006105fb610b05565b905060018160000160146101000a81548160ff02191690831515021790555060008160000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff169050828260000160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555060008373ffffffffffffffffffffffffffffffffffffffff163073ffffffffffffffffffffffffffffffffffffffff16633659cfe6846040516024016106cc9190610d61565b604051602081830303815290604052915060e01b6020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff838183161783525050505060405161071b9190610e16565b600060405180830381855af49150503d8060008114610756576040519150601f19603f3d011682016040523d82523d6000602084013e61075b565b606091505b505090508015806107c357508173ffffffffffffffffffffffffffffffffffffffff16610786610b05565b60000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1614155b156107fa576040517fa1cfa5a800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60008360000160146101000a81548160ff0219169083151502179055600080fd5b60008060405160200161082d90610eb0565b6040516020818303038152906040528051906020012090508091505090565b610854610b36565b73ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16146108c357336040517f8e4a23d60000000000000000000000000000000000000000000000000000000081526004016108ba9190610d61565b60405180910390fd5b565b600073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff160361092b576040517fd92e233d00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b61093481610b69565b61097557806040517f8a8b41ec00000000000000000000000000000000000000000000000000000000815260040161096c9190610d61565b60405180910390fd5b600061097f610b05565b90508060000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1603610a0a576040517fa88ee57700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b8060000160149054906101000a900460ff16158015610a2e5750610a2d82610b7c565b5b15610a7057816040517f15504301000000000000000000000000000000000000000000000000000000008152600401610a679190610d61565b60405180910390fd5b818160000160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055503073ffffffffffffffffffffffffffffffffffffffff167f5d611f318680d00598bb735d61bacf0c514c6b50e1e5ad30040a4df2b12791c783604051610af99190610d61565b60405180910390a25050565b600080604051602001610b1790610f42565b6040516020818303038152906040528051906020012090508091505090565b6000610b4061081b565b60000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16905090565b600080823b905060008111915050919050565b60008060003073ffffffffffffffffffffffffffffffffffffffff163073ffffffffffffffffffffffffffffffffffffffff1663c7f62cda86604051602401610bc59190610d61565b604051602081830303815290604052915060e01b6020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff8381831617835250505050604051610c149190610e16565b600060405180830381855af49150503d8060008114610c4f576040519150601f19603f3d011682016040523d82523d6000602084013e610c54565b606091505b509150915081158015610cb9575063a1cfa5a860e01b604051602001610c7a9190610faf565b6040516020818303038152906040528051906020012081604051602001610ca19190610e16565b60405160208183030381529060405280519060200120145b92505050919050565b600080fd5b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b6000610cf282610cc7565b9050919050565b610d0281610ce7565b8114610d0d57600080fd5b50565b600081359050610d1f81610cf9565b92915050565b600060208284031215610d3b57610d3a610cc2565b5b6000610d4984828501610d10565b91505092915050565b610d5b81610ce7565b82525050565b6000602082019050610d766000830184610d52565b92915050565b6000604082019050610d916000830185610d52565b610d9e6020830184610d52565b9392505050565b600081519050919050565b600081905092915050565b60005b83811015610dd9578082015181840152602081019050610dbe565b60008484015250505050565b6000610df082610da5565b610dfa8185610db0565b9350610e0a818560208601610dbb565b80840191505092915050565b6000610e228284610de5565b915081905092915050565b600082825260208201905092915050565b7f696f2e73796e7468657469782e636f72652d636f6e7472616374732e4f776e6160008201527f626c650000000000000000000000000000000000000000000000000000000000602082015250565b6000610e9a602383610e2d565b9150610ea582610e3e565b604082019050919050565b60006020820190508181036000830152610ec981610e8d565b9050919050565b7f696f2e73796e7468657469782e636f72652d636f6e7472616374732e50726f7860008201527f7900000000000000000000000000000000000000000000000000000000000000602082015250565b6000610f2c602183610e2d565b9150610f3782610ed0565b604082019050919050565b60006020820190508181036000830152610f5b81610f1f565b9050919050565b60007fffffffff0000000000000000000000000000000000000000000000000000000082169050919050565b6000819050919050565b610fa9610fa482610f62565b610f8e565b82525050565b6000610fbb8284610f98565b6004820191508190509291505056fea264697066735822122023a7c33d7b91dce35ffbcf8837693364ab22a3905d0fc00016833e5fac45ca2f64736f6c63430008110033","storage":{"0x5c7865864a2a990d80b5bb5c40e7b73a029960dc711fbb56120dfab976e92ea3":"0x0"}},"0x4e59b44847b379578588920ca78fbf26c0b4956c":{"nonce":2,"balance":"0x0","code":"0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe03601600081602082378035828234f58015156039578182fd5b8082525050506014600cf3","storage":{}},"0x70997970c51812dc3a010c7d01b50e0d17dc79c8":{"nonce":0,"balance":"0x21e19e0c9bab2400000","code":"0x","storage":{}},"0x8138ef7cf908021d117e542120b7a39065016107":{"nonce":1,"balance":"0x0","code":"0x608060405234801561001057600080fd5b50600436106100575760003560e01c80632a952b2d1461005c57806350c946fe14610085578063625ca21c146100a5578063daa250be146100c6578063deba1b98146100d9575b600080fd5b61006f61006a366004613a63565b6100ec565b60405161007c9190613a7c565b60405180910390f35b610098610093366004613a63565b61011c565b60405161007c9190613b21565b6100b86100b3366004613c92565b610276565b60405190815260200161007c565b61006f6100d4366004613d5f565b6102bb565b6100b86100e7366004613c92565b6102d6565b6100f46139e4565b6040805160008082526020820190815281830190925261011691849190610310565b92915050565b6101416040805160608101909152806000815260200160608152602001606081525090565b61014a82610ab6565b60408051606081019091528154909190829060ff16600981111561017057610170613aa7565b600981111561018157610181613aa7565b815260200160018201805461019590613dc2565b80601f01602080910402602001604051908101604052809291908181526020018280546101c190613dc2565b801561020e5780601f106101e35761010080835404028352916020019161020e565b820191906000526020600020905b8154815290600101906020018083116101f157829003601f168201915b505050505081526020016002820180548060200260200160405190810160405280929190818152602001828054801561026657602002820191906000526020600020905b815481526020019060010190808311610252575b5050505050815250509050919050565b600080604051806060016040528086600981111561029657610296613aa7565b81526020018581526020018481525090506102b081610ac1565b9150505b9392505050565b6102c36139e4565b6102ce848484610310565b949350505050565b60008060405180606001604052808660098111156102f6576102f6613aa7565b81526020018581526020018481525090506102b081610acc565b6103186139e4565b81518351146103a05760408051634bab873760e11b81526004810191909152600d60448201526c72756e74696d6556616c75657360981b606482015260806024820152602260848201527f6d7573742062652073616d65206c656e6774682061732072756e74696d654b6560a482015261797360f01b60c482015260e4015b60405180910390fd5b60006103ab85610c26565b805490915060ff1660018160098111156103c7576103c7613aa7565b036104755761046c6103da838787610c84565b8360010180546103e990613dc2565b80601f016020809104026020016040519081016040528092919081815260200182805461041590613dc2565b80156104625780601f1061043757610100808354040283529160200191610462565b820191906000526020600020905b81548152906001019060200180831161044557829003601f168201915b5050505050610d46565b925050506102b4565b600281600981111561048957610489613aa7565b036105305761046c61049c838787610c84565b8360010180546104ab90613dc2565b80601f01602080910402602001604051908101604052809291908181526020018280546104d790613dc2565b80156105245780601f106104f957610100808354040283529160200191610524565b820191906000526020600020905b81548152906001019060200180831161050757829003601f168201915b50505050508787610ebb565b600381600981111561054457610544613aa7565b036105de5761046c82600101805461055b90613dc2565b80601f016020809104026020016040519081016040528092919081815260200182805461058790613dc2565b80156105d45780601f106105a9576101008083540402835291602001916105d4565b820191906000526020600020905b8154815290600101906020018083116105b757829003601f168201915b5050505050610f59565b60048160098111156105f2576105f2613aa7565b0361068c5761046c82600101805461060990613dc2565b80601f016020809104026020016040519081016040528092919081815260200182805461063590613dc2565b80156106825780601f1061065757610100808354040283529160200191610682565b820191906000526020600020905b81548152906001019060200180831161066557829003601f168201915b5050505050611087565b60058160098111156106a0576106a0613aa7565b0361073a5761046c8260010180546106b790613dc2565b80601f01602080910402602001604051908101604052809291908181526020018280546106e390613dc2565b80156107305780601f1061070557610100808354040283529160200191610730565b820191906000526020600020905b81548152906001019060200180831161071357829003601f168201915b505050505061131e565b600981600981111561074e5761074e613aa7565b036107ea5761046c82600101805461076590613dc2565b80601f016020809104026020016040519081016040528092919081815260200182805461079190613dc2565b80156107de5780601f106107b3576101008083540402835291602001916107de565b820191906000526020600020905b8154815290600101906020018083116107c157829003601f168201915b505050505086866114b5565b60068160098111156107fe576107fe613aa7565b036108a35761046c610811838787610c84565b83600101805461082090613dc2565b80601f016020809104026020016040519081016040528092919081815260200182805461084c90613dc2565b80156108995780601f1061086e57610100808354040283529160200191610899565b820191906000526020600020905b81548152906001019060200180831161087c57829003601f168201915b50505050506115c7565b60078160098111156108b7576108b7613aa7565b036109ec576040805160608101909152825461046c91908490829060ff1660098111156108e6576108e6613aa7565b60098111156108f7576108f7613aa7565b815260200160018201805461090b90613dc2565b80601f016020809104026020016040519081016040528092919081815260200182805461093790613dc2565b80156109845780601f1061095957610100808354040283529160200191610984565b820191906000526020600020905b81548152906001019060200180831161096757829003601f168201915b50505050508152602001600282018054806020026020016040519081016040528092919081815260200182805480156109dc57602002820191906000526020600020905b8154815260200190600101908083116109c8575b5050505050815250508686611728565b6008816009811115610a0057610a00613aa7565b03610a9a5761046c826001018054610a1790613dc2565b80601f0160208091040260200160405190810160405280929190818152602001828054610a4390613dc2565b8015610a905780601f10610a6557610100808354040283529160200191610a90565b820191906000526020600020905b815481529060010190602001808311610a7357829003601f168201915b50505050506118a5565b6040516323a9bbc960e01b815260048101879052602401610397565b600061011682610c26565b6000610116826118ea565b6000610ad782610ac1565b9050610ae28161192a565b15610b35577fcb64985827770858ec421ad26da7e558c757541643036ce44d6b4eb9e8e5dc5e81836000015184602001518560400151604051610b289493929190613e32565b60405180910390a1919050565b610b3e82611a8c565b610b5d578160405163382bbbc960e11b81526004016103979190613b21565b60005b826040015151811015610bd957610b9383604001518281518110610b8657610b86613e6a565b602002602001015161192a565b610bd15782604001518181518110610bad57610bad613e6a565b6020026020010151604051632f19f96160e11b815260040161039791815260200190565b600101610b60565b50610be382611c31565b8351602085015160408087015190519395507fcb64985827770858ec421ad26da7e558c757541643036ce44d6b4eb9e8e5dc5e9450610b28938693929190613e32565b604080516020808201839052606082018190527f696f2e73796e7468657469782e6f7261636c652d6d616e616765722e4e6f6465608080840191909152828401949094528251808303909401845260a0909101909152815191012090565b600283015460609067ffffffffffffffff811115610ca457610ca4613b9a565b604051908082528060200260200182016040528015610cdd57816020015b610cca6139e4565b815260200190600190039081610cc25790505b50905060005b6002850154811015610d3e57610d19856002018281548110610d0757610d07613e6a565b90600052602060002001548585610310565b828281518110610d2b57610d2b613e6a565b6020908102919091010152600101610ce3565b509392505050565b610d4e6139e4565b600082806020019051810190610d649190613e80565b90506000816008811115610d7a57610d7a613aa7565b03610d9057610d8884611ca5565b915050610116565b6001816008811115610da457610da4613aa7565b03610db257610d8884611d0d565b6002816008811115610dc657610dc6613aa7565b03610dd457610d8884611d90565b6003816008811115610de857610de8613aa7565b03610df657610d8884611e13565b6004816008811115610e0a57610e0a613aa7565b03610e1857610d8884611ec9565b6005816008811115610e2c57610e2c613aa7565b03610e3a57610d8884612009565b6006816008811115610e4e57610e4e613aa7565b03610e5c57610d88846120e4565b6007816008811115610e7057610e70613aa7565b03610e7e57610d888461220c565b6008816008811115610e9257610e92613aa7565b03610ea057610d88846122ce565b80604051631be413d360e11b81526004016103979190613ea1565b610ec36139e4565b600084806020019051810190610ed99190613ed3565b604051631ecba7c360e31b81529091506001600160a01b0382169063f65d3e1890610f0e908990899089908990600401613ef0565b608060405180830381865afa158015610f2b573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610f4f9190613f91565b9695505050505050565b610f616139e4565b600080600084806020019051810190610f7a9190613fe8565b92509250925060008390506000806000836001600160a01b031663feaf968c6040518163ffffffff1660e01b815260040160a060405180830381865afa158015610fc8573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610fec9190614041565b509350509250925060008660001461100f5761100a8585858a6123c7565b611011565b825b905060128660ff161161103b5761103661102f60ff881660126140a7565b82906124c2565b611053565b61105361104c601260ff89166140a7565b82906124dc565b9050604051806080016040528082815260200183815260200160008152602001600081525098505050505050505050919050565b61108f6139e4565b600080600080600080878060200190518101906110ac91906140ba565b604080516002808252606082018352979d50959b50939950919750955093506000929060208301908036833701905050905081816000815181106110f2576110f2613e6a565b602002602001019063ffffffff16908163ffffffff168152505060008160018151811061112157611121613e6a565b63ffffffff9092166020928302919091019091015260405163883bdbfd60e01b81526000906001600160a01b0385169063883bdbfd90611165908590600401614143565b600060405180830381865afa158015611182573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f191682016040526111aa91908101906141f5565b5090506000816000815181106111c2576111c2613e6a565b6020026020010151826001815181106111dd576111dd613e6a565b60200260200101516111ef91906142c1565b9050600061121761120563ffffffff87166124f6565b61120f9084614304565b60060b61252d565b905060008260060b12801561124c575061123b63ffffffff8616612569565b612569565b8260060b6112499190614342565b15155b1561125f578061125b81614356565b9150505b600061126d6012600a61445d565b9050600061128061123684848f8f612593565b905060006112908a60ff16612569565b61129c8c60ff16612569565b6112a6919061446c565b905060008082136112d1576112cc6112c56112c084614493565b612686565b84906124dc565b6112e4565b6112e46112dd83612686565b84906124c2565b905060405180608001604052808281526020014281526020016000815260200160008152509e505050505050505050505050505050919050565b6113266139e4565b60008060008480602001905181019061133f91906144bf565b91945092509050826000826113bc576040516396834ad360e01b8152600481018590526001600160a01b038316906396834ad390602401608060405180830381865afa158015611393573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906113b791906144f5565b611425565b604051639474f45b60e01b8152600481018590526001600160a01b03831690639474f45b90602401608060405180830381865afa158015611401573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061142591906144f5565b90506000816040015160030b601261143d919061456f565b90506000808213611467576114626114576112c084614493565b845160070b906124dc565b61147e565b61147e61147383612686565b845160070b906124c2565b9050604051806080016040528082815260200184606001518152602001600081526020016000815250975050505050505050919050565b6114bd6139e4565b6000806000868060200190518101906114d69190614597565b92509250925060005b8651811015611545578681815181106114fa576114fa613e6a565b6020026020010151717374616c656e657373546f6c6572616e636560701b0361153d5785818151811061152f5761152f613e6a565b602002602001015160001c91505b6001016114df565b5060408051600180825281830190925260009160208083019080368337019050509050828160008151811061157c5761157c613e6a565b602002602001018181525050836001838360405160200161159f939291906145ce565b60408051601f198184030181529082905263cf2cabdf60e01b82526103979291600401614603565b6115cf6139e4565b6000828060200190518101906115e59190614627565b90506000846000815181106115fc576115fc613e6a565b602002602001015160000151905060008560018151811061161f5761161f613e6a565b6020026020010151600001519050808214611702576000611653601261164d611648858761446c565b6126a9565b906124c2565b905082158061167b5750611666836126a9565b6116709082614640565b61167985612569565b125b15611700576002875111156116b0578660028151811061169d5761169d613e6a565b6020026020010151945050505050610116565b826000036116d15760405163014cc07160e01b815260040160405180910390fd5b6116da836126a9565b6116e49082614640565b60405163dcac091960e01b815260040161039791815260200190565b505b8560008151811061171557611715613e6a565b6020026020010151935050505092915050565b6117306139e4565b6000846020015180602001905181019061174a9190614627565b905060005b84518110156117bc5784818151811061176a5761176a613e6a565b6020026020010151717374616c656e657373546f6c6572616e636560701b036117b4576117ad8482815181106117a2576117a2613e6a565b602002602001015190565b91506117bc565b60010161174f565b50600085604001516000815181106117d6576117d6613e6a565b6020026020010151905060006117ed828787610310565b60208101519091506117ff84426140a7565b1161180e5792506102b4915050565b86604001515160010361187157866040015160008151811061183257611832613e6a565b602002602001015181600001518260200151604051631808066560e21b8152600401610397939291909283526020830191909152604082015260600190565b61189a876040015160018151811061188b5761188b613e6a565b60200260200101518787610310565b979650505050505050565b6118ad6139e4565b6040518060800160405280838060200190518101906118cc9190614627565b81526020014281526020016000815260200160008152509050919050565b600081600001518260200151836040015160405160200161190d9392919061466e565b604051602081830303815290604052805190602001209050919050565b60008061193683610c26565b60408051606081019091528154909190829060ff16600981111561195c5761195c613aa7565b600981111561196d5761196d613aa7565b815260200160018201805461198190613dc2565b80601f01602080910402602001604051908101604052809291908181526020018280546119ad90613dc2565b80156119fa5780601f106119cf576101008083540402835291602001916119fa565b820191906000526020600020905b8154815290600101906020018083116119dd57829003601f168201915b5050505050815260200160028201805480602002602001604051908101604052809291908181526020018280548015611a5257602002820191906000526020600020905b815481526020019060010190808311611a3e575b505050505081525050905060006009811115611a7057611a70613aa7565b81516009811115611a8357611a83613aa7565b14159392505050565b6000600182516009811115611aa357611aa3613aa7565b1480611ac15750600682516009811115611abf57611abf613aa7565b145b80611ade5750600782516009811115611adc57611adc613aa7565b145b15611aee57611aec826126c1565b505b600182516009811115611b0357611b03613aa7565b03611b11576101168261284a565b600282516009811115611b2657611b26613aa7565b03611b3457610116826128a5565b600382516009811115611b4957611b49613aa7565b03611b575761011682612973565b600482516009811115611b6c57611b6c613aa7565b03611b7a5761011682612aae565b600582516009811115611b8f57611b8f613aa7565b03611b9d5761011682612e92565b600982516009811115611bb257611bb2613aa7565b03611bc05761011682612fcb565b600682516009811115611bd557611bd5613aa7565b03611be3576101168261300e565b600782516009811115611bf857611bf8613aa7565b03611c065761011682613052565b600882516009811115611c1b57611c1b613aa7565b03611c295761011682613078565b506000919050565b600080611c3d836118ea565b9050611c4881610c26565b8351815491935090839060ff19166001836009811115611c6a57611c6a613aa7565b021790555060208301516001830190611c8390826146ed565b5060408301518051611c9f916002850191602090910190613a0c565b50915091565b611cad6139e4565b60005b8251811015611d07578160200151838281518110611cd057611cd0613e6a565b6020026020010151602001511115611cff57828181518110611cf457611cf4613e6a565b602002602001015191505b600101611cb0565b50919050565b611d156139e4565b81600081518110611d2857611d28613e6a565b602002602001015190506000600190505b8251811015611d07578160000151838281518110611d5957611d59613e6a565b6020026020010151600001511215611d8857828181518110611d7d57611d7d613e6a565b602002602001015191505b600101611d39565b611d986139e4565b81600081518110611dab57611dab613e6a565b602002602001015190506000600190505b8251811015611d07578160000151838281518110611ddc57611ddc613e6a565b6020026020010151600001511315611e0b57828181518110611e0057611e00613e6a565b602002602001015191505b600101611dbc565b611e1b6139e4565b60005b8251811015611e9557828181518110611e3957611e39613e6a565b60200260200101516000015182600001818151611e56919061456f565b9052508251839082908110611e6d57611e6d613e6a565b60200260200101516020015182602001818151611e8a91906147ad565b905250600101611e1e565b50611ea08251612569565b8151611eac9190614640565b815281516020820151611ebf91906147c0565b6020820152919050565b611ed16139e4565b611eed826000611ee86001865161123691906140a7565b6130a4565b60028251611efb91906147d4565b600003611fd65760408051600280825260608201909252600091816020015b611f226139e4565b815260200190600190039081611f1a57905050905082600160028551611f4891906147c0565b611f5291906140a7565b81518110611f6257611f62613e6a565b602002602001015181600081518110611f7d57611f7d613e6a565b60200260200101819052508260028451611f9791906147c0565b81518110611fa757611fa7613e6a565b602002602001015181600181518110611fc257611fc2613e6a565b60200260200101819052506102b481611e13565b8160028351611fe591906147c0565b81518110611ff557611ff5613e6a565b60200260200101519050919050565b919050565b6120116139e4565b8160008151811061202457612024613e6a565b60209081029190910101515181528151829060009061204557612045613e6a565b6020908102919091018101518101519082015260015b82518110156120d25782818151811061207657612076613e6a565b6020026020010151600001518260000181815161209391906147e8565b90525082518390829081106120aa576120aa613e6a565b602002602001015160200151826020018181516120c791906147ad565b90525060010161205b565b5081518160200151611ebf91906147c0565b6120ec6139e4565b816000815181106120ff576120ff613e6a565b60209081029190910101515181528151829060009061212057612120613e6a565b6020908102919091018101518101519082015260015b82518110156120d25782818151811061215157612151613e6a565b60200260200101516000015160000361219e5782818151811061217657612176613e6a565b6020026020010151600001516040516338ee04a760e01b815260040161039791815260200190565b8281815181106121b0576121b0613e6a565b602002602001015160000151826000018181516121cd9190614640565b90525082518390829081106121e4576121e4613e6a565b6020026020010151602001518260200181815161220191906147ad565b905250600101612136565b6122146139e4565b8160008151811061222757612227613e6a565b60209081029190910101515181528151829060009061224857612248613e6a565b6020908102919091018101518101519082015260015b82518110156120d25761229083828151811061227c5761227c613e6a565b602090810291909101015151835190613264565b825282518390829081106122a6576122a6613e6a565b602002602001015160200151826020018181516122c391906147ad565b90525060010161225e565b6122d66139e4565b816000815181106122e9576122e9613e6a565b60209081029190910101515181528151829060009061230a5761230a613e6a565b6020908102919091018101518101519082015260015b82518110156120d25782818151811061233b5761233b613e6a565b6020026020010151600001516000036123605782818151811061217657612176613e6a565b61238983828151811061237557612375613e6a565b602090810291909101015151835190613283565b8252825183908290811061239f5761239f613e6a565b602002602001015160200151826020018181516123bc91906147ad565b905250600101612320565b6000826001826123d785426140a7565b90505b69ffffffffffffffffffff8716156124a3576001600160a01b038816639a6fc8f561240489614818565b6040516001600160e01b031960e084901b16815269ffffffffffffffffffff8216600482015290995060240160a060405180830381865afa925050508015612469575060408051601f3d908101601f1916820190925261246691810190614041565b60015b156124a357858210156124805750505050506124a3565b61248a848961456f565b97508661249681614834565b97505050505050506123da565b6124ac82612569565b6124b69084614640565b98975050505050505050565b60006124d261123683600a61484d565b6102b490846147e8565b60006124ec61123683600a61484d565b6102b49084614640565b6000667fffffffffffff66ffffffffffffff83161115612529576040516329d2678160e21b815260040160405180910390fd5b5090565b6000627fffff19600683900b128061254b5750627fffff600683900b135b1561252957604051630d962f7960e21b815260040160405180910390fd5b60006001600160ff1b038211156125295760405163677c430560e11b815260040160405180910390fd5b60008061259f86613298565b90506fffffffffffffffffffffffffffffffff6001600160a01b0382161161261c5760006125d66001600160a01b03831680614859565b9050836001600160a01b0316856001600160a01b03161061260557612600600160c01b87836136cd565b612614565b6126148187600160c01b6136cd565b92505061267d565b600061263b6001600160a01b03831680680100000000000000006136cd565b9050836001600160a01b0316856001600160a01b03161061266a57612665600160801b87836136cd565b612679565b6126798187600160801b6136cd565b9250505b50949350505050565b6000808212156125295760405163029f024d60e31b815260040160405180910390fd5b600080821215612529576126bc82614493565b610116565b6000805b8260400151518110156128415760006126fa846040015183815181106126ed576126ed613e6a565b6020026020010151610ab6565b60408051606081019091528154909190829060ff16600981111561272057612720613aa7565b600981111561273157612731613aa7565b815260200160018201805461274590613dc2565b80601f016020809104026020016040519081016040528092919081815260200182805461277190613dc2565b80156127be5780601f10612793576101008083540402835291602001916127be565b820191906000526020600020905b8154815290600101906020018083116127a157829003601f168201915b505050505081526020016002820180548060200260200160405190810160405280929190818152602001828054801561281657602002820191906000526020600020905b815481526020019060010190808311612802575b505050505081525050905061282a81611a8c565b612838575060009392505050565b506001016126c5565b50600192915050565b60006002826040015151101561286257506000919050565b81602001515160201461287757506000919050565b600082602001518060200190518101906128919190614627565b905060088111156128415750600092915050565b6000602082602001515110156128bd57506000919050565b600082602001518060200190518101906128d79190613ed3565b90506128ea816306e7ea3960e21b6138e2565b6128f75750600092915050565b604051633b70a5bf60e21b81526001600160a01b0382169063edc296fc90612923908690600401613b21565b6020604051808303816000875af1158015612942573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906129669190614870565b6128415750600092915050565b6040810151516000901561298957506000919050565b81602001515160601461299e57506000919050565b60008083602001518060200190518101906129b99190613fe8565b92505091506000829050806001600160a01b031663feaf968c6040518163ffffffff1660e01b815260040160a060405180830381865afa158015612a01573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612a259190614041565b5050505050806001600160a01b031663313ce5676040518163ffffffff1660e01b8152600401602060405180830381865afa158015612a68573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612a8c919061488b565b60ff168260ff1614612aa357506000949350505050565b506001949350505050565b60408101515160009015612ac457506000919050565b81602001515160c014612ad957506000919050565b6000806000806000808760200151806020019051810190612afa91906140ba565b9550955095509550955095508360ff16866001600160a01b031663313ce5676040518163ffffffff1660e01b8152600401602060405180830381865afa158015612b48573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612b6c919061488b565b60ff1614612b8257506000979650505050505050565b8260ff16856001600160a01b031663313ce5676040518163ffffffff1660e01b8152600401602060405180830381865afa158015612bc4573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612be8919061488b565b60ff1614612bfe57506000979650505050505050565b6000826001600160a01b0316630dfe16816040518163ffffffff1660e01b8152600401602060405180830381865afa158015612c3e573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612c629190613ed3565b90506000836001600160a01b031663d21220a76040518163ffffffff1660e01b8152600401602060405180830381865afa158015612ca4573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612cc89190613ed3565b9050876001600160a01b0316826001600160a01b0316148015612cfc5750866001600160a01b0316816001600160a01b0316145b158015612d385750866001600160a01b0316826001600160a01b0316148015612d365750876001600160a01b0316816001600160a01b0316145b155b15612d4d575060009998505050505050505050565b60128660ff161180612d62575060128560ff16115b15612d77575060009998505050505050505050565b8263ffffffff16600003612d95575060009998505050505050505050565b6040805160028082526060820183526000926020830190803683370190505090508381600081518110612dca57612dca613e6a565b602002602001019063ffffffff16908163ffffffff1681525050600081600181518110612df957612df9613e6a565b63ffffffff9092166020928302919091019091015260405163883bdbfd60e01b81526001600160a01b0386169063883bdbfd90612e3a908490600401614143565b600060405180830381865afa158015612e57573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f19168201604052612e7f91908101906141f5565b5060019c9b505050505050505050505050565b60408101515160009015612ea857506000919050565b816020015151606014612ebd57506000919050565b60008060008460200151806020019051810190612eda91906144bf565b919450925090508281612f55576040516396834ad360e01b8152600481018490526001600160a01b038216906396834ad390602401608060405180830381865afa158015612f2c573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612f5091906144f5565b612fbe565b604051639474f45b60e01b8152600481018490526001600160a01b03821690639474f45b90602401608060405180830381865afa158015612f9a573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612fbe91906144f5565b5060019695505050505050565b60408101515160009015612fe157506000919050565b816020015151606014612ff657506000919050565b8160200151806020019051810190612aa39190614597565b60008160400151516002148061302957508160400151516003145b61303557506000919050565b81602001515160201461304a57506000919050565b506001919050565b600081604001515160011480613029575081604001515160021461303557506000919050565b6040810151516000901561308e57506000919050565b6020826020015151101561304a57506000919050565b81818082036130b4575050505050565b6000856130da60026130c6888861446c565b6130d09190614640565b6112c0908861456f565b815181106130ea576130ea613e6a565b60200260200101516000015190505b818313613236575b808661310c85612686565b8151811061311c5761311c613e6a565b60200260200101516000015112156131405782613138816148a6565b935050613101565b8561314a83612686565b8151811061315a5761315a613e6a565b60200260200101516000015181121561317f5781613177816148be565b925050613140565b818313613231578561319083612686565b815181106131a0576131a0613e6a565b6020026020010151866131b285612686565b815181106131c2576131c2613e6a565b6020026020010151876131d486612686565b815181106131e4576131e4613e6a565b60200260200101886131f586612686565b8151811061320557613205613e6a565b602002602001018290528290525050828061321f906148a6565b935050818061322d906148be565b9250505b6130f9565b81851215613249576132498686846130a4565b8383121561325c5761325c8684866130a4565b505050505050565b6000670de0b6b3a764000061327983856147e8565b6102b49190614640565b600081613279670de0b6b3a7640000856147e8565b60008060008360020b126132b8576132b3600284900b612686565b6132c8565b6132c86112c0600285900b614493565b90506132e36112c06132dd620d89e7196148db565b60020b90565b8111156133165760405162461bcd60e51b81526020600482015260016024820152601560fa1b6044820152606401610397565b60008160011660000361332d57600160801b61333f565b6ffffcb933bd6fad37aa2d162d1a5940015b70ffffffffffffffffffffffffffffffffff169050600282161561337e576080613379826ffff97272373d413259a46990580e213a614859565b901c90505b60048216156133a85760806133a3826ffff2e50f5f656932ef12357cf3c7fdcc614859565b901c90505b60088216156133d25760806133cd826fffe5caca7e10e4e61c3624eaa0941cd0614859565b901c90505b60108216156133fc5760806133f7826fffcb9843d60f6159c9db58835c926644614859565b901c90505b6020821615613426576080613421826fff973b41fa98c081472e6896dfb254c0614859565b901c90505b604082161561345057608061344b826fff2ea16466c96a3843ec78b326b52861614859565b901c90505b608082161561347a576080613475826ffe5dee046a99a2a811c461f1969c3053614859565b901c90505b6101008216156134a55760806134a0826ffcbe86c7900a88aedcffc83b479aa3a4614859565b901c90505b6102008216156134d05760806134cb826ff987a7253ac413176f2b074cf7815e54614859565b901c90505b6104008216156134fb5760806134f6826ff3392b0822b70005940c7a398e4b70f3614859565b901c90505b610800821615613526576080613521826fe7159475a2c29b7443b29c7fa6e889d9614859565b901c90505b61100082161561355157608061354c826fd097f3bdfd2022b8845ad8f792aa5825614859565b901c90505b61200082161561357c576080613577826fa9f746462d870fdf8a65dc1f90e061e5614859565b901c90505b6140008216156135a75760806135a2826f70d869a156d2a1b890bb3df62baf32f7614859565b901c90505b6180008216156135d25760806135cd826f31be135f97d08fd981231505542fcfa6614859565b901c90505b620100008216156135fe5760806135f9826f09aa508b5b7a84e1c677de54f3e99bc9614859565b901c90505b62020000821615613629576080613624826e5d6af8dedb81196699c329225ee604614859565b901c90505b6204000082161561365357608061364e826d2216e584f5fa1ea926041bedfe98614859565b901c90505b6208000082161561367b576080613676826b048a170391f7dc42444e8fa2614859565b901c90505b60008460020b131561369657613693816000196147c0565b90505b6102ce6136a8640100000000836147d4565b156136b45760016136b7565b60005b6136c89060ff16602084901c6147ad565b6139ba565b6000808060001985870985870292508281108382030391505080600003613749576000841161373e5760405162461bcd60e51b815260206004820152601960248201527f48616e646c65206e6f6e2d6f766572666c6f77206361736573000000000000006044820152606401610397565b5082900490506102b4565b8084116137985760405162461bcd60e51b815260206004820152601960248201527f70726576656e74732064656e6f6d696e61746f72203d3d2030000000000000006044820152606401610397565b60008486880980840393811190920391905060006137d06137b887612569565b6137c188612569565b6137ca90614493565b16612686565b9586900495938490049360008190030460010190506137ef8184614859565b909317926000613800876003614859565b600218905061380f8188614859565b61381a9060026140a7565b6138249082614859565b90506138308188614859565b61383b9060026140a7565b6138459082614859565b90506138518188614859565b61385c9060026140a7565b6138669082614859565b90506138728188614859565b61387d9060026140a7565b6138879082614859565b90506138938188614859565b61389e9060026140a7565b6138a89082614859565b90506138b48188614859565b6138bf9060026140a7565b6138c99082614859565b90506138d58186614859565b9998505050505050505050565b604080516001600160e01b0319831660248083019190915282518083039091018152604490910182526020810180516001600160e01b03166301ffc9a760e01b1790529051600091829182916001600160a01b0387169161394391906148fd565b6000604051808303816000865af19150503d8060008114613980576040519150601f19603f3d011682016040523d82523d6000602084013e613985565b606091505b50915091508161399a57600092505050610116565b80516000036139ae57600092505050610116565b60200151949350505050565b60006001600160a01b038211156125295760405163dccde8ed60e01b815260040160405180910390fd5b6040518060800160405280600081526020016000815260200160008152602001600081525090565b828054828255906000526020600020908101928215613a47579160200282015b82811115613a47578251825591602001919060010190613a2c565b506125299291505b808211156125295760008155600101613a4f565b600060208284031215613a7557600080fd5b5035919050565b8151815260208083015190820152604080830151908201526060808301519082015260808101610116565b634e487b7160e01b600052602160045260246000fd5b600a8110613acd57613acd613aa7565b9052565b60005b83811015613aec578181015183820152602001613ad4565b50506000910152565b60008151808452613b0d816020860160208601613ad1565b601f01601f19169290920160200192915050565b60006020808352613b358184018551613abd565b8084015160606040850152613b4d6080850182613af5565b6040860151858203601f19016060870152805180835290840192506000918401905b80831015613b8f5783518252928401926001929092019190840190613b6f565b509695505050505050565b634e487b7160e01b600052604160045260246000fd5b6040516080810167ffffffffffffffff81118282101715613bd357613bd3613b9a565b60405290565b604051601f8201601f1916810167ffffffffffffffff81118282101715613c0257613c02613b9a565b604052919050565b600067ffffffffffffffff821115613c2457613c24613b9a565b5060051b60200190565b600082601f830112613c3f57600080fd5b81356020613c54613c4f83613c0a565b613bd9565b8083825260208201915060208460051b870101935086841115613c7657600080fd5b602086015b84811015613b8f5780358352918301918301613c7b565b600080600060608486031215613ca757600080fd5b8335600a8110613cb657600080fd5b925060208481013567ffffffffffffffff80821115613cd457600080fd5b818701915087601f830112613ce857600080fd5b813581811115613cfa57613cfa613b9a565b613d0c601f8201601f19168501613bd9565b8181528985838601011115613d2057600080fd5b818585018683013760009181019094015291935060408601359180831115613d4757600080fd5b5050613d5586828701613c2e565b9150509250925092565b600080600060608486031215613d7457600080fd5b83359250602084013567ffffffffffffffff80821115613d9357600080fd5b613d9f87838801613c2e565b93506040860135915080821115613db557600080fd5b50613d5586828701613c2e565b600181811c90821680613dd657607f821691505b602082108103611d0757634e487b7160e01b600052602260045260246000fd5b60008151808452602080850194506020840160005b83811015613e2757815187529582019590820190600101613e0b565b509495945050505050565b848152613e426020820185613abd565b608060408201526000613e586080830185613af5565b828103606084015261189a8185613df6565b634e487b7160e01b600052603260045260246000fd5b600060208284031215613e9257600080fd5b8151600981106102b457600080fd5b6020810160098310613eb557613eb5613aa7565b91905290565b6001600160a01b0381168114613ed057600080fd5b50565b600060208284031215613ee557600080fd5b81516102b481613ebb565b608080825285518282018190526000919060209060a0850190828a01855b82811015613f5257613f42848351805182526020810151602083015260408101516040830152606081015160608301525050565b9285019290840190600101613f0e565b5050508481036020860152613f678189613af5565b925050508281036040840152613f7d8186613df6565b9050828103606084015261189a8185613df6565b600060808284031215613fa357600080fd5b613fab613bb0565b825181526020830151602082015260408301516040820152606083015160608201528091505092915050565b805160ff8116811461200457600080fd5b600080600060608486031215613ffd57600080fd5b835161400881613ebb565b6020850151909350915061401e60408501613fd7565b90509250925092565b805169ffffffffffffffffffff8116811461200457600080fd5b600080600080600060a0868803121561405957600080fd5b61406286614027565b945060208601519350604086015192506060860151915061408560808701614027565b90509295509295909350565b634e487b7160e01b600052601160045260246000fd5b8181038181111561011657610116614091565b60008060008060008060c087890312156140d357600080fd5b86516140de81613ebb565b60208801519096506140ef81613ebb565b94506140fd60408801613fd7565b935061410b60608801613fd7565b9250608087015161411b81613ebb565b60a088015190925063ffffffff8116811461413557600080fd5b809150509295509295509295565b6020808252825182820181905260009190848201906040850190845b8181101561418157835163ffffffff168352928401929184019160010161415f565b50909695505050505050565b600082601f83011261419e57600080fd5b815160206141ae613c4f83613c0a565b8083825260208201915060208460051b8701019350868411156141d057600080fd5b602086015b84811015613b8f5780516141e881613ebb565b83529183019183016141d5565b6000806040838503121561420857600080fd5b825167ffffffffffffffff8082111561422057600080fd5b818501915085601f83011261423457600080fd5b81516020614244613c4f83613c0a565b82815260059290921b8401810191818101908984111561426357600080fd5b948201945b838610156142915785518060060b81146142825760008081fd5b82529482019490820190614268565b918801519196509093505050808211156142aa57600080fd5b506142b78582860161418d565b9150509250929050565b600682810b9082900b03667fffffffffffff198112667fffffffffffff8213171561011657610116614091565b634e487b7160e01b600052601260045260246000fd5b60008160060b8360060b8061431b5761431b6142ee565b667fffffffffffff1982146000198214161561433957614339614091565b90059392505050565b600082614351576143516142ee565b500790565b60008160020b627fffff19810361436f5761436f614091565b6000190192915050565b600181815b808511156143b457816000190482111561439a5761439a614091565b808516156143a757918102915b93841c939080029061437e565b509250929050565b6000826143cb57506001610116565b816143d857506000610116565b81600181146143ee57600281146143f857614414565b6001915050610116565b60ff84111561440957614409614091565b50506001821b610116565b5060208310610133831016604e8410600b8410161715614437575081810a610116565b6144418383614379565b806000190482111561445557614455614091565b029392505050565b60006102b460ff8416836143bc565b818103600083128015838313168383128216171561448c5761448c614091565b5092915050565b6000600160ff1b82016144a8576144a8614091565b5060000390565b8051801515811461200457600080fd5b6000806000606084860312156144d457600080fd5b83516144df81613ebb565b6020850151909350915061401e604085016144af565b60006080828403121561450757600080fd5b61450f613bb0565b82518060070b811461452057600080fd5b8152602083015167ffffffffffffffff8116811461453d57600080fd5b60208201526040830151600381900b811461455757600080fd5b60408201526060928301519281019290925250919050565b808201828112600083128015821682158216171561458f5761458f614091565b505092915050565b6000806000606084860312156145ac57600080fd5b83516145b781613ebb565b602085015160409095015190969495509392505050565b60ff8416815267ffffffffffffffff831660208201526060604082015260006145fa6060830184613df6565b95945050505050565b6001600160a01b03831681526040602082018190526000906102ce90830184613af5565b60006020828403121561463957600080fd5b5051919050565b60008261464f5761464f6142ee565b600160ff1b82146000198414161561466957614669614091565b500590565b6146788185613abd565b60606020820152600061468e6060830185613af5565b8281036040840152610f4f8185613df6565b601f8211156146e8576000816000526020600020601f850160051c810160208610156146c95750805b601f850160051c820191505b8181101561325c578281556001016146d5565b505050565b815167ffffffffffffffff81111561470757614707613b9a565b61471b816147158454613dc2565b846146a0565b602080601f83116001811461475057600084156147385750858301515b600019600386901b1c1916600185901b17855561325c565b600085815260208120601f198616915b8281101561477f57888601518255948401946001909101908401614760565b508582101561479d5787850151600019600388901b60f8161c191681555b5050505050600190811b01905550565b8082018082111561011657610116614091565b6000826147cf576147cf6142ee565b500490565b6000826147e3576147e36142ee565b500690565b80820260008212600160ff1b8414161561480457614804614091565b818105831482151761011657610116614091565b600069ffffffffffffffffffff82168061436f5761436f614091565b60006001820161484657614846614091565b5060010190565b60006102b483836143bc565b808202811582820484141761011657610116614091565b60006020828403121561488257600080fd5b6102b4826144af565b60006020828403121561489d57600080fd5b6102b482613fd7565b60006001600160ff1b01820161484657614846614091565b6000600160ff1b82016148d3576148d3614091565b506000190190565b60008160020b627fffff1981036148f4576148f4614091565b60000392915050565b6000825161490f818460208701613ad1565b919091019291505056fea264697066735822122074f32fef384fdc296b0859f1c1f941c8e736c6cb972aa9e2b894956ebd6a80b364736f6c63430008160033","storage":{}},"0x83a0444b93927c3afcbe46e522280390f748e171":{"nonce":1,"balance":"0x0","code":"0x6080604052366100135761001161001d565b005b61001b61001d565b005b6000610027610093565b90503660008037600080366000845af43d6000803e806000811461004a573d6000f35b3d6000fd5b600080823b905060008111915050919050565b6000806040516020016100749061017a565b6040516020818303038152906040528051906020012090508091505090565b600061009d6100c6565b60000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16905090565b6000806040516020016100d89061020c565b6040516020818303038152906040528051906020012090508091505090565b600082825260208201905092915050565b7f696f2e73796e7468657469782e636f72652d636f6e7472616374732e4f776e6160008201527f626c650000000000000000000000000000000000000000000000000000000000602082015250565b60006101646023836100f7565b915061016f82610108565b604082019050919050565b6000602082019050818103600083015261019381610157565b9050919050565b7f696f2e73796e7468657469782e636f72652d636f6e7472616374732e50726f7860008201527f7900000000000000000000000000000000000000000000000000000000000000602082015250565b60006101f66021836100f7565b91506102018261019a565b604082019050919050565b60006020820190508181036000830152610225816101e9565b905091905056fea2646970667358221220800da1f73cebd5e4afa07496d9bca6b6c4f526bdd3f4014ec15c70fe3a1c441364736f6c63430008110033","storage":{"0x5a648c35a2f5512218b4683cf10e03f5b7c9dc7346e1bf77d304ae97f60f592b":"0x108f53faf774d7c4c56f5bce9ca6e605ce8aeadd","0x5c7865864a2a990d80b5bb5c40e7b73a029960dc711fbb56120dfab976e92ea3":"0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266"}},"0x90f79bf6eb2c4f870365e785982e1f101e93b906":{"nonce":0,"balance":"0x21e19e0c9bab2400000","code":"0x","storage":{}},"0x976ea74026e726554db657fa54763abd0c3a0aa9":{"nonce":0,"balance":"0x21e19e0c9bab2400000","code":"0x","storage":{}},"0x9965507d1a55bcc2695c58ba16fb37d819b0a4dc":{"nonce":0,"balance":"0x21e19e0c9bab2400000","code":"0x","storage":{}},"0xa0ee7a142d267c1f36714e4a8f75612f20a79720":{"nonce":0,"balance":"0x21e19e0c9bab2400000","code":"0x","storage":{}},"0xc67e2bd3108604cf0168c0e5ef9cd6d78b9bb14b":{"nonce":1,"balance":"0x21e19c6edb7e2445f20","code":"0x","storage":{}},"0xeb045d78d273107348b0300c01d29b7552d622ab":{"nonce":0,"balance":"0x21e19e0c9bab2400000","code":"0x","storage":{}},"0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266":{"nonce":1,"balance":"0x21e19e08b86820a43ea","code":"0x","storage":{}}},"best_block_number":"0x5","blocks":[{"header":{"parentHash":"0x08abe6e453727534d8dd708843a7522b7d500338bdfe2402ca105dcdb05eebe9","sha3Uncles":"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347","miner":"0x0000000000000000000000000000000000000000","stateRoot":"0xcd346446ed010523161f40a5f2b512def549bfb79e165b4354488738416481f2","transactionsRoot":"0xb3a4689832e0b599260ae70362ffcf224b60571b35ff8836904a3d81e2675d66","receiptsRoot":"0x2d13fdc120ab90536fed583939de7fb68b64926a306c1f629593ca9c2c93b198","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","difficulty":"0x0","number":"0x1","gasLimit":"0x1c9c380","gasUsed":"0x3ea90d","timestamp":"0x66b200ca","mixHash":"0x0000000000000000000000000000000000000000000000000000000000000000","nonce":"0x0000000000000000","baseFeePerGas":"0x2e0b6260","blobGasUsed":"0x0","excessBlobGas":"0x0","extraData":"0x"},"transactions":[{"EIP1559":{"chainId":"0x343a","nonce":"0x0","gas":"0x3ea90d","maxFeePerGas":"0x83215600","maxPriorityFeePerGas":"0x3b9aca00","value":"0x0","accessList":[],"input":"0x608060405234801561001057600080fd5b5061494f806100206000396000f3fe608060405234801561001057600080fd5b50600436106100575760003560e01c80632a952b2d1461005c57806350c946fe14610085578063625ca21c146100a5578063daa250be146100c6578063deba1b98146100d9575b600080fd5b61006f61006a366004613a63565b6100ec565b60405161007c9190613a7c565b60405180910390f35b610098610093366004613a63565b61011c565b60405161007c9190613b21565b6100b86100b3366004613c92565b610276565b60405190815260200161007c565b61006f6100d4366004613d5f565b6102bb565b6100b86100e7366004613c92565b6102d6565b6100f46139e4565b6040805160008082526020820190815281830190925261011691849190610310565b92915050565b6101416040805160608101909152806000815260200160608152602001606081525090565b61014a82610ab6565b60408051606081019091528154909190829060ff16600981111561017057610170613aa7565b600981111561018157610181613aa7565b815260200160018201805461019590613dc2565b80601f01602080910402602001604051908101604052809291908181526020018280546101c190613dc2565b801561020e5780601f106101e35761010080835404028352916020019161020e565b820191906000526020600020905b8154815290600101906020018083116101f157829003601f168201915b505050505081526020016002820180548060200260200160405190810160405280929190818152602001828054801561026657602002820191906000526020600020905b815481526020019060010190808311610252575b5050505050815250509050919050565b600080604051806060016040528086600981111561029657610296613aa7565b81526020018581526020018481525090506102b081610ac1565b9150505b9392505050565b6102c36139e4565b6102ce848484610310565b949350505050565b60008060405180606001604052808660098111156102f6576102f6613aa7565b81526020018581526020018481525090506102b081610acc565b6103186139e4565b81518351146103a05760408051634bab873760e11b81526004810191909152600d60448201526c72756e74696d6556616c75657360981b606482015260806024820152602260848201527f6d7573742062652073616d65206c656e6774682061732072756e74696d654b6560a482015261797360f01b60c482015260e4015b60405180910390fd5b60006103ab85610c26565b805490915060ff1660018160098111156103c7576103c7613aa7565b036104755761046c6103da838787610c84565b8360010180546103e990613dc2565b80601f016020809104026020016040519081016040528092919081815260200182805461041590613dc2565b80156104625780601f1061043757610100808354040283529160200191610462565b820191906000526020600020905b81548152906001019060200180831161044557829003601f168201915b5050505050610d46565b925050506102b4565b600281600981111561048957610489613aa7565b036105305761046c61049c838787610c84565b8360010180546104ab90613dc2565b80601f01602080910402602001604051908101604052809291908181526020018280546104d790613dc2565b80156105245780601f106104f957610100808354040283529160200191610524565b820191906000526020600020905b81548152906001019060200180831161050757829003601f168201915b50505050508787610ebb565b600381600981111561054457610544613aa7565b036105de5761046c82600101805461055b90613dc2565b80601f016020809104026020016040519081016040528092919081815260200182805461058790613dc2565b80156105d45780601f106105a9576101008083540402835291602001916105d4565b820191906000526020600020905b8154815290600101906020018083116105b757829003601f168201915b5050505050610f59565b60048160098111156105f2576105f2613aa7565b0361068c5761046c82600101805461060990613dc2565b80601f016020809104026020016040519081016040528092919081815260200182805461063590613dc2565b80156106825780601f1061065757610100808354040283529160200191610682565b820191906000526020600020905b81548152906001019060200180831161066557829003601f168201915b5050505050611087565b60058160098111156106a0576106a0613aa7565b0361073a5761046c8260010180546106b790613dc2565b80601f01602080910402602001604051908101604052809291908181526020018280546106e390613dc2565b80156107305780601f1061070557610100808354040283529160200191610730565b820191906000526020600020905b81548152906001019060200180831161071357829003601f168201915b505050505061131e565b600981600981111561074e5761074e613aa7565b036107ea5761046c82600101805461076590613dc2565b80601f016020809104026020016040519081016040528092919081815260200182805461079190613dc2565b80156107de5780601f106107b3576101008083540402835291602001916107de565b820191906000526020600020905b8154815290600101906020018083116107c157829003601f168201915b505050505086866114b5565b60068160098111156107fe576107fe613aa7565b036108a35761046c610811838787610c84565b83600101805461082090613dc2565b80601f016020809104026020016040519081016040528092919081815260200182805461084c90613dc2565b80156108995780601f1061086e57610100808354040283529160200191610899565b820191906000526020600020905b81548152906001019060200180831161087c57829003601f168201915b50505050506115c7565b60078160098111156108b7576108b7613aa7565b036109ec576040805160608101909152825461046c91908490829060ff1660098111156108e6576108e6613aa7565b60098111156108f7576108f7613aa7565b815260200160018201805461090b90613dc2565b80601f016020809104026020016040519081016040528092919081815260200182805461093790613dc2565b80156109845780601f1061095957610100808354040283529160200191610984565b820191906000526020600020905b81548152906001019060200180831161096757829003601f168201915b50505050508152602001600282018054806020026020016040519081016040528092919081815260200182805480156109dc57602002820191906000526020600020905b8154815260200190600101908083116109c8575b5050505050815250508686611728565b6008816009811115610a0057610a00613aa7565b03610a9a5761046c826001018054610a1790613dc2565b80601f0160208091040260200160405190810160405280929190818152602001828054610a4390613dc2565b8015610a905780601f10610a6557610100808354040283529160200191610a90565b820191906000526020600020905b815481529060010190602001808311610a7357829003601f168201915b50505050506118a5565b6040516323a9bbc960e01b815260048101879052602401610397565b600061011682610c26565b6000610116826118ea565b6000610ad782610ac1565b9050610ae28161192a565b15610b35577fcb64985827770858ec421ad26da7e558c757541643036ce44d6b4eb9e8e5dc5e81836000015184602001518560400151604051610b289493929190613e32565b60405180910390a1919050565b610b3e82611a8c565b610b5d578160405163382bbbc960e11b81526004016103979190613b21565b60005b826040015151811015610bd957610b9383604001518281518110610b8657610b86613e6a565b602002602001015161192a565b610bd15782604001518181518110610bad57610bad613e6a565b6020026020010151604051632f19f96160e11b815260040161039791815260200190565b600101610b60565b50610be382611c31565b8351602085015160408087015190519395507fcb64985827770858ec421ad26da7e558c757541643036ce44d6b4eb9e8e5dc5e9450610b28938693929190613e32565b604080516020808201839052606082018190527f696f2e73796e7468657469782e6f7261636c652d6d616e616765722e4e6f6465608080840191909152828401949094528251808303909401845260a0909101909152815191012090565b600283015460609067ffffffffffffffff811115610ca457610ca4613b9a565b604051908082528060200260200182016040528015610cdd57816020015b610cca6139e4565b815260200190600190039081610cc25790505b50905060005b6002850154811015610d3e57610d19856002018281548110610d0757610d07613e6a565b90600052602060002001548585610310565b828281518110610d2b57610d2b613e6a565b6020908102919091010152600101610ce3565b509392505050565b610d4e6139e4565b600082806020019051810190610d649190613e80565b90506000816008811115610d7a57610d7a613aa7565b03610d9057610d8884611ca5565b915050610116565b6001816008811115610da457610da4613aa7565b03610db257610d8884611d0d565b6002816008811115610dc657610dc6613aa7565b03610dd457610d8884611d90565b6003816008811115610de857610de8613aa7565b03610df657610d8884611e13565b6004816008811115610e0a57610e0a613aa7565b03610e1857610d8884611ec9565b6005816008811115610e2c57610e2c613aa7565b03610e3a57610d8884612009565b6006816008811115610e4e57610e4e613aa7565b03610e5c57610d88846120e4565b6007816008811115610e7057610e70613aa7565b03610e7e57610d888461220c565b6008816008811115610e9257610e92613aa7565b03610ea057610d88846122ce565b80604051631be413d360e11b81526004016103979190613ea1565b610ec36139e4565b600084806020019051810190610ed99190613ed3565b604051631ecba7c360e31b81529091506001600160a01b0382169063f65d3e1890610f0e908990899089908990600401613ef0565b608060405180830381865afa158015610f2b573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610f4f9190613f91565b9695505050505050565b610f616139e4565b600080600084806020019051810190610f7a9190613fe8565b92509250925060008390506000806000836001600160a01b031663feaf968c6040518163ffffffff1660e01b815260040160a060405180830381865afa158015610fc8573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610fec9190614041565b509350509250925060008660001461100f5761100a8585858a6123c7565b611011565b825b905060128660ff161161103b5761103661102f60ff881660126140a7565b82906124c2565b611053565b61105361104c601260ff89166140a7565b82906124dc565b9050604051806080016040528082815260200183815260200160008152602001600081525098505050505050505050919050565b61108f6139e4565b600080600080600080878060200190518101906110ac91906140ba565b604080516002808252606082018352979d50959b50939950919750955093506000929060208301908036833701905050905081816000815181106110f2576110f2613e6a565b602002602001019063ffffffff16908163ffffffff168152505060008160018151811061112157611121613e6a565b63ffffffff9092166020928302919091019091015260405163883bdbfd60e01b81526000906001600160a01b0385169063883bdbfd90611165908590600401614143565b600060405180830381865afa158015611182573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f191682016040526111aa91908101906141f5565b5090506000816000815181106111c2576111c2613e6a565b6020026020010151826001815181106111dd576111dd613e6a565b60200260200101516111ef91906142c1565b9050600061121761120563ffffffff87166124f6565b61120f9084614304565b60060b61252d565b905060008260060b12801561124c575061123b63ffffffff8616612569565b612569565b8260060b6112499190614342565b15155b1561125f578061125b81614356565b9150505b600061126d6012600a61445d565b9050600061128061123684848f8f612593565b905060006112908a60ff16612569565b61129c8c60ff16612569565b6112a6919061446c565b905060008082136112d1576112cc6112c56112c084614493565b612686565b84906124dc565b6112e4565b6112e46112dd83612686565b84906124c2565b905060405180608001604052808281526020014281526020016000815260200160008152509e505050505050505050505050505050919050565b6113266139e4565b60008060008480602001905181019061133f91906144bf565b91945092509050826000826113bc576040516396834ad360e01b8152600481018590526001600160a01b038316906396834ad390602401608060405180830381865afa158015611393573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906113b791906144f5565b611425565b604051639474f45b60e01b8152600481018590526001600160a01b03831690639474f45b90602401608060405180830381865afa158015611401573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061142591906144f5565b90506000816040015160030b601261143d919061456f565b90506000808213611467576114626114576112c084614493565b845160070b906124dc565b61147e565b61147e61147383612686565b845160070b906124c2565b9050604051806080016040528082815260200184606001518152602001600081526020016000815250975050505050505050919050565b6114bd6139e4565b6000806000868060200190518101906114d69190614597565b92509250925060005b8651811015611545578681815181106114fa576114fa613e6a565b6020026020010151717374616c656e657373546f6c6572616e636560701b0361153d5785818151811061152f5761152f613e6a565b602002602001015160001c91505b6001016114df565b5060408051600180825281830190925260009160208083019080368337019050509050828160008151811061157c5761157c613e6a565b602002602001018181525050836001838360405160200161159f939291906145ce565b60408051601f198184030181529082905263cf2cabdf60e01b82526103979291600401614603565b6115cf6139e4565b6000828060200190518101906115e59190614627565b90506000846000815181106115fc576115fc613e6a565b602002602001015160000151905060008560018151811061161f5761161f613e6a565b6020026020010151600001519050808214611702576000611653601261164d611648858761446c565b6126a9565b906124c2565b905082158061167b5750611666836126a9565b6116709082614640565b61167985612569565b125b15611700576002875111156116b0578660028151811061169d5761169d613e6a565b6020026020010151945050505050610116565b826000036116d15760405163014cc07160e01b815260040160405180910390fd5b6116da836126a9565b6116e49082614640565b60405163dcac091960e01b815260040161039791815260200190565b505b8560008151811061171557611715613e6a565b6020026020010151935050505092915050565b6117306139e4565b6000846020015180602001905181019061174a9190614627565b905060005b84518110156117bc5784818151811061176a5761176a613e6a565b6020026020010151717374616c656e657373546f6c6572616e636560701b036117b4576117ad8482815181106117a2576117a2613e6a565b602002602001015190565b91506117bc565b60010161174f565b50600085604001516000815181106117d6576117d6613e6a565b6020026020010151905060006117ed828787610310565b60208101519091506117ff84426140a7565b1161180e5792506102b4915050565b86604001515160010361187157866040015160008151811061183257611832613e6a565b602002602001015181600001518260200151604051631808066560e21b8152600401610397939291909283526020830191909152604082015260600190565b61189a876040015160018151811061188b5761188b613e6a565b60200260200101518787610310565b979650505050505050565b6118ad6139e4565b6040518060800160405280838060200190518101906118cc9190614627565b81526020014281526020016000815260200160008152509050919050565b600081600001518260200151836040015160405160200161190d9392919061466e565b604051602081830303815290604052805190602001209050919050565b60008061193683610c26565b60408051606081019091528154909190829060ff16600981111561195c5761195c613aa7565b600981111561196d5761196d613aa7565b815260200160018201805461198190613dc2565b80601f01602080910402602001604051908101604052809291908181526020018280546119ad90613dc2565b80156119fa5780601f106119cf576101008083540402835291602001916119fa565b820191906000526020600020905b8154815290600101906020018083116119dd57829003601f168201915b5050505050815260200160028201805480602002602001604051908101604052809291908181526020018280548015611a5257602002820191906000526020600020905b815481526020019060010190808311611a3e575b505050505081525050905060006009811115611a7057611a70613aa7565b81516009811115611a8357611a83613aa7565b14159392505050565b6000600182516009811115611aa357611aa3613aa7565b1480611ac15750600682516009811115611abf57611abf613aa7565b145b80611ade5750600782516009811115611adc57611adc613aa7565b145b15611aee57611aec826126c1565b505b600182516009811115611b0357611b03613aa7565b03611b11576101168261284a565b600282516009811115611b2657611b26613aa7565b03611b3457610116826128a5565b600382516009811115611b4957611b49613aa7565b03611b575761011682612973565b600482516009811115611b6c57611b6c613aa7565b03611b7a5761011682612aae565b600582516009811115611b8f57611b8f613aa7565b03611b9d5761011682612e92565b600982516009811115611bb257611bb2613aa7565b03611bc05761011682612fcb565b600682516009811115611bd557611bd5613aa7565b03611be3576101168261300e565b600782516009811115611bf857611bf8613aa7565b03611c065761011682613052565b600882516009811115611c1b57611c1b613aa7565b03611c295761011682613078565b506000919050565b600080611c3d836118ea565b9050611c4881610c26565b8351815491935090839060ff19166001836009811115611c6a57611c6a613aa7565b021790555060208301516001830190611c8390826146ed565b5060408301518051611c9f916002850191602090910190613a0c565b50915091565b611cad6139e4565b60005b8251811015611d07578160200151838281518110611cd057611cd0613e6a565b6020026020010151602001511115611cff57828181518110611cf457611cf4613e6a565b602002602001015191505b600101611cb0565b50919050565b611d156139e4565b81600081518110611d2857611d28613e6a565b602002602001015190506000600190505b8251811015611d07578160000151838281518110611d5957611d59613e6a565b6020026020010151600001511215611d8857828181518110611d7d57611d7d613e6a565b602002602001015191505b600101611d39565b611d986139e4565b81600081518110611dab57611dab613e6a565b602002602001015190506000600190505b8251811015611d07578160000151838281518110611ddc57611ddc613e6a565b6020026020010151600001511315611e0b57828181518110611e0057611e00613e6a565b602002602001015191505b600101611dbc565b611e1b6139e4565b60005b8251811015611e9557828181518110611e3957611e39613e6a565b60200260200101516000015182600001818151611e56919061456f565b9052508251839082908110611e6d57611e6d613e6a565b60200260200101516020015182602001818151611e8a91906147ad565b905250600101611e1e565b50611ea08251612569565b8151611eac9190614640565b815281516020820151611ebf91906147c0565b6020820152919050565b611ed16139e4565b611eed826000611ee86001865161123691906140a7565b6130a4565b60028251611efb91906147d4565b600003611fd65760408051600280825260608201909252600091816020015b611f226139e4565b815260200190600190039081611f1a57905050905082600160028551611f4891906147c0565b611f5291906140a7565b81518110611f6257611f62613e6a565b602002602001015181600081518110611f7d57611f7d613e6a565b60200260200101819052508260028451611f9791906147c0565b81518110611fa757611fa7613e6a565b602002602001015181600181518110611fc257611fc2613e6a565b60200260200101819052506102b481611e13565b8160028351611fe591906147c0565b81518110611ff557611ff5613e6a565b60200260200101519050919050565b919050565b6120116139e4565b8160008151811061202457612024613e6a565b60209081029190910101515181528151829060009061204557612045613e6a565b6020908102919091018101518101519082015260015b82518110156120d25782818151811061207657612076613e6a565b6020026020010151600001518260000181815161209391906147e8565b90525082518390829081106120aa576120aa613e6a565b602002602001015160200151826020018181516120c791906147ad565b90525060010161205b565b5081518160200151611ebf91906147c0565b6120ec6139e4565b816000815181106120ff576120ff613e6a565b60209081029190910101515181528151829060009061212057612120613e6a565b6020908102919091018101518101519082015260015b82518110156120d25782818151811061215157612151613e6a565b60200260200101516000015160000361219e5782818151811061217657612176613e6a565b6020026020010151600001516040516338ee04a760e01b815260040161039791815260200190565b8281815181106121b0576121b0613e6a565b602002602001015160000151826000018181516121cd9190614640565b90525082518390829081106121e4576121e4613e6a565b6020026020010151602001518260200181815161220191906147ad565b905250600101612136565b6122146139e4565b8160008151811061222757612227613e6a565b60209081029190910101515181528151829060009061224857612248613e6a565b6020908102919091018101518101519082015260015b82518110156120d25761229083828151811061227c5761227c613e6a565b602090810291909101015151835190613264565b825282518390829081106122a6576122a6613e6a565b602002602001015160200151826020018181516122c391906147ad565b90525060010161225e565b6122d66139e4565b816000815181106122e9576122e9613e6a565b60209081029190910101515181528151829060009061230a5761230a613e6a565b6020908102919091018101518101519082015260015b82518110156120d25782818151811061233b5761233b613e6a565b6020026020010151600001516000036123605782818151811061217657612176613e6a565b61238983828151811061237557612375613e6a565b602090810291909101015151835190613283565b8252825183908290811061239f5761239f613e6a565b602002602001015160200151826020018181516123bc91906147ad565b905250600101612320565b6000826001826123d785426140a7565b90505b69ffffffffffffffffffff8716156124a3576001600160a01b038816639a6fc8f561240489614818565b6040516001600160e01b031960e084901b16815269ffffffffffffffffffff8216600482015290995060240160a060405180830381865afa925050508015612469575060408051601f3d908101601f1916820190925261246691810190614041565b60015b156124a357858210156124805750505050506124a3565b61248a848961456f565b97508661249681614834565b97505050505050506123da565b6124ac82612569565b6124b69084614640565b98975050505050505050565b60006124d261123683600a61484d565b6102b490846147e8565b60006124ec61123683600a61484d565b6102b49084614640565b6000667fffffffffffff66ffffffffffffff83161115612529576040516329d2678160e21b815260040160405180910390fd5b5090565b6000627fffff19600683900b128061254b5750627fffff600683900b135b1561252957604051630d962f7960e21b815260040160405180910390fd5b60006001600160ff1b038211156125295760405163677c430560e11b815260040160405180910390fd5b60008061259f86613298565b90506fffffffffffffffffffffffffffffffff6001600160a01b0382161161261c5760006125d66001600160a01b03831680614859565b9050836001600160a01b0316856001600160a01b03161061260557612600600160c01b87836136cd565b612614565b6126148187600160c01b6136cd565b92505061267d565b600061263b6001600160a01b03831680680100000000000000006136cd565b9050836001600160a01b0316856001600160a01b03161061266a57612665600160801b87836136cd565b612679565b6126798187600160801b6136cd565b9250505b50949350505050565b6000808212156125295760405163029f024d60e31b815260040160405180910390fd5b600080821215612529576126bc82614493565b610116565b6000805b8260400151518110156128415760006126fa846040015183815181106126ed576126ed613e6a565b6020026020010151610ab6565b60408051606081019091528154909190829060ff16600981111561272057612720613aa7565b600981111561273157612731613aa7565b815260200160018201805461274590613dc2565b80601f016020809104026020016040519081016040528092919081815260200182805461277190613dc2565b80156127be5780601f10612793576101008083540402835291602001916127be565b820191906000526020600020905b8154815290600101906020018083116127a157829003601f168201915b505050505081526020016002820180548060200260200160405190810160405280929190818152602001828054801561281657602002820191906000526020600020905b815481526020019060010190808311612802575b505050505081525050905061282a81611a8c565b612838575060009392505050565b506001016126c5565b50600192915050565b60006002826040015151101561286257506000919050565b81602001515160201461287757506000919050565b600082602001518060200190518101906128919190614627565b905060088111156128415750600092915050565b6000602082602001515110156128bd57506000919050565b600082602001518060200190518101906128d79190613ed3565b90506128ea816306e7ea3960e21b6138e2565b6128f75750600092915050565b604051633b70a5bf60e21b81526001600160a01b0382169063edc296fc90612923908690600401613b21565b6020604051808303816000875af1158015612942573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906129669190614870565b6128415750600092915050565b6040810151516000901561298957506000919050565b81602001515160601461299e57506000919050565b60008083602001518060200190518101906129b99190613fe8565b92505091506000829050806001600160a01b031663feaf968c6040518163ffffffff1660e01b815260040160a060405180830381865afa158015612a01573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612a259190614041565b5050505050806001600160a01b031663313ce5676040518163ffffffff1660e01b8152600401602060405180830381865afa158015612a68573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612a8c919061488b565b60ff168260ff1614612aa357506000949350505050565b506001949350505050565b60408101515160009015612ac457506000919050565b81602001515160c014612ad957506000919050565b6000806000806000808760200151806020019051810190612afa91906140ba565b9550955095509550955095508360ff16866001600160a01b031663313ce5676040518163ffffffff1660e01b8152600401602060405180830381865afa158015612b48573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612b6c919061488b565b60ff1614612b8257506000979650505050505050565b8260ff16856001600160a01b031663313ce5676040518163ffffffff1660e01b8152600401602060405180830381865afa158015612bc4573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612be8919061488b565b60ff1614612bfe57506000979650505050505050565b6000826001600160a01b0316630dfe16816040518163ffffffff1660e01b8152600401602060405180830381865afa158015612c3e573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612c629190613ed3565b90506000836001600160a01b031663d21220a76040518163ffffffff1660e01b8152600401602060405180830381865afa158015612ca4573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612cc89190613ed3565b9050876001600160a01b0316826001600160a01b0316148015612cfc5750866001600160a01b0316816001600160a01b0316145b158015612d385750866001600160a01b0316826001600160a01b0316148015612d365750876001600160a01b0316816001600160a01b0316145b155b15612d4d575060009998505050505050505050565b60128660ff161180612d62575060128560ff16115b15612d77575060009998505050505050505050565b8263ffffffff16600003612d95575060009998505050505050505050565b6040805160028082526060820183526000926020830190803683370190505090508381600081518110612dca57612dca613e6a565b602002602001019063ffffffff16908163ffffffff1681525050600081600181518110612df957612df9613e6a565b63ffffffff9092166020928302919091019091015260405163883bdbfd60e01b81526001600160a01b0386169063883bdbfd90612e3a908490600401614143565b600060405180830381865afa158015612e57573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f19168201604052612e7f91908101906141f5565b5060019c9b505050505050505050505050565b60408101515160009015612ea857506000919050565b816020015151606014612ebd57506000919050565b60008060008460200151806020019051810190612eda91906144bf565b919450925090508281612f55576040516396834ad360e01b8152600481018490526001600160a01b038216906396834ad390602401608060405180830381865afa158015612f2c573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612f5091906144f5565b612fbe565b604051639474f45b60e01b8152600481018490526001600160a01b03821690639474f45b90602401608060405180830381865afa158015612f9a573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612fbe91906144f5565b5060019695505050505050565b60408101515160009015612fe157506000919050565b816020015151606014612ff657506000919050565b8160200151806020019051810190612aa39190614597565b60008160400151516002148061302957508160400151516003145b61303557506000919050565b81602001515160201461304a57506000919050565b506001919050565b600081604001515160011480613029575081604001515160021461303557506000919050565b6040810151516000901561308e57506000919050565b6020826020015151101561304a57506000919050565b81818082036130b4575050505050565b6000856130da60026130c6888861446c565b6130d09190614640565b6112c0908861456f565b815181106130ea576130ea613e6a565b60200260200101516000015190505b818313613236575b808661310c85612686565b8151811061311c5761311c613e6a565b60200260200101516000015112156131405782613138816148a6565b935050613101565b8561314a83612686565b8151811061315a5761315a613e6a565b60200260200101516000015181121561317f5781613177816148be565b925050613140565b818313613231578561319083612686565b815181106131a0576131a0613e6a565b6020026020010151866131b285612686565b815181106131c2576131c2613e6a565b6020026020010151876131d486612686565b815181106131e4576131e4613e6a565b60200260200101886131f586612686565b8151811061320557613205613e6a565b602002602001018290528290525050828061321f906148a6565b935050818061322d906148be565b9250505b6130f9565b81851215613249576132498686846130a4565b8383121561325c5761325c8684866130a4565b505050505050565b6000670de0b6b3a764000061327983856147e8565b6102b49190614640565b600081613279670de0b6b3a7640000856147e8565b60008060008360020b126132b8576132b3600284900b612686565b6132c8565b6132c86112c0600285900b614493565b90506132e36112c06132dd620d89e7196148db565b60020b90565b8111156133165760405162461bcd60e51b81526020600482015260016024820152601560fa1b6044820152606401610397565b60008160011660000361332d57600160801b61333f565b6ffffcb933bd6fad37aa2d162d1a5940015b70ffffffffffffffffffffffffffffffffff169050600282161561337e576080613379826ffff97272373d413259a46990580e213a614859565b901c90505b60048216156133a85760806133a3826ffff2e50f5f656932ef12357cf3c7fdcc614859565b901c90505b60088216156133d25760806133cd826fffe5caca7e10e4e61c3624eaa0941cd0614859565b901c90505b60108216156133fc5760806133f7826fffcb9843d60f6159c9db58835c926644614859565b901c90505b6020821615613426576080613421826fff973b41fa98c081472e6896dfb254c0614859565b901c90505b604082161561345057608061344b826fff2ea16466c96a3843ec78b326b52861614859565b901c90505b608082161561347a576080613475826ffe5dee046a99a2a811c461f1969c3053614859565b901c90505b6101008216156134a55760806134a0826ffcbe86c7900a88aedcffc83b479aa3a4614859565b901c90505b6102008216156134d05760806134cb826ff987a7253ac413176f2b074cf7815e54614859565b901c90505b6104008216156134fb5760806134f6826ff3392b0822b70005940c7a398e4b70f3614859565b901c90505b610800821615613526576080613521826fe7159475a2c29b7443b29c7fa6e889d9614859565b901c90505b61100082161561355157608061354c826fd097f3bdfd2022b8845ad8f792aa5825614859565b901c90505b61200082161561357c576080613577826fa9f746462d870fdf8a65dc1f90e061e5614859565b901c90505b6140008216156135a75760806135a2826f70d869a156d2a1b890bb3df62baf32f7614859565b901c90505b6180008216156135d25760806135cd826f31be135f97d08fd981231505542fcfa6614859565b901c90505b620100008216156135fe5760806135f9826f09aa508b5b7a84e1c677de54f3e99bc9614859565b901c90505b62020000821615613629576080613624826e5d6af8dedb81196699c329225ee604614859565b901c90505b6204000082161561365357608061364e826d2216e584f5fa1ea926041bedfe98614859565b901c90505b6208000082161561367b576080613676826b048a170391f7dc42444e8fa2614859565b901c90505b60008460020b131561369657613693816000196147c0565b90505b6102ce6136a8640100000000836147d4565b156136b45760016136b7565b60005b6136c89060ff16602084901c6147ad565b6139ba565b6000808060001985870985870292508281108382030391505080600003613749576000841161373e5760405162461bcd60e51b815260206004820152601960248201527f48616e646c65206e6f6e2d6f766572666c6f77206361736573000000000000006044820152606401610397565b5082900490506102b4565b8084116137985760405162461bcd60e51b815260206004820152601960248201527f70726576656e74732064656e6f6d696e61746f72203d3d2030000000000000006044820152606401610397565b60008486880980840393811190920391905060006137d06137b887612569565b6137c188612569565b6137ca90614493565b16612686565b9586900495938490049360008190030460010190506137ef8184614859565b909317926000613800876003614859565b600218905061380f8188614859565b61381a9060026140a7565b6138249082614859565b90506138308188614859565b61383b9060026140a7565b6138459082614859565b90506138518188614859565b61385c9060026140a7565b6138669082614859565b90506138728188614859565b61387d9060026140a7565b6138879082614859565b90506138938188614859565b61389e9060026140a7565b6138a89082614859565b90506138b48188614859565b6138bf9060026140a7565b6138c99082614859565b90506138d58186614859565b9998505050505050505050565b604080516001600160e01b0319831660248083019190915282518083039091018152604490910182526020810180516001600160e01b03166301ffc9a760e01b1790529051600091829182916001600160a01b0387169161394391906148fd565b6000604051808303816000865af19150503d8060008114613980576040519150601f19603f3d011682016040523d82523d6000602084013e613985565b606091505b50915091508161399a57600092505050610116565b80516000036139ae57600092505050610116565b60200151949350505050565b60006001600160a01b038211156125295760405163dccde8ed60e01b815260040160405180910390fd5b6040518060800160405280600081526020016000815260200160008152602001600081525090565b828054828255906000526020600020908101928215613a47579160200282015b82811115613a47578251825591602001919060010190613a2c565b506125299291505b808211156125295760008155600101613a4f565b600060208284031215613a7557600080fd5b5035919050565b8151815260208083015190820152604080830151908201526060808301519082015260808101610116565b634e487b7160e01b600052602160045260246000fd5b600a8110613acd57613acd613aa7565b9052565b60005b83811015613aec578181015183820152602001613ad4565b50506000910152565b60008151808452613b0d816020860160208601613ad1565b601f01601f19169290920160200192915050565b60006020808352613b358184018551613abd565b8084015160606040850152613b4d6080850182613af5565b6040860151858203601f19016060870152805180835290840192506000918401905b80831015613b8f5783518252928401926001929092019190840190613b6f565b509695505050505050565b634e487b7160e01b600052604160045260246000fd5b6040516080810167ffffffffffffffff81118282101715613bd357613bd3613b9a565b60405290565b604051601f8201601f1916810167ffffffffffffffff81118282101715613c0257613c02613b9a565b604052919050565b600067ffffffffffffffff821115613c2457613c24613b9a565b5060051b60200190565b600082601f830112613c3f57600080fd5b81356020613c54613c4f83613c0a565b613bd9565b8083825260208201915060208460051b870101935086841115613c7657600080fd5b602086015b84811015613b8f5780358352918301918301613c7b565b600080600060608486031215613ca757600080fd5b8335600a8110613cb657600080fd5b925060208481013567ffffffffffffffff80821115613cd457600080fd5b818701915087601f830112613ce857600080fd5b813581811115613cfa57613cfa613b9a565b613d0c601f8201601f19168501613bd9565b8181528985838601011115613d2057600080fd5b818585018683013760009181019094015291935060408601359180831115613d4757600080fd5b5050613d5586828701613c2e565b9150509250925092565b600080600060608486031215613d7457600080fd5b83359250602084013567ffffffffffffffff80821115613d9357600080fd5b613d9f87838801613c2e565b93506040860135915080821115613db557600080fd5b50613d5586828701613c2e565b600181811c90821680613dd657607f821691505b602082108103611d0757634e487b7160e01b600052602260045260246000fd5b60008151808452602080850194506020840160005b83811015613e2757815187529582019590820190600101613e0b565b509495945050505050565b848152613e426020820185613abd565b608060408201526000613e586080830185613af5565b828103606084015261189a8185613df6565b634e487b7160e01b600052603260045260246000fd5b600060208284031215613e9257600080fd5b8151600981106102b457600080fd5b6020810160098310613eb557613eb5613aa7565b91905290565b6001600160a01b0381168114613ed057600080fd5b50565b600060208284031215613ee557600080fd5b81516102b481613ebb565b608080825285518282018190526000919060209060a0850190828a01855b82811015613f5257613f42848351805182526020810151602083015260408101516040830152606081015160608301525050565b9285019290840190600101613f0e565b5050508481036020860152613f678189613af5565b925050508281036040840152613f7d8186613df6565b9050828103606084015261189a8185613df6565b600060808284031215613fa357600080fd5b613fab613bb0565b825181526020830151602082015260408301516040820152606083015160608201528091505092915050565b805160ff8116811461200457600080fd5b600080600060608486031215613ffd57600080fd5b835161400881613ebb565b6020850151909350915061401e60408501613fd7565b90509250925092565b805169ffffffffffffffffffff8116811461200457600080fd5b600080600080600060a0868803121561405957600080fd5b61406286614027565b945060208601519350604086015192506060860151915061408560808701614027565b90509295509295909350565b634e487b7160e01b600052601160045260246000fd5b8181038181111561011657610116614091565b60008060008060008060c087890312156140d357600080fd5b86516140de81613ebb565b60208801519096506140ef81613ebb565b94506140fd60408801613fd7565b935061410b60608801613fd7565b9250608087015161411b81613ebb565b60a088015190925063ffffffff8116811461413557600080fd5b809150509295509295509295565b6020808252825182820181905260009190848201906040850190845b8181101561418157835163ffffffff168352928401929184019160010161415f565b50909695505050505050565b600082601f83011261419e57600080fd5b815160206141ae613c4f83613c0a565b8083825260208201915060208460051b8701019350868411156141d057600080fd5b602086015b84811015613b8f5780516141e881613ebb565b83529183019183016141d5565b6000806040838503121561420857600080fd5b825167ffffffffffffffff8082111561422057600080fd5b818501915085601f83011261423457600080fd5b81516020614244613c4f83613c0a565b82815260059290921b8401810191818101908984111561426357600080fd5b948201945b838610156142915785518060060b81146142825760008081fd5b82529482019490820190614268565b918801519196509093505050808211156142aa57600080fd5b506142b78582860161418d565b9150509250929050565b600682810b9082900b03667fffffffffffff198112667fffffffffffff8213171561011657610116614091565b634e487b7160e01b600052601260045260246000fd5b60008160060b8360060b8061431b5761431b6142ee565b667fffffffffffff1982146000198214161561433957614339614091565b90059392505050565b600082614351576143516142ee565b500790565b60008160020b627fffff19810361436f5761436f614091565b6000190192915050565b600181815b808511156143b457816000190482111561439a5761439a614091565b808516156143a757918102915b93841c939080029061437e565b509250929050565b6000826143cb57506001610116565b816143d857506000610116565b81600181146143ee57600281146143f857614414565b6001915050610116565b60ff84111561440957614409614091565b50506001821b610116565b5060208310610133831016604e8410600b8410161715614437575081810a610116565b6144418383614379565b806000190482111561445557614455614091565b029392505050565b60006102b460ff8416836143bc565b818103600083128015838313168383128216171561448c5761448c614091565b5092915050565b6000600160ff1b82016144a8576144a8614091565b5060000390565b8051801515811461200457600080fd5b6000806000606084860312156144d457600080fd5b83516144df81613ebb565b6020850151909350915061401e604085016144af565b60006080828403121561450757600080fd5b61450f613bb0565b82518060070b811461452057600080fd5b8152602083015167ffffffffffffffff8116811461453d57600080fd5b60208201526040830151600381900b811461455757600080fd5b60408201526060928301519281019290925250919050565b808201828112600083128015821682158216171561458f5761458f614091565b505092915050565b6000806000606084860312156145ac57600080fd5b83516145b781613ebb565b602085015160409095015190969495509392505050565b60ff8416815267ffffffffffffffff831660208201526060604082015260006145fa6060830184613df6565b95945050505050565b6001600160a01b03831681526040602082018190526000906102ce90830184613af5565b60006020828403121561463957600080fd5b5051919050565b60008261464f5761464f6142ee565b600160ff1b82146000198414161561466957614669614091565b500590565b6146788185613abd565b60606020820152600061468e6060830185613af5565b8281036040840152610f4f8185613df6565b601f8211156146e8576000816000526020600020601f850160051c810160208610156146c95750805b601f850160051c820191505b8181101561325c578281556001016146d5565b505050565b815167ffffffffffffffff81111561470757614707613b9a565b61471b816147158454613dc2565b846146a0565b602080601f83116001811461475057600084156147385750858301515b600019600386901b1c1916600185901b17855561325c565b600085815260208120601f198616915b8281101561477f57888601518255948401946001909101908401614760565b508582101561479d5787850151600019600388901b60f8161c191681555b5050505050600190811b01905550565b8082018082111561011657610116614091565b6000826147cf576147cf6142ee565b500490565b6000826147e3576147e36142ee565b500690565b80820260008212600160ff1b8414161561480457614804614091565b818105831482151761011657610116614091565b600069ffffffffffffffffffff82168061436f5761436f614091565b60006001820161484657614846614091565b5060010190565b60006102b483836143bc565b808202811582820484141761011657610116614091565b60006020828403121561488257600080fd5b6102b4826144af565b60006020828403121561489d57600080fd5b6102b482613fd7565b60006001600160ff1b01820161484657614846614091565b6000600160ff1b82016148d3576148d3614091565b506000190190565b60008160020b627fffff1981036148f4576148f4614091565b60000392915050565b6000825161490f818460208701613ad1565b919091019291505056fea264697066735822122074f32fef384fdc296b0859f1c1f941c8e736c6cb972aa9e2b894956ebd6a80b364736f6c63430008160033","r":"0x1","s":"0x1","yParity":"0x0","hash":"0xbc73db80bf4b8784ba10a8910a0b7ef85f6846d102b41dd990969ea205335354"}}],"ommers":[]},{"header":{"parentHash":"0x026ae0c6ae91f186a9befa1ac8be30eea35e30e77de51a731085221e5cd39209","sha3Uncles":"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347","miner":"0x0000000000000000000000000000000000000000","stateRoot":"0xb6003e7ba07a15a9e35f63daa484728ec4ceeded0c4d10ac1b04e9552d412b3c","transactionsRoot":"0x6e4969a136061ca7a390d12830d47a151585325a8d396819fb2b958ff85e9f8f","receiptsRoot":"0xc3e81df67d3e2a6c8345a954ef250cfcc41abcc2292a5aa263071124533fc9ad","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","difficulty":"0x0","number":"0x3","gasLimit":"0x1c9c380","gasUsed":"0x3c0f6","timestamp":"0x66b200ce","mixHash":"0x0000000000000000000000000000000000000000000000000000000000000000","nonce":"0x0000000000000000","baseFeePerGas":"0x18993a68","blobGasUsed":"0x0","excessBlobGas":"0x0","extraData":"0x"},"transactions":[{"EIP1559":{"chainId":"0x343a","nonce":"0x0","gas":"0x3c0f6","maxFeePerGas":"0x5d4285cd","maxPriorityFeePerGas":"0x3b9aca00","value":"0x0","accessList":[],"input":"0x608060405234801561001057600080fd5b50610380806100206000396000f3fe6080604052600080357fffffffff0000000000000000000000000000000000000000000000000000000016905060008160e01c610251565b60006379ba509782101561015e5781631627540c811461009857632a952b2d81146100b457633659cfe681146100d0576350c946fe81146100ec576353a47bb781146101085763625ca21c81146101245763718fe928811461014057610158565b7347d08dad17ccb558b3ea74b1a0e73a9cc804a9dc9150610158565b738138ef7cf908021d117e542120b7a390650161079150610158565b7347d08dad17ccb558b3ea74b1a0e73a9cc804a9dc9150610158565b738138ef7cf908021d117e542120b7a390650161079150610158565b7347d08dad17ccb558b3ea74b1a0e73a9cc804a9dc9150610158565b738138ef7cf908021d117e542120b7a390650161079150610158565b7347d08dad17ccb558b3ea74b1a0e73a9cc804a9dc91505b5061024c565b816379ba509781146101a657638da5cb5b81146101c25763aaf10f4281146101de5763c7f62cda81146101fa5763daa250be81146102165763deba1b9881146102325761024a565b7347d08dad17ccb558b3ea74b1a0e73a9cc804a9dc915061024a565b7347d08dad17ccb558b3ea74b1a0e73a9cc804a9dc915061024a565b7347d08dad17ccb558b3ea74b1a0e73a9cc804a9dc915061024a565b7347d08dad17ccb558b3ea74b1a0e73a9cc804a9dc915061024a565b738138ef7cf908021d117e542120b7a39065016107915061024a565b738138ef7cf908021d117e542120b7a3906501610791505b505b919050565b61025a81610037565b915050600073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff16036102ce57816040517fc2a825f50000000000000000000000000000000000000000000000000000000081526004016102c5919061032f565b60405180910390fd5b3660008037600080366000845af43d6000803e80600081146102ef573d6000f35b3d6000fd5b60007fffffffff0000000000000000000000000000000000000000000000000000000082169050919050565b610329816102f4565b82525050565b60006020820190506103446000830184610320565b9291505056fea264697066735822122017a4b7fdaaab3897a7b47abaed8d2ee92d558883d3bb2a8454f9601b2ab2c3db64736f6c63430008150033","r":"0x1","s":"0x1","yParity":"0x0","hash":"0x2476e039803622aeb040f924f04c493f559aed3d6c9372ab405cb33c8c695328"}}],"ommers":[]},{"header":{"parentHash":"0x3d22100ac0ee8d5cde334f7f926191a861b0648971ebc179547df28a0224c6d0","sha3Uncles":"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347","miner":"0x0000000000000000000000000000000000000000","stateRoot":"0x9511d4711e5c30a72b0bff38a261daa75dcc5ba8b772d970a5c742244b4c861b","transactionsRoot":"0xba5fff578d3d6c2cd63acbe9bca353eaa6fe22a5c408956eff49106e0a96c507","receiptsRoot":"0xbae111f01cb07677e3a8c5031546138407c01bc964d3493d732dc4edf47d36d3","logsBloom":"0x00000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000020000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000001000000000000000000000400000001000010000000000000000000000000000000000000000000000000000000000000000000000000","difficulty":"0x0","number":"0x5","gasLimit":"0x1c9c380","gasUsed":"0xcae7","timestamp":"0x66b200cb","mixHash":"0x0000000000000000000000000000000000000000000000000000000000000000","nonce":"0x0000000000000000","baseFeePerGas":"0x12e09c7a","blobGasUsed":"0x0","excessBlobGas":"0x0","extraData":"0x"},"transactions":[{"EIP1559":{"chainId":"0x343a","nonce":"0x0","gas":"0xcc4d","maxFeePerGas":"0x557e5ec4","maxPriorityFeePerGas":"0x3b9aca00","to":"0x83a0444b93927c3afcbe46e522280390f748e171","value":"0x0","accessList":[],"input":"0x3659cfe6000000000000000000000000108f53faf774d7c4c56f5bce9ca6e605ce8aeadd","r":"0x1","s":"0x1","yParity":"0x0","hash":"0xf88e7b19ee347145c257e0cf7ac4ecc2bae83ca79d7edaa231e71d3213aeb151"}}],"ommers":[]},{"header":{"parentHash":"0x08abe6e453727534d8dd708843a7522b7d500338bdfe2402ca105dcdb05eebe9","sha3Uncles":"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347","miner":"0x0000000000000000000000000000000000000000","stateRoot":"0x9c8eaf493f8b4edce2ba1647343eadcc0989cf461e712c0a6253ff2ca1842bb7","transactionsRoot":"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421","receiptsRoot":"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","difficulty":"0x0","number":"0x1","gasLimit":"0x1c9c380","gasUsed":"0x0","timestamp":"0x66b200ca","mixHash":"0x0000000000000000000000000000000000000000000000000000000000000000","nonce":"0x0000000000000000","baseFeePerGas":"0x3b9aca00","blobGasUsed":"0x0","excessBlobGas":"0x0","extraData":"0x"},"transactions":[],"ommers":[]},{"header":{"parentHash":"0xdd07c07470e1deff3749831f0f1ad8d4b6e35505e83b3c6ea14181716197cd8a","sha3Uncles":"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347","miner":"0x0000000000000000000000000000000000000000","stateRoot":"0x29aa352e71b139e83b397bdd3dcf9b65d74770edaf3a9624d0dbc4f96f868680","transactionsRoot":"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421","receiptsRoot":"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","difficulty":"0x0","number":"0x2","gasLimit":"0x1c9c380","gasUsed":"0x0","timestamp":"0x66b200cb","mixHash":"0x0000000000000000000000000000000000000000000000000000000000000000","nonce":"0x0000000000000000","baseFeePerGas":"0x24a1ab52","blobGasUsed":"0x0","excessBlobGas":"0x0","extraData":"0x"},"transactions":[],"ommers":[]},{"header":{"parentHash":"0x0000000000000000000000000000000000000000000000000000000000000000","sha3Uncles":"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347","miner":"0x0000000000000000000000000000000000000000","stateRoot":"0x0000000000000000000000000000000000000000000000000000000000000000","transactionsRoot":"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421","receiptsRoot":"0x0000000000000000000000000000000000000000000000000000000000000000","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","difficulty":"0x0","number":"0x0","gasLimit":"0x1c9c380","gasUsed":"0x0","timestamp":"0x66b200c9","mixHash":"0x0000000000000000000000000000000000000000000000000000000000000000","nonce":"0x0000000000000000","baseFeePerGas":"0x3b9aca00","blobGasUsed":"0x0","excessBlobGas":"0x0","extraData":"0x"},"transactions":[],"ommers":[]},{"header":{"parentHash":"0xf6930be4847cac5017bbcbec2756eed19f36b4196526a98a88e311c296e3a9be","sha3Uncles":"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347","miner":"0x0000000000000000000000000000000000000000","stateRoot":"0x29aa352e71b139e83b397bdd3dcf9b65d74770edaf3a9624d0dbc4f96f868680","transactionsRoot":"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421","receiptsRoot":"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","difficulty":"0x0","number":"0x1","gasLimit":"0x1c9c380","gasUsed":"0x0","timestamp":"0x66b200cc","mixHash":"0x0000000000000000000000000000000000000000000000000000000000000000","nonce":"0x0000000000000000","baseFeePerGas":"0x200d75e8","blobGasUsed":"0x0","excessBlobGas":"0x0","extraData":"0x"},"transactions":[],"ommers":[]},{"header":{"parentHash":"0x08abe6e453727534d8dd708843a7522b7d500338bdfe2402ca105dcdb05eebe9","sha3Uncles":"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347","miner":"0x0000000000000000000000000000000000000000","stateRoot":"0xb6003e7ba07a15a9e35f63daa484728ec4ceeded0c4d10ac1b04e9552d412b3c","transactionsRoot":"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421","receiptsRoot":"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","difficulty":"0x0","number":"0x4","gasLimit":"0x1c9c380","gasUsed":"0x0","timestamp":"0x66b200ca","mixHash":"0x0000000000000000000000000000000000000000000000000000000000000000","nonce":"0x0000000000000000","baseFeePerGas":"0x1592fbf9","blobGasUsed":"0x0","excessBlobGas":"0x0","extraData":"0x"},"transactions":[],"ommers":[]},{"header":{"parentHash":"0x149d41e3b89d8324cef3feff98ef308e97bafe8745cc8461c60172bc7d4c44ba","sha3Uncles":"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347","miner":"0x0000000000000000000000000000000000000000","stateRoot":"0x510f2275449c013534a25ad0b13c867caf720947b68bcbcd4863f7b172a5d023","transactionsRoot":"0x0b44110186e52ff0ceb6b0776ca2992c94144a4ed712eef65ea038260ef0fcc7","receiptsRoot":"0xc2823b8eb4730d9f2657137cc2ddc2c4f22ab68e0ab826236cf6a1551ca2b3a5","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","difficulty":"0x0","number":"0x2","gasLimit":"0x1c9c380","gasUsed":"0xe61f9","timestamp":"0x66b200cb","mixHash":"0x0000000000000000000000000000000000000000000000000000000000000000","nonce":"0x0000000000000000","baseFeePerGas":"0x342770c0","blobGasUsed":"0x0","excessBlobGas":"0x0","extraData":"0x"},"transactions":[{"EIP1559":{"chainId":"0x343a","nonce":"0x0","gas":"0xe94d1","maxFeePerGas":"0x83215600","maxPriorityFeePerGas":"0x3b9aca00","to":"0x4e59b44847b379578588920ca78fbf26c0b4956c","value":"0x0","accessList":[],"input":"0x4786e4342646b3ba97c1790b6cf5a55087a36240b22570f5d3a5d6bcc929d93b608060405234801561001057600080fd5b5060008061002661006d60201b61081b1760201c565b60000160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555050610141565b60008060405160200161007f90610121565b6040516020818303038152906040528051906020012090508091505090565b600082825260208201905092915050565b7f696f2e73796e7468657469782e636f72652d636f6e7472616374732e4f776e6160008201527f626c650000000000000000000000000000000000000000000000000000000000602082015250565b600061010b60238361009e565b9150610116826100af565b604082019050919050565b6000602082019050818103600083015261013a816100fe565b9050919050565b611000806101506000396000f3fe608060405234801561001057600080fd5b50600436106100885760003560e01c806379ba50971161005b57806379ba5097146100ed5780638da5cb5b146100f7578063aaf10f4214610115578063c7f62cda1461013357610088565b80631627540c1461008d5780633659cfe6146100a957806353a47bb7146100c5578063718fe928146100e3575b600080fd5b6100a760048036038101906100a29190610d25565b61014f565b005b6100c360048036038101906100be9190610d25565b6102d0565b005b6100cd6102e4565b6040516100da9190610d61565b60405180910390f35b6100eb610317565b005b6100f56103fe565b005b6100ff61058b565b60405161010c9190610d61565b60405180910390f35b61011d6105be565b60405161012a9190610d61565b60405180910390f35b61014d60048036038101906101489190610d25565b6105f1565b005b61015761084c565b600061016161081b565b9050600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff16036101c9576040517fd92e233d00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b8060010160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1603610252576040517fa88ee57700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b818160010160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055507f906a1c6bd7e3091ea86693dd029a831c19049ce77f1dce2ce0bab1cacbabce22826040516102c49190610d61565b60405180910390a15050565b6102d861084c565b6102e1816108c5565b50565b60006102ee61081b565b60010160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16905090565b600061032161081b565b90503373ffffffffffffffffffffffffffffffffffffffff168160010160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16146103b757336040517fa0e5a0d70000000000000000000000000000000000000000000000000000000081526004016103ae9190610d61565b60405180910390fd5b60008160010160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555050565b600061040861081b565b905060008160010160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1690508073ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16146104a357336040517fa0e5a0d700000000000000000000000000000000000000000000000000000000815260040161049a9190610d61565b60405180910390fd5b7fb532073b38c83145e3e5135377a08bf9aab55bc0fd7c1179cd4fb995d2a5159c8260000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16826040516104f8929190610d7c565b60405180910390a1808260000160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555060008260010160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055505050565b600061059561081b565b60000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16905090565b60006105c8610b05565b60000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16905090565b60006105fb610b05565b905060018160000160146101000a81548160ff02191690831515021790555060008160000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff169050828260000160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555060008373ffffffffffffffffffffffffffffffffffffffff163073ffffffffffffffffffffffffffffffffffffffff16633659cfe6846040516024016106cc9190610d61565b604051602081830303815290604052915060e01b6020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff838183161783525050505060405161071b9190610e16565b600060405180830381855af49150503d8060008114610756576040519150601f19603f3d011682016040523d82523d6000602084013e61075b565b606091505b505090508015806107c357508173ffffffffffffffffffffffffffffffffffffffff16610786610b05565b60000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1614155b156107fa576040517fa1cfa5a800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60008360000160146101000a81548160ff0219169083151502179055600080fd5b60008060405160200161082d90610eb0565b6040516020818303038152906040528051906020012090508091505090565b610854610b36565b73ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16146108c357336040517f8e4a23d60000000000000000000000000000000000000000000000000000000081526004016108ba9190610d61565b60405180910390fd5b565b600073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff160361092b576040517fd92e233d00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b61093481610b69565b61097557806040517f8a8b41ec00000000000000000000000000000000000000000000000000000000815260040161096c9190610d61565b60405180910390fd5b600061097f610b05565b90508060000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1603610a0a576040517fa88ee57700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b8060000160149054906101000a900460ff16158015610a2e5750610a2d82610b7c565b5b15610a7057816040517f15504301000000000000000000000000000000000000000000000000000000008152600401610a679190610d61565b60405180910390fd5b818160000160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055503073ffffffffffffffffffffffffffffffffffffffff167f5d611f318680d00598bb735d61bacf0c514c6b50e1e5ad30040a4df2b12791c783604051610af99190610d61565b60405180910390a25050565b600080604051602001610b1790610f42565b6040516020818303038152906040528051906020012090508091505090565b6000610b4061081b565b60000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16905090565b600080823b905060008111915050919050565b60008060003073ffffffffffffffffffffffffffffffffffffffff163073ffffffffffffffffffffffffffffffffffffffff1663c7f62cda86604051602401610bc59190610d61565b604051602081830303815290604052915060e01b6020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff8381831617835250505050604051610c149190610e16565b600060405180830381855af49150503d8060008114610c4f576040519150601f19603f3d011682016040523d82523d6000602084013e610c54565b606091505b509150915081158015610cb9575063a1cfa5a860e01b604051602001610c7a9190610faf565b6040516020818303038152906040528051906020012081604051602001610ca19190610e16565b60405160208183030381529060405280519060200120145b92505050919050565b600080fd5b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b6000610cf282610cc7565b9050919050565b610d0281610ce7565b8114610d0d57600080fd5b50565b600081359050610d1f81610cf9565b92915050565b600060208284031215610d3b57610d3a610cc2565b5b6000610d4984828501610d10565b91505092915050565b610d5b81610ce7565b82525050565b6000602082019050610d766000830184610d52565b92915050565b6000604082019050610d916000830185610d52565b610d9e6020830184610d52565b9392505050565b600081519050919050565b600081905092915050565b60005b83811015610dd9578082015181840152602081019050610dbe565b60008484015250505050565b6000610df082610da5565b610dfa8185610db0565b9350610e0a818560208601610dbb565b80840191505092915050565b6000610e228284610de5565b915081905092915050565b600082825260208201905092915050565b7f696f2e73796e7468657469782e636f72652d636f6e7472616374732e4f776e6160008201527f626c650000000000000000000000000000000000000000000000000000000000602082015250565b6000610e9a602383610e2d565b9150610ea582610e3e565b604082019050919050565b60006020820190508181036000830152610ec981610e8d565b9050919050565b7f696f2e73796e7468657469782e636f72652d636f6e7472616374732e50726f7860008201527f7900000000000000000000000000000000000000000000000000000000000000602082015250565b6000610f2c602183610e2d565b9150610f3782610ed0565b604082019050919050565b60006020820190508181036000830152610f5b81610f1f565b9050919050565b60007fffffffff0000000000000000000000000000000000000000000000000000000082169050919050565b6000819050919050565b610fa9610fa482610f62565b610f8e565b82525050565b6000610fbb8284610f98565b6004820191508190509291505056fea264697066735822122023a7c33d7b91dce35ffbcf8837693364ab22a3905d0fc00016833e5fac45ca2f64736f6c63430008110033","r":"0x1","s":"0x1","yParity":"0x0","hash":"0x4feae6769d748b4f0f7c9bf21d782236c88f13906789a3ec602961296e4c3e43"}}],"ommers":[]},{"header":{"parentHash":"0xb3535af5103fd1c2bbd6dc7ff23f0799037a6542c231ebcb85abd776560fa512","sha3Uncles":"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347","miner":"0x0000000000000000000000000000000000000000","stateRoot":"0x23d74fb99ff6e42cbb5c33f92b078e37be6af2b6092459b103ff7059a6517ebc","transactionsRoot":"0x9eab45eca206fe11c107ea985c7d02fcfa442836aea3e04ba11dc4df587d5aa6","receiptsRoot":"0xe25abcfa973db8c55f73292137c626430de130a382ad4466337fefb0f7c8fde0","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","difficulty":"0x0","number":"0x2","gasLimit":"0x1c9c380","gasUsed":"0x3ce3f","timestamp":"0x66b200cd","mixHash":"0x0000000000000000000000000000000000000000000000000000000000000000","nonce":"0x0000000000000000","baseFeePerGas":"0x1c0bc72b","blobGasUsed":"0x0","excessBlobGas":"0x0","extraData":"0x"},"transactions":[{"EIP1559":{"chainId":"0x343a","nonce":"0x0","gas":"0x3d8a8","maxFeePerGas":"0x6211577c","maxPriorityFeePerGas":"0x3b9aca00","to":"0x4e59b44847b379578588920ca78fbf26c0b4956c","value":"0x0","accessList":[],"input":"0x4786e4342646b3ba97c1790b6cf5a55087a36240b22570f5d3a5d6bcc929d93b608060405234801561001057600080fd5b5060405161068538038061068583398181016040528101906100329190610275565b818181600073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff160361009b576040517fd92e233d00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6100ae8161019d60201b61004f1760201c565b6100ef57806040517f8a8b41ec0000000000000000000000000000000000000000000000000000000081526004016100e691906102c4565b60405180910390fd5b806100fe6101b060201b60201c565b60000160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555050806101536101e160201b6100621760201c565b60000160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555050505050610414565b600080823b905060008111915050919050565b6000806040516020016101c290610362565b6040516020818303038152906040528051906020012090508091505090565b6000806040516020016101f3906103f4565b6040516020818303038152906040528051906020012090508091505090565b600080fd5b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b600061024282610217565b9050919050565b61025281610237565b811461025d57600080fd5b50565b60008151905061026f81610249565b92915050565b6000806040838503121561028c5761028b610212565b5b600061029a85828601610260565b92505060206102ab85828601610260565b9150509250929050565b6102be81610237565b82525050565b60006020820190506102d960008301846102b5565b92915050565b600082825260208201905092915050565b7f696f2e73796e7468657469782e636f72652d636f6e7472616374732e50726f7860008201527f7900000000000000000000000000000000000000000000000000000000000000602082015250565b600061034c6021836102df565b9150610357826102f0565b604082019050919050565b6000602082019050818103600083015261037b8161033f565b9050919050565b7f696f2e73796e7468657469782e636f72652d636f6e7472616374732e4f776e6160008201527f626c650000000000000000000000000000000000000000000000000000000000602082015250565b60006103de6023836102df565b91506103e982610382565b604082019050919050565b6000602082019050818103600083015261040d816103d1565b9050919050565b610262806104236000396000f3fe6080604052366100135761001161001d565b005b61001b61001d565b005b6000610027610093565b90503660008037600080366000845af43d6000803e806000811461004a573d6000f35b3d6000fd5b600080823b905060008111915050919050565b6000806040516020016100749061017a565b6040516020818303038152906040528051906020012090508091505090565b600061009d6100c6565b60000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16905090565b6000806040516020016100d89061020c565b6040516020818303038152906040528051906020012090508091505090565b600082825260208201905092915050565b7f696f2e73796e7468657469782e636f72652d636f6e7472616374732e4f776e6160008201527f626c650000000000000000000000000000000000000000000000000000000000602082015250565b60006101646023836100f7565b915061016f82610108565b604082019050919050565b6000602082019050818103600083015261019381610157565b9050919050565b7f696f2e73796e7468657469782e636f72652d636f6e7472616374732e50726f7860008201527f7900000000000000000000000000000000000000000000000000000000000000602082015250565b60006101f66021836100f7565b91506102018261019a565b604082019050919050565b60006020820190508181036000830152610225816101e9565b905091905056fea2646970667358221220800da1f73cebd5e4afa07496d9bca6b6c4f526bdd3f4014ec15c70fe3a1c441364736f6c6343000811003300000000000000000000000047d08dad17ccb558b3ea74b1a0e73a9cc804a9dc000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb92266","r":"0x1","s":"0x1","yParity":"0x0","hash":"0xb6794d5c7abed6f91d447e8efb72ef2580595a6d7c8dee57ba1dbb330970146a"}}],"ommers":[]},{"header":{"parentHash":"0x08abe6e453727534d8dd708843a7522b7d500338bdfe2402ca105dcdb05eebe9","sha3Uncles":"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347","miner":"0x0000000000000000000000000000000000000000","stateRoot":"0x510f2275449c013534a25ad0b13c867caf720947b68bcbcd4863f7b172a5d023","transactionsRoot":"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421","receiptsRoot":"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","difficulty":"0x0","number":"0x3","gasLimit":"0x1c9c380","gasUsed":"0x0","timestamp":"0x66b200ca","mixHash":"0x0000000000000000000000000000000000000000000000000000000000000000","nonce":"0x0000000000000000","baseFeePerGas":"0x29dd5614","blobGasUsed":"0x0","excessBlobGas":"0x0","extraData":"0x"},"transactions":[],"ommers":[]}]} \ No newline at end of file +{"block":{"number":5,"beneficiary":"0x0000000000000000000000000000000000000000","timestamp":1722941643,"gas_limit":30000000,"basefee":316710010,"difficulty":"0x0","prevrandao":"0xe7ef87fc7c2090741a6749a087e4ca8092cb4d07136008799e4ebeac3b69e34a","blob_excess_gas_and_price":{"excess_blob_gas":0,"blob_gasprice":1}},"accounts":{"0x0000000000000000000000000000000000000000":{"nonce":0,"balance":"0x1088aa62285a00","code":"0x","storage":{}},"0x108f53faf774d7c4c56f5bce9ca6e605ce8aeadd":{"nonce":1,"balance":"0x0","code":"0x6080604052600080357fffffffff0000000000000000000000000000000000000000000000000000000016905060008160e01c610251565b60006379ba509782101561015e5781631627540c811461009857632a952b2d81146100b457633659cfe681146100d0576350c946fe81146100ec576353a47bb781146101085763625ca21c81146101245763718fe928811461014057610158565b7347d08dad17ccb558b3ea74b1a0e73a9cc804a9dc9150610158565b738138ef7cf908021d117e542120b7a390650161079150610158565b7347d08dad17ccb558b3ea74b1a0e73a9cc804a9dc9150610158565b738138ef7cf908021d117e542120b7a390650161079150610158565b7347d08dad17ccb558b3ea74b1a0e73a9cc804a9dc9150610158565b738138ef7cf908021d117e542120b7a390650161079150610158565b7347d08dad17ccb558b3ea74b1a0e73a9cc804a9dc91505b5061024c565b816379ba509781146101a657638da5cb5b81146101c25763aaf10f4281146101de5763c7f62cda81146101fa5763daa250be81146102165763deba1b9881146102325761024a565b7347d08dad17ccb558b3ea74b1a0e73a9cc804a9dc915061024a565b7347d08dad17ccb558b3ea74b1a0e73a9cc804a9dc915061024a565b7347d08dad17ccb558b3ea74b1a0e73a9cc804a9dc915061024a565b7347d08dad17ccb558b3ea74b1a0e73a9cc804a9dc915061024a565b738138ef7cf908021d117e542120b7a39065016107915061024a565b738138ef7cf908021d117e542120b7a3906501610791505b505b919050565b61025a81610037565b915050600073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff16036102ce57816040517fc2a825f50000000000000000000000000000000000000000000000000000000081526004016102c5919061032f565b60405180910390fd5b3660008037600080366000845af43d6000803e80600081146102ef573d6000f35b3d6000fd5b60007fffffffff0000000000000000000000000000000000000000000000000000000082169050919050565b610329816102f4565b82525050565b60006020820190506103446000830184610320565b9291505056fea264697066735822122017a4b7fdaaab3897a7b47abaed8d2ee92d558883d3bb2a8454f9601b2ab2c3db64736f6c63430008150033","storage":{}},"0x14dc79964da2c08b23698b3d3cc7ca32193d9955":{"nonce":0,"balance":"0x21e19e0c9bab2400000","code":"0x","storage":{}},"0x15d34aaf54267db7d7c367839aaf71a00a2c6a65":{"nonce":0,"balance":"0x21e19e0c9bab2400000","code":"0x","storage":{}},"0x19ba1fac55eea44d12a01372a8eb0c2ebbf9ca21":{"nonce":1,"balance":"0x21e19df7c2963f0ac6b","code":"0x","storage":{}},"0x19c6ab860dbe2bc433574193a4409770a8748bf6":{"nonce":1,"balance":"0x21e19df8da6b7bdc410","code":"0x","storage":{}},"0x23618e81e3f5cdf7f54c3d65f7fbc0abf5b21e8f":{"nonce":0,"balance":"0x21e19e0c9bab2400000","code":"0x","storage":{}},"0x3c44cdddb6a900fa2b585dd299e03d12fa4293bc":{"nonce":0,"balance":"0x21e19e0c9bab2400000","code":"0x","storage":{}},"0x40567ec443c1d1872af5155755ac3803cc3fe61e":{"nonce":1,"balance":"0x21e19da82562f921b40","code":"0x","storage":{}},"0x47d08dad17ccb558b3ea74b1a0e73a9cc804a9dc":{"nonce":1,"balance":"0x0","code":"0x608060405234801561001057600080fd5b50600436106100885760003560e01c806379ba50971161005b57806379ba5097146100ed5780638da5cb5b146100f7578063aaf10f4214610115578063c7f62cda1461013357610088565b80631627540c1461008d5780633659cfe6146100a957806353a47bb7146100c5578063718fe928146100e3575b600080fd5b6100a760048036038101906100a29190610d25565b61014f565b005b6100c360048036038101906100be9190610d25565b6102d0565b005b6100cd6102e4565b6040516100da9190610d61565b60405180910390f35b6100eb610317565b005b6100f56103fe565b005b6100ff61058b565b60405161010c9190610d61565b60405180910390f35b61011d6105be565b60405161012a9190610d61565b60405180910390f35b61014d60048036038101906101489190610d25565b6105f1565b005b61015761084c565b600061016161081b565b9050600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff16036101c9576040517fd92e233d00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b8060010160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1603610252576040517fa88ee57700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b818160010160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055507f906a1c6bd7e3091ea86693dd029a831c19049ce77f1dce2ce0bab1cacbabce22826040516102c49190610d61565b60405180910390a15050565b6102d861084c565b6102e1816108c5565b50565b60006102ee61081b565b60010160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16905090565b600061032161081b565b90503373ffffffffffffffffffffffffffffffffffffffff168160010160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16146103b757336040517fa0e5a0d70000000000000000000000000000000000000000000000000000000081526004016103ae9190610d61565b60405180910390fd5b60008160010160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555050565b600061040861081b565b905060008160010160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1690508073ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16146104a357336040517fa0e5a0d700000000000000000000000000000000000000000000000000000000815260040161049a9190610d61565b60405180910390fd5b7fb532073b38c83145e3e5135377a08bf9aab55bc0fd7c1179cd4fb995d2a5159c8260000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16826040516104f8929190610d7c565b60405180910390a1808260000160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555060008260010160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055505050565b600061059561081b565b60000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16905090565b60006105c8610b05565b60000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16905090565b60006105fb610b05565b905060018160000160146101000a81548160ff02191690831515021790555060008160000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff169050828260000160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555060008373ffffffffffffffffffffffffffffffffffffffff163073ffffffffffffffffffffffffffffffffffffffff16633659cfe6846040516024016106cc9190610d61565b604051602081830303815290604052915060e01b6020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff838183161783525050505060405161071b9190610e16565b600060405180830381855af49150503d8060008114610756576040519150601f19603f3d011682016040523d82523d6000602084013e61075b565b606091505b505090508015806107c357508173ffffffffffffffffffffffffffffffffffffffff16610786610b05565b60000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1614155b156107fa576040517fa1cfa5a800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60008360000160146101000a81548160ff0219169083151502179055600080fd5b60008060405160200161082d90610eb0565b6040516020818303038152906040528051906020012090508091505090565b610854610b36565b73ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16146108c357336040517f8e4a23d60000000000000000000000000000000000000000000000000000000081526004016108ba9190610d61565b60405180910390fd5b565b600073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff160361092b576040517fd92e233d00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b61093481610b69565b61097557806040517f8a8b41ec00000000000000000000000000000000000000000000000000000000815260040161096c9190610d61565b60405180910390fd5b600061097f610b05565b90508060000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1603610a0a576040517fa88ee57700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b8060000160149054906101000a900460ff16158015610a2e5750610a2d82610b7c565b5b15610a7057816040517f15504301000000000000000000000000000000000000000000000000000000008152600401610a679190610d61565b60405180910390fd5b818160000160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055503073ffffffffffffffffffffffffffffffffffffffff167f5d611f318680d00598bb735d61bacf0c514c6b50e1e5ad30040a4df2b12791c783604051610af99190610d61565b60405180910390a25050565b600080604051602001610b1790610f42565b6040516020818303038152906040528051906020012090508091505090565b6000610b4061081b565b60000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16905090565b600080823b905060008111915050919050565b60008060003073ffffffffffffffffffffffffffffffffffffffff163073ffffffffffffffffffffffffffffffffffffffff1663c7f62cda86604051602401610bc59190610d61565b604051602081830303815290604052915060e01b6020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff8381831617835250505050604051610c149190610e16565b600060405180830381855af49150503d8060008114610c4f576040519150601f19603f3d011682016040523d82523d6000602084013e610c54565b606091505b509150915081158015610cb9575063a1cfa5a860e01b604051602001610c7a9190610faf565b6040516020818303038152906040528051906020012081604051602001610ca19190610e16565b60405160208183030381529060405280519060200120145b92505050919050565b600080fd5b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b6000610cf282610cc7565b9050919050565b610d0281610ce7565b8114610d0d57600080fd5b50565b600081359050610d1f81610cf9565b92915050565b600060208284031215610d3b57610d3a610cc2565b5b6000610d4984828501610d10565b91505092915050565b610d5b81610ce7565b82525050565b6000602082019050610d766000830184610d52565b92915050565b6000604082019050610d916000830185610d52565b610d9e6020830184610d52565b9392505050565b600081519050919050565b600081905092915050565b60005b83811015610dd9578082015181840152602081019050610dbe565b60008484015250505050565b6000610df082610da5565b610dfa8185610db0565b9350610e0a818560208601610dbb565b80840191505092915050565b6000610e228284610de5565b915081905092915050565b600082825260208201905092915050565b7f696f2e73796e7468657469782e636f72652d636f6e7472616374732e4f776e6160008201527f626c650000000000000000000000000000000000000000000000000000000000602082015250565b6000610e9a602383610e2d565b9150610ea582610e3e565b604082019050919050565b60006020820190508181036000830152610ec981610e8d565b9050919050565b7f696f2e73796e7468657469782e636f72652d636f6e7472616374732e50726f7860008201527f7900000000000000000000000000000000000000000000000000000000000000602082015250565b6000610f2c602183610e2d565b9150610f3782610ed0565b604082019050919050565b60006020820190508181036000830152610f5b81610f1f565b9050919050565b60007fffffffff0000000000000000000000000000000000000000000000000000000082169050919050565b6000819050919050565b610fa9610fa482610f62565b610f8e565b82525050565b6000610fbb8284610f98565b6004820191508190509291505056fea264697066735822122023a7c33d7b91dce35ffbcf8837693364ab22a3905d0fc00016833e5fac45ca2f64736f6c63430008110033","storage":{"0x5c7865864a2a990d80b5bb5c40e7b73a029960dc711fbb56120dfab976e92ea3":"0x0"}},"0x4e59b44847b379578588920ca78fbf26c0b4956c":{"nonce":2,"balance":"0x0","code":"0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe03601600081602082378035828234f58015156039578182fd5b8082525050506014600cf3","storage":{}},"0x70997970c51812dc3a010c7d01b50e0d17dc79c8":{"nonce":0,"balance":"0x21e19e0c9bab2400000","code":"0x","storage":{}},"0x8138ef7cf908021d117e542120b7a39065016107":{"nonce":1,"balance":"0x0","code":"0x608060405234801561001057600080fd5b50600436106100575760003560e01c80632a952b2d1461005c57806350c946fe14610085578063625ca21c146100a5578063daa250be146100c6578063deba1b98146100d9575b600080fd5b61006f61006a366004613a63565b6100ec565b60405161007c9190613a7c565b60405180910390f35b610098610093366004613a63565b61011c565b60405161007c9190613b21565b6100b86100b3366004613c92565b610276565b60405190815260200161007c565b61006f6100d4366004613d5f565b6102bb565b6100b86100e7366004613c92565b6102d6565b6100f46139e4565b6040805160008082526020820190815281830190925261011691849190610310565b92915050565b6101416040805160608101909152806000815260200160608152602001606081525090565b61014a82610ab6565b60408051606081019091528154909190829060ff16600981111561017057610170613aa7565b600981111561018157610181613aa7565b815260200160018201805461019590613dc2565b80601f01602080910402602001604051908101604052809291908181526020018280546101c190613dc2565b801561020e5780601f106101e35761010080835404028352916020019161020e565b820191906000526020600020905b8154815290600101906020018083116101f157829003601f168201915b505050505081526020016002820180548060200260200160405190810160405280929190818152602001828054801561026657602002820191906000526020600020905b815481526020019060010190808311610252575b5050505050815250509050919050565b600080604051806060016040528086600981111561029657610296613aa7565b81526020018581526020018481525090506102b081610ac1565b9150505b9392505050565b6102c36139e4565b6102ce848484610310565b949350505050565b60008060405180606001604052808660098111156102f6576102f6613aa7565b81526020018581526020018481525090506102b081610acc565b6103186139e4565b81518351146103a05760408051634bab873760e11b81526004810191909152600d60448201526c72756e74696d6556616c75657360981b606482015260806024820152602260848201527f6d7573742062652073616d65206c656e6774682061732072756e74696d654b6560a482015261797360f01b60c482015260e4015b60405180910390fd5b60006103ab85610c26565b805490915060ff1660018160098111156103c7576103c7613aa7565b036104755761046c6103da838787610c84565b8360010180546103e990613dc2565b80601f016020809104026020016040519081016040528092919081815260200182805461041590613dc2565b80156104625780601f1061043757610100808354040283529160200191610462565b820191906000526020600020905b81548152906001019060200180831161044557829003601f168201915b5050505050610d46565b925050506102b4565b600281600981111561048957610489613aa7565b036105305761046c61049c838787610c84565b8360010180546104ab90613dc2565b80601f01602080910402602001604051908101604052809291908181526020018280546104d790613dc2565b80156105245780601f106104f957610100808354040283529160200191610524565b820191906000526020600020905b81548152906001019060200180831161050757829003601f168201915b50505050508787610ebb565b600381600981111561054457610544613aa7565b036105de5761046c82600101805461055b90613dc2565b80601f016020809104026020016040519081016040528092919081815260200182805461058790613dc2565b80156105d45780601f106105a9576101008083540402835291602001916105d4565b820191906000526020600020905b8154815290600101906020018083116105b757829003601f168201915b5050505050610f59565b60048160098111156105f2576105f2613aa7565b0361068c5761046c82600101805461060990613dc2565b80601f016020809104026020016040519081016040528092919081815260200182805461063590613dc2565b80156106825780601f1061065757610100808354040283529160200191610682565b820191906000526020600020905b81548152906001019060200180831161066557829003601f168201915b5050505050611087565b60058160098111156106a0576106a0613aa7565b0361073a5761046c8260010180546106b790613dc2565b80601f01602080910402602001604051908101604052809291908181526020018280546106e390613dc2565b80156107305780601f1061070557610100808354040283529160200191610730565b820191906000526020600020905b81548152906001019060200180831161071357829003601f168201915b505050505061131e565b600981600981111561074e5761074e613aa7565b036107ea5761046c82600101805461076590613dc2565b80601f016020809104026020016040519081016040528092919081815260200182805461079190613dc2565b80156107de5780601f106107b3576101008083540402835291602001916107de565b820191906000526020600020905b8154815290600101906020018083116107c157829003601f168201915b505050505086866114b5565b60068160098111156107fe576107fe613aa7565b036108a35761046c610811838787610c84565b83600101805461082090613dc2565b80601f016020809104026020016040519081016040528092919081815260200182805461084c90613dc2565b80156108995780601f1061086e57610100808354040283529160200191610899565b820191906000526020600020905b81548152906001019060200180831161087c57829003601f168201915b50505050506115c7565b60078160098111156108b7576108b7613aa7565b036109ec576040805160608101909152825461046c91908490829060ff1660098111156108e6576108e6613aa7565b60098111156108f7576108f7613aa7565b815260200160018201805461090b90613dc2565b80601f016020809104026020016040519081016040528092919081815260200182805461093790613dc2565b80156109845780601f1061095957610100808354040283529160200191610984565b820191906000526020600020905b81548152906001019060200180831161096757829003601f168201915b50505050508152602001600282018054806020026020016040519081016040528092919081815260200182805480156109dc57602002820191906000526020600020905b8154815260200190600101908083116109c8575b5050505050815250508686611728565b6008816009811115610a0057610a00613aa7565b03610a9a5761046c826001018054610a1790613dc2565b80601f0160208091040260200160405190810160405280929190818152602001828054610a4390613dc2565b8015610a905780601f10610a6557610100808354040283529160200191610a90565b820191906000526020600020905b815481529060010190602001808311610a7357829003601f168201915b50505050506118a5565b6040516323a9bbc960e01b815260048101879052602401610397565b600061011682610c26565b6000610116826118ea565b6000610ad782610ac1565b9050610ae28161192a565b15610b35577fcb64985827770858ec421ad26da7e558c757541643036ce44d6b4eb9e8e5dc5e81836000015184602001518560400151604051610b289493929190613e32565b60405180910390a1919050565b610b3e82611a8c565b610b5d578160405163382bbbc960e11b81526004016103979190613b21565b60005b826040015151811015610bd957610b9383604001518281518110610b8657610b86613e6a565b602002602001015161192a565b610bd15782604001518181518110610bad57610bad613e6a565b6020026020010151604051632f19f96160e11b815260040161039791815260200190565b600101610b60565b50610be382611c31565b8351602085015160408087015190519395507fcb64985827770858ec421ad26da7e558c757541643036ce44d6b4eb9e8e5dc5e9450610b28938693929190613e32565b604080516020808201839052606082018190527f696f2e73796e7468657469782e6f7261636c652d6d616e616765722e4e6f6465608080840191909152828401949094528251808303909401845260a0909101909152815191012090565b600283015460609067ffffffffffffffff811115610ca457610ca4613b9a565b604051908082528060200260200182016040528015610cdd57816020015b610cca6139e4565b815260200190600190039081610cc25790505b50905060005b6002850154811015610d3e57610d19856002018281548110610d0757610d07613e6a565b90600052602060002001548585610310565b828281518110610d2b57610d2b613e6a565b6020908102919091010152600101610ce3565b509392505050565b610d4e6139e4565b600082806020019051810190610d649190613e80565b90506000816008811115610d7a57610d7a613aa7565b03610d9057610d8884611ca5565b915050610116565b6001816008811115610da457610da4613aa7565b03610db257610d8884611d0d565b6002816008811115610dc657610dc6613aa7565b03610dd457610d8884611d90565b6003816008811115610de857610de8613aa7565b03610df657610d8884611e13565b6004816008811115610e0a57610e0a613aa7565b03610e1857610d8884611ec9565b6005816008811115610e2c57610e2c613aa7565b03610e3a57610d8884612009565b6006816008811115610e4e57610e4e613aa7565b03610e5c57610d88846120e4565b6007816008811115610e7057610e70613aa7565b03610e7e57610d888461220c565b6008816008811115610e9257610e92613aa7565b03610ea057610d88846122ce565b80604051631be413d360e11b81526004016103979190613ea1565b610ec36139e4565b600084806020019051810190610ed99190613ed3565b604051631ecba7c360e31b81529091506001600160a01b0382169063f65d3e1890610f0e908990899089908990600401613ef0565b608060405180830381865afa158015610f2b573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610f4f9190613f91565b9695505050505050565b610f616139e4565b600080600084806020019051810190610f7a9190613fe8565b92509250925060008390506000806000836001600160a01b031663feaf968c6040518163ffffffff1660e01b815260040160a060405180830381865afa158015610fc8573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610fec9190614041565b509350509250925060008660001461100f5761100a8585858a6123c7565b611011565b825b905060128660ff161161103b5761103661102f60ff881660126140a7565b82906124c2565b611053565b61105361104c601260ff89166140a7565b82906124dc565b9050604051806080016040528082815260200183815260200160008152602001600081525098505050505050505050919050565b61108f6139e4565b600080600080600080878060200190518101906110ac91906140ba565b604080516002808252606082018352979d50959b50939950919750955093506000929060208301908036833701905050905081816000815181106110f2576110f2613e6a565b602002602001019063ffffffff16908163ffffffff168152505060008160018151811061112157611121613e6a565b63ffffffff9092166020928302919091019091015260405163883bdbfd60e01b81526000906001600160a01b0385169063883bdbfd90611165908590600401614143565b600060405180830381865afa158015611182573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f191682016040526111aa91908101906141f5565b5090506000816000815181106111c2576111c2613e6a565b6020026020010151826001815181106111dd576111dd613e6a565b60200260200101516111ef91906142c1565b9050600061121761120563ffffffff87166124f6565b61120f9084614304565b60060b61252d565b905060008260060b12801561124c575061123b63ffffffff8616612569565b612569565b8260060b6112499190614342565b15155b1561125f578061125b81614356565b9150505b600061126d6012600a61445d565b9050600061128061123684848f8f612593565b905060006112908a60ff16612569565b61129c8c60ff16612569565b6112a6919061446c565b905060008082136112d1576112cc6112c56112c084614493565b612686565b84906124dc565b6112e4565b6112e46112dd83612686565b84906124c2565b905060405180608001604052808281526020014281526020016000815260200160008152509e505050505050505050505050505050919050565b6113266139e4565b60008060008480602001905181019061133f91906144bf565b91945092509050826000826113bc576040516396834ad360e01b8152600481018590526001600160a01b038316906396834ad390602401608060405180830381865afa158015611393573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906113b791906144f5565b611425565b604051639474f45b60e01b8152600481018590526001600160a01b03831690639474f45b90602401608060405180830381865afa158015611401573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061142591906144f5565b90506000816040015160030b601261143d919061456f565b90506000808213611467576114626114576112c084614493565b845160070b906124dc565b61147e565b61147e61147383612686565b845160070b906124c2565b9050604051806080016040528082815260200184606001518152602001600081526020016000815250975050505050505050919050565b6114bd6139e4565b6000806000868060200190518101906114d69190614597565b92509250925060005b8651811015611545578681815181106114fa576114fa613e6a565b6020026020010151717374616c656e657373546f6c6572616e636560701b0361153d5785818151811061152f5761152f613e6a565b602002602001015160001c91505b6001016114df565b5060408051600180825281830190925260009160208083019080368337019050509050828160008151811061157c5761157c613e6a565b602002602001018181525050836001838360405160200161159f939291906145ce565b60408051601f198184030181529082905263cf2cabdf60e01b82526103979291600401614603565b6115cf6139e4565b6000828060200190518101906115e59190614627565b90506000846000815181106115fc576115fc613e6a565b602002602001015160000151905060008560018151811061161f5761161f613e6a565b6020026020010151600001519050808214611702576000611653601261164d611648858761446c565b6126a9565b906124c2565b905082158061167b5750611666836126a9565b6116709082614640565b61167985612569565b125b15611700576002875111156116b0578660028151811061169d5761169d613e6a565b6020026020010151945050505050610116565b826000036116d15760405163014cc07160e01b815260040160405180910390fd5b6116da836126a9565b6116e49082614640565b60405163dcac091960e01b815260040161039791815260200190565b505b8560008151811061171557611715613e6a565b6020026020010151935050505092915050565b6117306139e4565b6000846020015180602001905181019061174a9190614627565b905060005b84518110156117bc5784818151811061176a5761176a613e6a565b6020026020010151717374616c656e657373546f6c6572616e636560701b036117b4576117ad8482815181106117a2576117a2613e6a565b602002602001015190565b91506117bc565b60010161174f565b50600085604001516000815181106117d6576117d6613e6a565b6020026020010151905060006117ed828787610310565b60208101519091506117ff84426140a7565b1161180e5792506102b4915050565b86604001515160010361187157866040015160008151811061183257611832613e6a565b602002602001015181600001518260200151604051631808066560e21b8152600401610397939291909283526020830191909152604082015260600190565b61189a876040015160018151811061188b5761188b613e6a565b60200260200101518787610310565b979650505050505050565b6118ad6139e4565b6040518060800160405280838060200190518101906118cc9190614627565b81526020014281526020016000815260200160008152509050919050565b600081600001518260200151836040015160405160200161190d9392919061466e565b604051602081830303815290604052805190602001209050919050565b60008061193683610c26565b60408051606081019091528154909190829060ff16600981111561195c5761195c613aa7565b600981111561196d5761196d613aa7565b815260200160018201805461198190613dc2565b80601f01602080910402602001604051908101604052809291908181526020018280546119ad90613dc2565b80156119fa5780601f106119cf576101008083540402835291602001916119fa565b820191906000526020600020905b8154815290600101906020018083116119dd57829003601f168201915b5050505050815260200160028201805480602002602001604051908101604052809291908181526020018280548015611a5257602002820191906000526020600020905b815481526020019060010190808311611a3e575b505050505081525050905060006009811115611a7057611a70613aa7565b81516009811115611a8357611a83613aa7565b14159392505050565b6000600182516009811115611aa357611aa3613aa7565b1480611ac15750600682516009811115611abf57611abf613aa7565b145b80611ade5750600782516009811115611adc57611adc613aa7565b145b15611aee57611aec826126c1565b505b600182516009811115611b0357611b03613aa7565b03611b11576101168261284a565b600282516009811115611b2657611b26613aa7565b03611b3457610116826128a5565b600382516009811115611b4957611b49613aa7565b03611b575761011682612973565b600482516009811115611b6c57611b6c613aa7565b03611b7a5761011682612aae565b600582516009811115611b8f57611b8f613aa7565b03611b9d5761011682612e92565b600982516009811115611bb257611bb2613aa7565b03611bc05761011682612fcb565b600682516009811115611bd557611bd5613aa7565b03611be3576101168261300e565b600782516009811115611bf857611bf8613aa7565b03611c065761011682613052565b600882516009811115611c1b57611c1b613aa7565b03611c295761011682613078565b506000919050565b600080611c3d836118ea565b9050611c4881610c26565b8351815491935090839060ff19166001836009811115611c6a57611c6a613aa7565b021790555060208301516001830190611c8390826146ed565b5060408301518051611c9f916002850191602090910190613a0c565b50915091565b611cad6139e4565b60005b8251811015611d07578160200151838281518110611cd057611cd0613e6a565b6020026020010151602001511115611cff57828181518110611cf457611cf4613e6a565b602002602001015191505b600101611cb0565b50919050565b611d156139e4565b81600081518110611d2857611d28613e6a565b602002602001015190506000600190505b8251811015611d07578160000151838281518110611d5957611d59613e6a565b6020026020010151600001511215611d8857828181518110611d7d57611d7d613e6a565b602002602001015191505b600101611d39565b611d986139e4565b81600081518110611dab57611dab613e6a565b602002602001015190506000600190505b8251811015611d07578160000151838281518110611ddc57611ddc613e6a565b6020026020010151600001511315611e0b57828181518110611e0057611e00613e6a565b602002602001015191505b600101611dbc565b611e1b6139e4565b60005b8251811015611e9557828181518110611e3957611e39613e6a565b60200260200101516000015182600001818151611e56919061456f565b9052508251839082908110611e6d57611e6d613e6a565b60200260200101516020015182602001818151611e8a91906147ad565b905250600101611e1e565b50611ea08251612569565b8151611eac9190614640565b815281516020820151611ebf91906147c0565b6020820152919050565b611ed16139e4565b611eed826000611ee86001865161123691906140a7565b6130a4565b60028251611efb91906147d4565b600003611fd65760408051600280825260608201909252600091816020015b611f226139e4565b815260200190600190039081611f1a57905050905082600160028551611f4891906147c0565b611f5291906140a7565b81518110611f6257611f62613e6a565b602002602001015181600081518110611f7d57611f7d613e6a565b60200260200101819052508260028451611f9791906147c0565b81518110611fa757611fa7613e6a565b602002602001015181600181518110611fc257611fc2613e6a565b60200260200101819052506102b481611e13565b8160028351611fe591906147c0565b81518110611ff557611ff5613e6a565b60200260200101519050919050565b919050565b6120116139e4565b8160008151811061202457612024613e6a565b60209081029190910101515181528151829060009061204557612045613e6a565b6020908102919091018101518101519082015260015b82518110156120d25782818151811061207657612076613e6a565b6020026020010151600001518260000181815161209391906147e8565b90525082518390829081106120aa576120aa613e6a565b602002602001015160200151826020018181516120c791906147ad565b90525060010161205b565b5081518160200151611ebf91906147c0565b6120ec6139e4565b816000815181106120ff576120ff613e6a565b60209081029190910101515181528151829060009061212057612120613e6a565b6020908102919091018101518101519082015260015b82518110156120d25782818151811061215157612151613e6a565b60200260200101516000015160000361219e5782818151811061217657612176613e6a565b6020026020010151600001516040516338ee04a760e01b815260040161039791815260200190565b8281815181106121b0576121b0613e6a565b602002602001015160000151826000018181516121cd9190614640565b90525082518390829081106121e4576121e4613e6a565b6020026020010151602001518260200181815161220191906147ad565b905250600101612136565b6122146139e4565b8160008151811061222757612227613e6a565b60209081029190910101515181528151829060009061224857612248613e6a565b6020908102919091018101518101519082015260015b82518110156120d25761229083828151811061227c5761227c613e6a565b602090810291909101015151835190613264565b825282518390829081106122a6576122a6613e6a565b602002602001015160200151826020018181516122c391906147ad565b90525060010161225e565b6122d66139e4565b816000815181106122e9576122e9613e6a565b60209081029190910101515181528151829060009061230a5761230a613e6a565b6020908102919091018101518101519082015260015b82518110156120d25782818151811061233b5761233b613e6a565b6020026020010151600001516000036123605782818151811061217657612176613e6a565b61238983828151811061237557612375613e6a565b602090810291909101015151835190613283565b8252825183908290811061239f5761239f613e6a565b602002602001015160200151826020018181516123bc91906147ad565b905250600101612320565b6000826001826123d785426140a7565b90505b69ffffffffffffffffffff8716156124a3576001600160a01b038816639a6fc8f561240489614818565b6040516001600160e01b031960e084901b16815269ffffffffffffffffffff8216600482015290995060240160a060405180830381865afa925050508015612469575060408051601f3d908101601f1916820190925261246691810190614041565b60015b156124a357858210156124805750505050506124a3565b61248a848961456f565b97508661249681614834565b97505050505050506123da565b6124ac82612569565b6124b69084614640565b98975050505050505050565b60006124d261123683600a61484d565b6102b490846147e8565b60006124ec61123683600a61484d565b6102b49084614640565b6000667fffffffffffff66ffffffffffffff83161115612529576040516329d2678160e21b815260040160405180910390fd5b5090565b6000627fffff19600683900b128061254b5750627fffff600683900b135b1561252957604051630d962f7960e21b815260040160405180910390fd5b60006001600160ff1b038211156125295760405163677c430560e11b815260040160405180910390fd5b60008061259f86613298565b90506fffffffffffffffffffffffffffffffff6001600160a01b0382161161261c5760006125d66001600160a01b03831680614859565b9050836001600160a01b0316856001600160a01b03161061260557612600600160c01b87836136cd565b612614565b6126148187600160c01b6136cd565b92505061267d565b600061263b6001600160a01b03831680680100000000000000006136cd565b9050836001600160a01b0316856001600160a01b03161061266a57612665600160801b87836136cd565b612679565b6126798187600160801b6136cd565b9250505b50949350505050565b6000808212156125295760405163029f024d60e31b815260040160405180910390fd5b600080821215612529576126bc82614493565b610116565b6000805b8260400151518110156128415760006126fa846040015183815181106126ed576126ed613e6a565b6020026020010151610ab6565b60408051606081019091528154909190829060ff16600981111561272057612720613aa7565b600981111561273157612731613aa7565b815260200160018201805461274590613dc2565b80601f016020809104026020016040519081016040528092919081815260200182805461277190613dc2565b80156127be5780601f10612793576101008083540402835291602001916127be565b820191906000526020600020905b8154815290600101906020018083116127a157829003601f168201915b505050505081526020016002820180548060200260200160405190810160405280929190818152602001828054801561281657602002820191906000526020600020905b815481526020019060010190808311612802575b505050505081525050905061282a81611a8c565b612838575060009392505050565b506001016126c5565b50600192915050565b60006002826040015151101561286257506000919050565b81602001515160201461287757506000919050565b600082602001518060200190518101906128919190614627565b905060088111156128415750600092915050565b6000602082602001515110156128bd57506000919050565b600082602001518060200190518101906128d79190613ed3565b90506128ea816306e7ea3960e21b6138e2565b6128f75750600092915050565b604051633b70a5bf60e21b81526001600160a01b0382169063edc296fc90612923908690600401613b21565b6020604051808303816000875af1158015612942573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906129669190614870565b6128415750600092915050565b6040810151516000901561298957506000919050565b81602001515160601461299e57506000919050565b60008083602001518060200190518101906129b99190613fe8565b92505091506000829050806001600160a01b031663feaf968c6040518163ffffffff1660e01b815260040160a060405180830381865afa158015612a01573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612a259190614041565b5050505050806001600160a01b031663313ce5676040518163ffffffff1660e01b8152600401602060405180830381865afa158015612a68573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612a8c919061488b565b60ff168260ff1614612aa357506000949350505050565b506001949350505050565b60408101515160009015612ac457506000919050565b81602001515160c014612ad957506000919050565b6000806000806000808760200151806020019051810190612afa91906140ba565b9550955095509550955095508360ff16866001600160a01b031663313ce5676040518163ffffffff1660e01b8152600401602060405180830381865afa158015612b48573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612b6c919061488b565b60ff1614612b8257506000979650505050505050565b8260ff16856001600160a01b031663313ce5676040518163ffffffff1660e01b8152600401602060405180830381865afa158015612bc4573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612be8919061488b565b60ff1614612bfe57506000979650505050505050565b6000826001600160a01b0316630dfe16816040518163ffffffff1660e01b8152600401602060405180830381865afa158015612c3e573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612c629190613ed3565b90506000836001600160a01b031663d21220a76040518163ffffffff1660e01b8152600401602060405180830381865afa158015612ca4573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612cc89190613ed3565b9050876001600160a01b0316826001600160a01b0316148015612cfc5750866001600160a01b0316816001600160a01b0316145b158015612d385750866001600160a01b0316826001600160a01b0316148015612d365750876001600160a01b0316816001600160a01b0316145b155b15612d4d575060009998505050505050505050565b60128660ff161180612d62575060128560ff16115b15612d77575060009998505050505050505050565b8263ffffffff16600003612d95575060009998505050505050505050565b6040805160028082526060820183526000926020830190803683370190505090508381600081518110612dca57612dca613e6a565b602002602001019063ffffffff16908163ffffffff1681525050600081600181518110612df957612df9613e6a565b63ffffffff9092166020928302919091019091015260405163883bdbfd60e01b81526001600160a01b0386169063883bdbfd90612e3a908490600401614143565b600060405180830381865afa158015612e57573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f19168201604052612e7f91908101906141f5565b5060019c9b505050505050505050505050565b60408101515160009015612ea857506000919050565b816020015151606014612ebd57506000919050565b60008060008460200151806020019051810190612eda91906144bf565b919450925090508281612f55576040516396834ad360e01b8152600481018490526001600160a01b038216906396834ad390602401608060405180830381865afa158015612f2c573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612f5091906144f5565b612fbe565b604051639474f45b60e01b8152600481018490526001600160a01b03821690639474f45b90602401608060405180830381865afa158015612f9a573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612fbe91906144f5565b5060019695505050505050565b60408101515160009015612fe157506000919050565b816020015151606014612ff657506000919050565b8160200151806020019051810190612aa39190614597565b60008160400151516002148061302957508160400151516003145b61303557506000919050565b81602001515160201461304a57506000919050565b506001919050565b600081604001515160011480613029575081604001515160021461303557506000919050565b6040810151516000901561308e57506000919050565b6020826020015151101561304a57506000919050565b81818082036130b4575050505050565b6000856130da60026130c6888861446c565b6130d09190614640565b6112c0908861456f565b815181106130ea576130ea613e6a565b60200260200101516000015190505b818313613236575b808661310c85612686565b8151811061311c5761311c613e6a565b60200260200101516000015112156131405782613138816148a6565b935050613101565b8561314a83612686565b8151811061315a5761315a613e6a565b60200260200101516000015181121561317f5781613177816148be565b925050613140565b818313613231578561319083612686565b815181106131a0576131a0613e6a565b6020026020010151866131b285612686565b815181106131c2576131c2613e6a565b6020026020010151876131d486612686565b815181106131e4576131e4613e6a565b60200260200101886131f586612686565b8151811061320557613205613e6a565b602002602001018290528290525050828061321f906148a6565b935050818061322d906148be565b9250505b6130f9565b81851215613249576132498686846130a4565b8383121561325c5761325c8684866130a4565b505050505050565b6000670de0b6b3a764000061327983856147e8565b6102b49190614640565b600081613279670de0b6b3a7640000856147e8565b60008060008360020b126132b8576132b3600284900b612686565b6132c8565b6132c86112c0600285900b614493565b90506132e36112c06132dd620d89e7196148db565b60020b90565b8111156133165760405162461bcd60e51b81526020600482015260016024820152601560fa1b6044820152606401610397565b60008160011660000361332d57600160801b61333f565b6ffffcb933bd6fad37aa2d162d1a5940015b70ffffffffffffffffffffffffffffffffff169050600282161561337e576080613379826ffff97272373d413259a46990580e213a614859565b901c90505b60048216156133a85760806133a3826ffff2e50f5f656932ef12357cf3c7fdcc614859565b901c90505b60088216156133d25760806133cd826fffe5caca7e10e4e61c3624eaa0941cd0614859565b901c90505b60108216156133fc5760806133f7826fffcb9843d60f6159c9db58835c926644614859565b901c90505b6020821615613426576080613421826fff973b41fa98c081472e6896dfb254c0614859565b901c90505b604082161561345057608061344b826fff2ea16466c96a3843ec78b326b52861614859565b901c90505b608082161561347a576080613475826ffe5dee046a99a2a811c461f1969c3053614859565b901c90505b6101008216156134a55760806134a0826ffcbe86c7900a88aedcffc83b479aa3a4614859565b901c90505b6102008216156134d05760806134cb826ff987a7253ac413176f2b074cf7815e54614859565b901c90505b6104008216156134fb5760806134f6826ff3392b0822b70005940c7a398e4b70f3614859565b901c90505b610800821615613526576080613521826fe7159475a2c29b7443b29c7fa6e889d9614859565b901c90505b61100082161561355157608061354c826fd097f3bdfd2022b8845ad8f792aa5825614859565b901c90505b61200082161561357c576080613577826fa9f746462d870fdf8a65dc1f90e061e5614859565b901c90505b6140008216156135a75760806135a2826f70d869a156d2a1b890bb3df62baf32f7614859565b901c90505b6180008216156135d25760806135cd826f31be135f97d08fd981231505542fcfa6614859565b901c90505b620100008216156135fe5760806135f9826f09aa508b5b7a84e1c677de54f3e99bc9614859565b901c90505b62020000821615613629576080613624826e5d6af8dedb81196699c329225ee604614859565b901c90505b6204000082161561365357608061364e826d2216e584f5fa1ea926041bedfe98614859565b901c90505b6208000082161561367b576080613676826b048a170391f7dc42444e8fa2614859565b901c90505b60008460020b131561369657613693816000196147c0565b90505b6102ce6136a8640100000000836147d4565b156136b45760016136b7565b60005b6136c89060ff16602084901c6147ad565b6139ba565b6000808060001985870985870292508281108382030391505080600003613749576000841161373e5760405162461bcd60e51b815260206004820152601960248201527f48616e646c65206e6f6e2d6f766572666c6f77206361736573000000000000006044820152606401610397565b5082900490506102b4565b8084116137985760405162461bcd60e51b815260206004820152601960248201527f70726576656e74732064656e6f6d696e61746f72203d3d2030000000000000006044820152606401610397565b60008486880980840393811190920391905060006137d06137b887612569565b6137c188612569565b6137ca90614493565b16612686565b9586900495938490049360008190030460010190506137ef8184614859565b909317926000613800876003614859565b600218905061380f8188614859565b61381a9060026140a7565b6138249082614859565b90506138308188614859565b61383b9060026140a7565b6138459082614859565b90506138518188614859565b61385c9060026140a7565b6138669082614859565b90506138728188614859565b61387d9060026140a7565b6138879082614859565b90506138938188614859565b61389e9060026140a7565b6138a89082614859565b90506138b48188614859565b6138bf9060026140a7565b6138c99082614859565b90506138d58186614859565b9998505050505050505050565b604080516001600160e01b0319831660248083019190915282518083039091018152604490910182526020810180516001600160e01b03166301ffc9a760e01b1790529051600091829182916001600160a01b0387169161394391906148fd565b6000604051808303816000865af19150503d8060008114613980576040519150601f19603f3d011682016040523d82523d6000602084013e613985565b606091505b50915091508161399a57600092505050610116565b80516000036139ae57600092505050610116565b60200151949350505050565b60006001600160a01b038211156125295760405163dccde8ed60e01b815260040160405180910390fd5b6040518060800160405280600081526020016000815260200160008152602001600081525090565b828054828255906000526020600020908101928215613a47579160200282015b82811115613a47578251825591602001919060010190613a2c565b506125299291505b808211156125295760008155600101613a4f565b600060208284031215613a7557600080fd5b5035919050565b8151815260208083015190820152604080830151908201526060808301519082015260808101610116565b634e487b7160e01b600052602160045260246000fd5b600a8110613acd57613acd613aa7565b9052565b60005b83811015613aec578181015183820152602001613ad4565b50506000910152565b60008151808452613b0d816020860160208601613ad1565b601f01601f19169290920160200192915050565b60006020808352613b358184018551613abd565b8084015160606040850152613b4d6080850182613af5565b6040860151858203601f19016060870152805180835290840192506000918401905b80831015613b8f5783518252928401926001929092019190840190613b6f565b509695505050505050565b634e487b7160e01b600052604160045260246000fd5b6040516080810167ffffffffffffffff81118282101715613bd357613bd3613b9a565b60405290565b604051601f8201601f1916810167ffffffffffffffff81118282101715613c0257613c02613b9a565b604052919050565b600067ffffffffffffffff821115613c2457613c24613b9a565b5060051b60200190565b600082601f830112613c3f57600080fd5b81356020613c54613c4f83613c0a565b613bd9565b8083825260208201915060208460051b870101935086841115613c7657600080fd5b602086015b84811015613b8f5780358352918301918301613c7b565b600080600060608486031215613ca757600080fd5b8335600a8110613cb657600080fd5b925060208481013567ffffffffffffffff80821115613cd457600080fd5b818701915087601f830112613ce857600080fd5b813581811115613cfa57613cfa613b9a565b613d0c601f8201601f19168501613bd9565b8181528985838601011115613d2057600080fd5b818585018683013760009181019094015291935060408601359180831115613d4757600080fd5b5050613d5586828701613c2e565b9150509250925092565b600080600060608486031215613d7457600080fd5b83359250602084013567ffffffffffffffff80821115613d9357600080fd5b613d9f87838801613c2e565b93506040860135915080821115613db557600080fd5b50613d5586828701613c2e565b600181811c90821680613dd657607f821691505b602082108103611d0757634e487b7160e01b600052602260045260246000fd5b60008151808452602080850194506020840160005b83811015613e2757815187529582019590820190600101613e0b565b509495945050505050565b848152613e426020820185613abd565b608060408201526000613e586080830185613af5565b828103606084015261189a8185613df6565b634e487b7160e01b600052603260045260246000fd5b600060208284031215613e9257600080fd5b8151600981106102b457600080fd5b6020810160098310613eb557613eb5613aa7565b91905290565b6001600160a01b0381168114613ed057600080fd5b50565b600060208284031215613ee557600080fd5b81516102b481613ebb565b608080825285518282018190526000919060209060a0850190828a01855b82811015613f5257613f42848351805182526020810151602083015260408101516040830152606081015160608301525050565b9285019290840190600101613f0e565b5050508481036020860152613f678189613af5565b925050508281036040840152613f7d8186613df6565b9050828103606084015261189a8185613df6565b600060808284031215613fa357600080fd5b613fab613bb0565b825181526020830151602082015260408301516040820152606083015160608201528091505092915050565b805160ff8116811461200457600080fd5b600080600060608486031215613ffd57600080fd5b835161400881613ebb565b6020850151909350915061401e60408501613fd7565b90509250925092565b805169ffffffffffffffffffff8116811461200457600080fd5b600080600080600060a0868803121561405957600080fd5b61406286614027565b945060208601519350604086015192506060860151915061408560808701614027565b90509295509295909350565b634e487b7160e01b600052601160045260246000fd5b8181038181111561011657610116614091565b60008060008060008060c087890312156140d357600080fd5b86516140de81613ebb565b60208801519096506140ef81613ebb565b94506140fd60408801613fd7565b935061410b60608801613fd7565b9250608087015161411b81613ebb565b60a088015190925063ffffffff8116811461413557600080fd5b809150509295509295509295565b6020808252825182820181905260009190848201906040850190845b8181101561418157835163ffffffff168352928401929184019160010161415f565b50909695505050505050565b600082601f83011261419e57600080fd5b815160206141ae613c4f83613c0a565b8083825260208201915060208460051b8701019350868411156141d057600080fd5b602086015b84811015613b8f5780516141e881613ebb565b83529183019183016141d5565b6000806040838503121561420857600080fd5b825167ffffffffffffffff8082111561422057600080fd5b818501915085601f83011261423457600080fd5b81516020614244613c4f83613c0a565b82815260059290921b8401810191818101908984111561426357600080fd5b948201945b838610156142915785518060060b81146142825760008081fd5b82529482019490820190614268565b918801519196509093505050808211156142aa57600080fd5b506142b78582860161418d565b9150509250929050565b600682810b9082900b03667fffffffffffff198112667fffffffffffff8213171561011657610116614091565b634e487b7160e01b600052601260045260246000fd5b60008160060b8360060b8061431b5761431b6142ee565b667fffffffffffff1982146000198214161561433957614339614091565b90059392505050565b600082614351576143516142ee565b500790565b60008160020b627fffff19810361436f5761436f614091565b6000190192915050565b600181815b808511156143b457816000190482111561439a5761439a614091565b808516156143a757918102915b93841c939080029061437e565b509250929050565b6000826143cb57506001610116565b816143d857506000610116565b81600181146143ee57600281146143f857614414565b6001915050610116565b60ff84111561440957614409614091565b50506001821b610116565b5060208310610133831016604e8410600b8410161715614437575081810a610116565b6144418383614379565b806000190482111561445557614455614091565b029392505050565b60006102b460ff8416836143bc565b818103600083128015838313168383128216171561448c5761448c614091565b5092915050565b6000600160ff1b82016144a8576144a8614091565b5060000390565b8051801515811461200457600080fd5b6000806000606084860312156144d457600080fd5b83516144df81613ebb565b6020850151909350915061401e604085016144af565b60006080828403121561450757600080fd5b61450f613bb0565b82518060070b811461452057600080fd5b8152602083015167ffffffffffffffff8116811461453d57600080fd5b60208201526040830151600381900b811461455757600080fd5b60408201526060928301519281019290925250919050565b808201828112600083128015821682158216171561458f5761458f614091565b505092915050565b6000806000606084860312156145ac57600080fd5b83516145b781613ebb565b602085015160409095015190969495509392505050565b60ff8416815267ffffffffffffffff831660208201526060604082015260006145fa6060830184613df6565b95945050505050565b6001600160a01b03831681526040602082018190526000906102ce90830184613af5565b60006020828403121561463957600080fd5b5051919050565b60008261464f5761464f6142ee565b600160ff1b82146000198414161561466957614669614091565b500590565b6146788185613abd565b60606020820152600061468e6060830185613af5565b8281036040840152610f4f8185613df6565b601f8211156146e8576000816000526020600020601f850160051c810160208610156146c95750805b601f850160051c820191505b8181101561325c578281556001016146d5565b505050565b815167ffffffffffffffff81111561470757614707613b9a565b61471b816147158454613dc2565b846146a0565b602080601f83116001811461475057600084156147385750858301515b600019600386901b1c1916600185901b17855561325c565b600085815260208120601f198616915b8281101561477f57888601518255948401946001909101908401614760565b508582101561479d5787850151600019600388901b60f8161c191681555b5050505050600190811b01905550565b8082018082111561011657610116614091565b6000826147cf576147cf6142ee565b500490565b6000826147e3576147e36142ee565b500690565b80820260008212600160ff1b8414161561480457614804614091565b818105831482151761011657610116614091565b600069ffffffffffffffffffff82168061436f5761436f614091565b60006001820161484657614846614091565b5060010190565b60006102b483836143bc565b808202811582820484141761011657610116614091565b60006020828403121561488257600080fd5b6102b4826144af565b60006020828403121561489d57600080fd5b6102b482613fd7565b60006001600160ff1b01820161484657614846614091565b6000600160ff1b82016148d3576148d3614091565b506000190190565b60008160020b627fffff1981036148f4576148f4614091565b60000392915050565b6000825161490f818460208701613ad1565b919091019291505056fea264697066735822122074f32fef384fdc296b0859f1c1f941c8e736c6cb972aa9e2b894956ebd6a80b364736f6c63430008160033","storage":{}},"0x83a0444b93927c3afcbe46e522280390f748e171":{"nonce":1,"balance":"0x0","code":"0x6080604052366100135761001161001d565b005b61001b61001d565b005b6000610027610093565b90503660008037600080366000845af43d6000803e806000811461004a573d6000f35b3d6000fd5b600080823b905060008111915050919050565b6000806040516020016100749061017a565b6040516020818303038152906040528051906020012090508091505090565b600061009d6100c6565b60000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16905090565b6000806040516020016100d89061020c565b6040516020818303038152906040528051906020012090508091505090565b600082825260208201905092915050565b7f696f2e73796e7468657469782e636f72652d636f6e7472616374732e4f776e6160008201527f626c650000000000000000000000000000000000000000000000000000000000602082015250565b60006101646023836100f7565b915061016f82610108565b604082019050919050565b6000602082019050818103600083015261019381610157565b9050919050565b7f696f2e73796e7468657469782e636f72652d636f6e7472616374732e50726f7860008201527f7900000000000000000000000000000000000000000000000000000000000000602082015250565b60006101f66021836100f7565b91506102018261019a565b604082019050919050565b60006020820190508181036000830152610225816101e9565b905091905056fea2646970667358221220800da1f73cebd5e4afa07496d9bca6b6c4f526bdd3f4014ec15c70fe3a1c441364736f6c63430008110033","storage":{"0x5a648c35a2f5512218b4683cf10e03f5b7c9dc7346e1bf77d304ae97f60f592b":"0x108f53faf774d7c4c56f5bce9ca6e605ce8aeadd","0x5c7865864a2a990d80b5bb5c40e7b73a029960dc711fbb56120dfab976e92ea3":"0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266"}},"0x90f79bf6eb2c4f870365e785982e1f101e93b906":{"nonce":0,"balance":"0x21e19e0c9bab2400000","code":"0x","storage":{}},"0x976ea74026e726554db657fa54763abd0c3a0aa9":{"nonce":0,"balance":"0x21e19e0c9bab2400000","code":"0x","storage":{}},"0x9965507d1a55bcc2695c58ba16fb37d819b0a4dc":{"nonce":0,"balance":"0x21e19e0c9bab2400000","code":"0x","storage":{}},"0xa0ee7a142d267c1f36714e4a8f75612f20a79720":{"nonce":0,"balance":"0x21e19e0c9bab2400000","code":"0x","storage":{}},"0xc67e2bd3108604cf0168c0e5ef9cd6d78b9bb14b":{"nonce":1,"balance":"0x21e19c6edb7e2445f20","code":"0x","storage":{}},"0xeb045d78d273107348b0300c01d29b7552d622ab":{"nonce":0,"balance":"0x21e19e0c9bab2400000","code":"0x","storage":{}},"0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266":{"nonce":1,"balance":"0x21e19e08b86820a43ea","code":"0x","storage":{}}},"best_block_number":5,"blocks":[{"header":{"parentHash":"0x08abe6e453727534d8dd708843a7522b7d500338bdfe2402ca105dcdb05eebe9","sha3Uncles":"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347","miner":"0x0000000000000000000000000000000000000000","stateRoot":"0xcd346446ed010523161f40a5f2b512def549bfb79e165b4354488738416481f2","transactionsRoot":"0xb3a4689832e0b599260ae70362ffcf224b60571b35ff8836904a3d81e2675d66","receiptsRoot":"0x2d13fdc120ab90536fed583939de7fb68b64926a306c1f629593ca9c2c93b198","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","difficulty":"0x0","number":"0x1","gasLimit":"0x1c9c380","gasUsed":"0x3ea90d","timestamp":"0x66b200ca","mixHash":"0x0000000000000000000000000000000000000000000000000000000000000000","nonce":"0x0000000000000000","baseFeePerGas":"0x2e0b6260","blobGasUsed":"0x0","excessBlobGas":"0x0","extraData":"0x"},"transactions":[{"EIP1559":{"chainId":"0x343a","nonce":"0x0","gas":"0x3ea90d","maxFeePerGas":"0x83215600","maxPriorityFeePerGas":"0x3b9aca00","value":"0x0","accessList":[],"input":"0x608060405234801561001057600080fd5b5061494f806100206000396000f3fe608060405234801561001057600080fd5b50600436106100575760003560e01c80632a952b2d1461005c57806350c946fe14610085578063625ca21c146100a5578063daa250be146100c6578063deba1b98146100d9575b600080fd5b61006f61006a366004613a63565b6100ec565b60405161007c9190613a7c565b60405180910390f35b610098610093366004613a63565b61011c565b60405161007c9190613b21565b6100b86100b3366004613c92565b610276565b60405190815260200161007c565b61006f6100d4366004613d5f565b6102bb565b6100b86100e7366004613c92565b6102d6565b6100f46139e4565b6040805160008082526020820190815281830190925261011691849190610310565b92915050565b6101416040805160608101909152806000815260200160608152602001606081525090565b61014a82610ab6565b60408051606081019091528154909190829060ff16600981111561017057610170613aa7565b600981111561018157610181613aa7565b815260200160018201805461019590613dc2565b80601f01602080910402602001604051908101604052809291908181526020018280546101c190613dc2565b801561020e5780601f106101e35761010080835404028352916020019161020e565b820191906000526020600020905b8154815290600101906020018083116101f157829003601f168201915b505050505081526020016002820180548060200260200160405190810160405280929190818152602001828054801561026657602002820191906000526020600020905b815481526020019060010190808311610252575b5050505050815250509050919050565b600080604051806060016040528086600981111561029657610296613aa7565b81526020018581526020018481525090506102b081610ac1565b9150505b9392505050565b6102c36139e4565b6102ce848484610310565b949350505050565b60008060405180606001604052808660098111156102f6576102f6613aa7565b81526020018581526020018481525090506102b081610acc565b6103186139e4565b81518351146103a05760408051634bab873760e11b81526004810191909152600d60448201526c72756e74696d6556616c75657360981b606482015260806024820152602260848201527f6d7573742062652073616d65206c656e6774682061732072756e74696d654b6560a482015261797360f01b60c482015260e4015b60405180910390fd5b60006103ab85610c26565b805490915060ff1660018160098111156103c7576103c7613aa7565b036104755761046c6103da838787610c84565b8360010180546103e990613dc2565b80601f016020809104026020016040519081016040528092919081815260200182805461041590613dc2565b80156104625780601f1061043757610100808354040283529160200191610462565b820191906000526020600020905b81548152906001019060200180831161044557829003601f168201915b5050505050610d46565b925050506102b4565b600281600981111561048957610489613aa7565b036105305761046c61049c838787610c84565b8360010180546104ab90613dc2565b80601f01602080910402602001604051908101604052809291908181526020018280546104d790613dc2565b80156105245780601f106104f957610100808354040283529160200191610524565b820191906000526020600020905b81548152906001019060200180831161050757829003601f168201915b50505050508787610ebb565b600381600981111561054457610544613aa7565b036105de5761046c82600101805461055b90613dc2565b80601f016020809104026020016040519081016040528092919081815260200182805461058790613dc2565b80156105d45780601f106105a9576101008083540402835291602001916105d4565b820191906000526020600020905b8154815290600101906020018083116105b757829003601f168201915b5050505050610f59565b60048160098111156105f2576105f2613aa7565b0361068c5761046c82600101805461060990613dc2565b80601f016020809104026020016040519081016040528092919081815260200182805461063590613dc2565b80156106825780601f1061065757610100808354040283529160200191610682565b820191906000526020600020905b81548152906001019060200180831161066557829003601f168201915b5050505050611087565b60058160098111156106a0576106a0613aa7565b0361073a5761046c8260010180546106b790613dc2565b80601f01602080910402602001604051908101604052809291908181526020018280546106e390613dc2565b80156107305780601f1061070557610100808354040283529160200191610730565b820191906000526020600020905b81548152906001019060200180831161071357829003601f168201915b505050505061131e565b600981600981111561074e5761074e613aa7565b036107ea5761046c82600101805461076590613dc2565b80601f016020809104026020016040519081016040528092919081815260200182805461079190613dc2565b80156107de5780601f106107b3576101008083540402835291602001916107de565b820191906000526020600020905b8154815290600101906020018083116107c157829003601f168201915b505050505086866114b5565b60068160098111156107fe576107fe613aa7565b036108a35761046c610811838787610c84565b83600101805461082090613dc2565b80601f016020809104026020016040519081016040528092919081815260200182805461084c90613dc2565b80156108995780601f1061086e57610100808354040283529160200191610899565b820191906000526020600020905b81548152906001019060200180831161087c57829003601f168201915b50505050506115c7565b60078160098111156108b7576108b7613aa7565b036109ec576040805160608101909152825461046c91908490829060ff1660098111156108e6576108e6613aa7565b60098111156108f7576108f7613aa7565b815260200160018201805461090b90613dc2565b80601f016020809104026020016040519081016040528092919081815260200182805461093790613dc2565b80156109845780601f1061095957610100808354040283529160200191610984565b820191906000526020600020905b81548152906001019060200180831161096757829003601f168201915b50505050508152602001600282018054806020026020016040519081016040528092919081815260200182805480156109dc57602002820191906000526020600020905b8154815260200190600101908083116109c8575b5050505050815250508686611728565b6008816009811115610a0057610a00613aa7565b03610a9a5761046c826001018054610a1790613dc2565b80601f0160208091040260200160405190810160405280929190818152602001828054610a4390613dc2565b8015610a905780601f10610a6557610100808354040283529160200191610a90565b820191906000526020600020905b815481529060010190602001808311610a7357829003601f168201915b50505050506118a5565b6040516323a9bbc960e01b815260048101879052602401610397565b600061011682610c26565b6000610116826118ea565b6000610ad782610ac1565b9050610ae28161192a565b15610b35577fcb64985827770858ec421ad26da7e558c757541643036ce44d6b4eb9e8e5dc5e81836000015184602001518560400151604051610b289493929190613e32565b60405180910390a1919050565b610b3e82611a8c565b610b5d578160405163382bbbc960e11b81526004016103979190613b21565b60005b826040015151811015610bd957610b9383604001518281518110610b8657610b86613e6a565b602002602001015161192a565b610bd15782604001518181518110610bad57610bad613e6a565b6020026020010151604051632f19f96160e11b815260040161039791815260200190565b600101610b60565b50610be382611c31565b8351602085015160408087015190519395507fcb64985827770858ec421ad26da7e558c757541643036ce44d6b4eb9e8e5dc5e9450610b28938693929190613e32565b604080516020808201839052606082018190527f696f2e73796e7468657469782e6f7261636c652d6d616e616765722e4e6f6465608080840191909152828401949094528251808303909401845260a0909101909152815191012090565b600283015460609067ffffffffffffffff811115610ca457610ca4613b9a565b604051908082528060200260200182016040528015610cdd57816020015b610cca6139e4565b815260200190600190039081610cc25790505b50905060005b6002850154811015610d3e57610d19856002018281548110610d0757610d07613e6a565b90600052602060002001548585610310565b828281518110610d2b57610d2b613e6a565b6020908102919091010152600101610ce3565b509392505050565b610d4e6139e4565b600082806020019051810190610d649190613e80565b90506000816008811115610d7a57610d7a613aa7565b03610d9057610d8884611ca5565b915050610116565b6001816008811115610da457610da4613aa7565b03610db257610d8884611d0d565b6002816008811115610dc657610dc6613aa7565b03610dd457610d8884611d90565b6003816008811115610de857610de8613aa7565b03610df657610d8884611e13565b6004816008811115610e0a57610e0a613aa7565b03610e1857610d8884611ec9565b6005816008811115610e2c57610e2c613aa7565b03610e3a57610d8884612009565b6006816008811115610e4e57610e4e613aa7565b03610e5c57610d88846120e4565b6007816008811115610e7057610e70613aa7565b03610e7e57610d888461220c565b6008816008811115610e9257610e92613aa7565b03610ea057610d88846122ce565b80604051631be413d360e11b81526004016103979190613ea1565b610ec36139e4565b600084806020019051810190610ed99190613ed3565b604051631ecba7c360e31b81529091506001600160a01b0382169063f65d3e1890610f0e908990899089908990600401613ef0565b608060405180830381865afa158015610f2b573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610f4f9190613f91565b9695505050505050565b610f616139e4565b600080600084806020019051810190610f7a9190613fe8565b92509250925060008390506000806000836001600160a01b031663feaf968c6040518163ffffffff1660e01b815260040160a060405180830381865afa158015610fc8573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610fec9190614041565b509350509250925060008660001461100f5761100a8585858a6123c7565b611011565b825b905060128660ff161161103b5761103661102f60ff881660126140a7565b82906124c2565b611053565b61105361104c601260ff89166140a7565b82906124dc565b9050604051806080016040528082815260200183815260200160008152602001600081525098505050505050505050919050565b61108f6139e4565b600080600080600080878060200190518101906110ac91906140ba565b604080516002808252606082018352979d50959b50939950919750955093506000929060208301908036833701905050905081816000815181106110f2576110f2613e6a565b602002602001019063ffffffff16908163ffffffff168152505060008160018151811061112157611121613e6a565b63ffffffff9092166020928302919091019091015260405163883bdbfd60e01b81526000906001600160a01b0385169063883bdbfd90611165908590600401614143565b600060405180830381865afa158015611182573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f191682016040526111aa91908101906141f5565b5090506000816000815181106111c2576111c2613e6a565b6020026020010151826001815181106111dd576111dd613e6a565b60200260200101516111ef91906142c1565b9050600061121761120563ffffffff87166124f6565b61120f9084614304565b60060b61252d565b905060008260060b12801561124c575061123b63ffffffff8616612569565b612569565b8260060b6112499190614342565b15155b1561125f578061125b81614356565b9150505b600061126d6012600a61445d565b9050600061128061123684848f8f612593565b905060006112908a60ff16612569565b61129c8c60ff16612569565b6112a6919061446c565b905060008082136112d1576112cc6112c56112c084614493565b612686565b84906124dc565b6112e4565b6112e46112dd83612686565b84906124c2565b905060405180608001604052808281526020014281526020016000815260200160008152509e505050505050505050505050505050919050565b6113266139e4565b60008060008480602001905181019061133f91906144bf565b91945092509050826000826113bc576040516396834ad360e01b8152600481018590526001600160a01b038316906396834ad390602401608060405180830381865afa158015611393573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906113b791906144f5565b611425565b604051639474f45b60e01b8152600481018590526001600160a01b03831690639474f45b90602401608060405180830381865afa158015611401573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061142591906144f5565b90506000816040015160030b601261143d919061456f565b90506000808213611467576114626114576112c084614493565b845160070b906124dc565b61147e565b61147e61147383612686565b845160070b906124c2565b9050604051806080016040528082815260200184606001518152602001600081526020016000815250975050505050505050919050565b6114bd6139e4565b6000806000868060200190518101906114d69190614597565b92509250925060005b8651811015611545578681815181106114fa576114fa613e6a565b6020026020010151717374616c656e657373546f6c6572616e636560701b0361153d5785818151811061152f5761152f613e6a565b602002602001015160001c91505b6001016114df565b5060408051600180825281830190925260009160208083019080368337019050509050828160008151811061157c5761157c613e6a565b602002602001018181525050836001838360405160200161159f939291906145ce565b60408051601f198184030181529082905263cf2cabdf60e01b82526103979291600401614603565b6115cf6139e4565b6000828060200190518101906115e59190614627565b90506000846000815181106115fc576115fc613e6a565b602002602001015160000151905060008560018151811061161f5761161f613e6a565b6020026020010151600001519050808214611702576000611653601261164d611648858761446c565b6126a9565b906124c2565b905082158061167b5750611666836126a9565b6116709082614640565b61167985612569565b125b15611700576002875111156116b0578660028151811061169d5761169d613e6a565b6020026020010151945050505050610116565b826000036116d15760405163014cc07160e01b815260040160405180910390fd5b6116da836126a9565b6116e49082614640565b60405163dcac091960e01b815260040161039791815260200190565b505b8560008151811061171557611715613e6a565b6020026020010151935050505092915050565b6117306139e4565b6000846020015180602001905181019061174a9190614627565b905060005b84518110156117bc5784818151811061176a5761176a613e6a565b6020026020010151717374616c656e657373546f6c6572616e636560701b036117b4576117ad8482815181106117a2576117a2613e6a565b602002602001015190565b91506117bc565b60010161174f565b50600085604001516000815181106117d6576117d6613e6a565b6020026020010151905060006117ed828787610310565b60208101519091506117ff84426140a7565b1161180e5792506102b4915050565b86604001515160010361187157866040015160008151811061183257611832613e6a565b602002602001015181600001518260200151604051631808066560e21b8152600401610397939291909283526020830191909152604082015260600190565b61189a876040015160018151811061188b5761188b613e6a565b60200260200101518787610310565b979650505050505050565b6118ad6139e4565b6040518060800160405280838060200190518101906118cc9190614627565b81526020014281526020016000815260200160008152509050919050565b600081600001518260200151836040015160405160200161190d9392919061466e565b604051602081830303815290604052805190602001209050919050565b60008061193683610c26565b60408051606081019091528154909190829060ff16600981111561195c5761195c613aa7565b600981111561196d5761196d613aa7565b815260200160018201805461198190613dc2565b80601f01602080910402602001604051908101604052809291908181526020018280546119ad90613dc2565b80156119fa5780601f106119cf576101008083540402835291602001916119fa565b820191906000526020600020905b8154815290600101906020018083116119dd57829003601f168201915b5050505050815260200160028201805480602002602001604051908101604052809291908181526020018280548015611a5257602002820191906000526020600020905b815481526020019060010190808311611a3e575b505050505081525050905060006009811115611a7057611a70613aa7565b81516009811115611a8357611a83613aa7565b14159392505050565b6000600182516009811115611aa357611aa3613aa7565b1480611ac15750600682516009811115611abf57611abf613aa7565b145b80611ade5750600782516009811115611adc57611adc613aa7565b145b15611aee57611aec826126c1565b505b600182516009811115611b0357611b03613aa7565b03611b11576101168261284a565b600282516009811115611b2657611b26613aa7565b03611b3457610116826128a5565b600382516009811115611b4957611b49613aa7565b03611b575761011682612973565b600482516009811115611b6c57611b6c613aa7565b03611b7a5761011682612aae565b600582516009811115611b8f57611b8f613aa7565b03611b9d5761011682612e92565b600982516009811115611bb257611bb2613aa7565b03611bc05761011682612fcb565b600682516009811115611bd557611bd5613aa7565b03611be3576101168261300e565b600782516009811115611bf857611bf8613aa7565b03611c065761011682613052565b600882516009811115611c1b57611c1b613aa7565b03611c295761011682613078565b506000919050565b600080611c3d836118ea565b9050611c4881610c26565b8351815491935090839060ff19166001836009811115611c6a57611c6a613aa7565b021790555060208301516001830190611c8390826146ed565b5060408301518051611c9f916002850191602090910190613a0c565b50915091565b611cad6139e4565b60005b8251811015611d07578160200151838281518110611cd057611cd0613e6a565b6020026020010151602001511115611cff57828181518110611cf457611cf4613e6a565b602002602001015191505b600101611cb0565b50919050565b611d156139e4565b81600081518110611d2857611d28613e6a565b602002602001015190506000600190505b8251811015611d07578160000151838281518110611d5957611d59613e6a565b6020026020010151600001511215611d8857828181518110611d7d57611d7d613e6a565b602002602001015191505b600101611d39565b611d986139e4565b81600081518110611dab57611dab613e6a565b602002602001015190506000600190505b8251811015611d07578160000151838281518110611ddc57611ddc613e6a565b6020026020010151600001511315611e0b57828181518110611e0057611e00613e6a565b602002602001015191505b600101611dbc565b611e1b6139e4565b60005b8251811015611e9557828181518110611e3957611e39613e6a565b60200260200101516000015182600001818151611e56919061456f565b9052508251839082908110611e6d57611e6d613e6a565b60200260200101516020015182602001818151611e8a91906147ad565b905250600101611e1e565b50611ea08251612569565b8151611eac9190614640565b815281516020820151611ebf91906147c0565b6020820152919050565b611ed16139e4565b611eed826000611ee86001865161123691906140a7565b6130a4565b60028251611efb91906147d4565b600003611fd65760408051600280825260608201909252600091816020015b611f226139e4565b815260200190600190039081611f1a57905050905082600160028551611f4891906147c0565b611f5291906140a7565b81518110611f6257611f62613e6a565b602002602001015181600081518110611f7d57611f7d613e6a565b60200260200101819052508260028451611f9791906147c0565b81518110611fa757611fa7613e6a565b602002602001015181600181518110611fc257611fc2613e6a565b60200260200101819052506102b481611e13565b8160028351611fe591906147c0565b81518110611ff557611ff5613e6a565b60200260200101519050919050565b919050565b6120116139e4565b8160008151811061202457612024613e6a565b60209081029190910101515181528151829060009061204557612045613e6a565b6020908102919091018101518101519082015260015b82518110156120d25782818151811061207657612076613e6a565b6020026020010151600001518260000181815161209391906147e8565b90525082518390829081106120aa576120aa613e6a565b602002602001015160200151826020018181516120c791906147ad565b90525060010161205b565b5081518160200151611ebf91906147c0565b6120ec6139e4565b816000815181106120ff576120ff613e6a565b60209081029190910101515181528151829060009061212057612120613e6a565b6020908102919091018101518101519082015260015b82518110156120d25782818151811061215157612151613e6a565b60200260200101516000015160000361219e5782818151811061217657612176613e6a565b6020026020010151600001516040516338ee04a760e01b815260040161039791815260200190565b8281815181106121b0576121b0613e6a565b602002602001015160000151826000018181516121cd9190614640565b90525082518390829081106121e4576121e4613e6a565b6020026020010151602001518260200181815161220191906147ad565b905250600101612136565b6122146139e4565b8160008151811061222757612227613e6a565b60209081029190910101515181528151829060009061224857612248613e6a565b6020908102919091018101518101519082015260015b82518110156120d25761229083828151811061227c5761227c613e6a565b602090810291909101015151835190613264565b825282518390829081106122a6576122a6613e6a565b602002602001015160200151826020018181516122c391906147ad565b90525060010161225e565b6122d66139e4565b816000815181106122e9576122e9613e6a565b60209081029190910101515181528151829060009061230a5761230a613e6a565b6020908102919091018101518101519082015260015b82518110156120d25782818151811061233b5761233b613e6a565b6020026020010151600001516000036123605782818151811061217657612176613e6a565b61238983828151811061237557612375613e6a565b602090810291909101015151835190613283565b8252825183908290811061239f5761239f613e6a565b602002602001015160200151826020018181516123bc91906147ad565b905250600101612320565b6000826001826123d785426140a7565b90505b69ffffffffffffffffffff8716156124a3576001600160a01b038816639a6fc8f561240489614818565b6040516001600160e01b031960e084901b16815269ffffffffffffffffffff8216600482015290995060240160a060405180830381865afa925050508015612469575060408051601f3d908101601f1916820190925261246691810190614041565b60015b156124a357858210156124805750505050506124a3565b61248a848961456f565b97508661249681614834565b97505050505050506123da565b6124ac82612569565b6124b69084614640565b98975050505050505050565b60006124d261123683600a61484d565b6102b490846147e8565b60006124ec61123683600a61484d565b6102b49084614640565b6000667fffffffffffff66ffffffffffffff83161115612529576040516329d2678160e21b815260040160405180910390fd5b5090565b6000627fffff19600683900b128061254b5750627fffff600683900b135b1561252957604051630d962f7960e21b815260040160405180910390fd5b60006001600160ff1b038211156125295760405163677c430560e11b815260040160405180910390fd5b60008061259f86613298565b90506fffffffffffffffffffffffffffffffff6001600160a01b0382161161261c5760006125d66001600160a01b03831680614859565b9050836001600160a01b0316856001600160a01b03161061260557612600600160c01b87836136cd565b612614565b6126148187600160c01b6136cd565b92505061267d565b600061263b6001600160a01b03831680680100000000000000006136cd565b9050836001600160a01b0316856001600160a01b03161061266a57612665600160801b87836136cd565b612679565b6126798187600160801b6136cd565b9250505b50949350505050565b6000808212156125295760405163029f024d60e31b815260040160405180910390fd5b600080821215612529576126bc82614493565b610116565b6000805b8260400151518110156128415760006126fa846040015183815181106126ed576126ed613e6a565b6020026020010151610ab6565b60408051606081019091528154909190829060ff16600981111561272057612720613aa7565b600981111561273157612731613aa7565b815260200160018201805461274590613dc2565b80601f016020809104026020016040519081016040528092919081815260200182805461277190613dc2565b80156127be5780601f10612793576101008083540402835291602001916127be565b820191906000526020600020905b8154815290600101906020018083116127a157829003601f168201915b505050505081526020016002820180548060200260200160405190810160405280929190818152602001828054801561281657602002820191906000526020600020905b815481526020019060010190808311612802575b505050505081525050905061282a81611a8c565b612838575060009392505050565b506001016126c5565b50600192915050565b60006002826040015151101561286257506000919050565b81602001515160201461287757506000919050565b600082602001518060200190518101906128919190614627565b905060088111156128415750600092915050565b6000602082602001515110156128bd57506000919050565b600082602001518060200190518101906128d79190613ed3565b90506128ea816306e7ea3960e21b6138e2565b6128f75750600092915050565b604051633b70a5bf60e21b81526001600160a01b0382169063edc296fc90612923908690600401613b21565b6020604051808303816000875af1158015612942573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906129669190614870565b6128415750600092915050565b6040810151516000901561298957506000919050565b81602001515160601461299e57506000919050565b60008083602001518060200190518101906129b99190613fe8565b92505091506000829050806001600160a01b031663feaf968c6040518163ffffffff1660e01b815260040160a060405180830381865afa158015612a01573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612a259190614041565b5050505050806001600160a01b031663313ce5676040518163ffffffff1660e01b8152600401602060405180830381865afa158015612a68573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612a8c919061488b565b60ff168260ff1614612aa357506000949350505050565b506001949350505050565b60408101515160009015612ac457506000919050565b81602001515160c014612ad957506000919050565b6000806000806000808760200151806020019051810190612afa91906140ba565b9550955095509550955095508360ff16866001600160a01b031663313ce5676040518163ffffffff1660e01b8152600401602060405180830381865afa158015612b48573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612b6c919061488b565b60ff1614612b8257506000979650505050505050565b8260ff16856001600160a01b031663313ce5676040518163ffffffff1660e01b8152600401602060405180830381865afa158015612bc4573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612be8919061488b565b60ff1614612bfe57506000979650505050505050565b6000826001600160a01b0316630dfe16816040518163ffffffff1660e01b8152600401602060405180830381865afa158015612c3e573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612c629190613ed3565b90506000836001600160a01b031663d21220a76040518163ffffffff1660e01b8152600401602060405180830381865afa158015612ca4573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612cc89190613ed3565b9050876001600160a01b0316826001600160a01b0316148015612cfc5750866001600160a01b0316816001600160a01b0316145b158015612d385750866001600160a01b0316826001600160a01b0316148015612d365750876001600160a01b0316816001600160a01b0316145b155b15612d4d575060009998505050505050505050565b60128660ff161180612d62575060128560ff16115b15612d77575060009998505050505050505050565b8263ffffffff16600003612d95575060009998505050505050505050565b6040805160028082526060820183526000926020830190803683370190505090508381600081518110612dca57612dca613e6a565b602002602001019063ffffffff16908163ffffffff1681525050600081600181518110612df957612df9613e6a565b63ffffffff9092166020928302919091019091015260405163883bdbfd60e01b81526001600160a01b0386169063883bdbfd90612e3a908490600401614143565b600060405180830381865afa158015612e57573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f19168201604052612e7f91908101906141f5565b5060019c9b505050505050505050505050565b60408101515160009015612ea857506000919050565b816020015151606014612ebd57506000919050565b60008060008460200151806020019051810190612eda91906144bf565b919450925090508281612f55576040516396834ad360e01b8152600481018490526001600160a01b038216906396834ad390602401608060405180830381865afa158015612f2c573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612f5091906144f5565b612fbe565b604051639474f45b60e01b8152600481018490526001600160a01b03821690639474f45b90602401608060405180830381865afa158015612f9a573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612fbe91906144f5565b5060019695505050505050565b60408101515160009015612fe157506000919050565b816020015151606014612ff657506000919050565b8160200151806020019051810190612aa39190614597565b60008160400151516002148061302957508160400151516003145b61303557506000919050565b81602001515160201461304a57506000919050565b506001919050565b600081604001515160011480613029575081604001515160021461303557506000919050565b6040810151516000901561308e57506000919050565b6020826020015151101561304a57506000919050565b81818082036130b4575050505050565b6000856130da60026130c6888861446c565b6130d09190614640565b6112c0908861456f565b815181106130ea576130ea613e6a565b60200260200101516000015190505b818313613236575b808661310c85612686565b8151811061311c5761311c613e6a565b60200260200101516000015112156131405782613138816148a6565b935050613101565b8561314a83612686565b8151811061315a5761315a613e6a565b60200260200101516000015181121561317f5781613177816148be565b925050613140565b818313613231578561319083612686565b815181106131a0576131a0613e6a565b6020026020010151866131b285612686565b815181106131c2576131c2613e6a565b6020026020010151876131d486612686565b815181106131e4576131e4613e6a565b60200260200101886131f586612686565b8151811061320557613205613e6a565b602002602001018290528290525050828061321f906148a6565b935050818061322d906148be565b9250505b6130f9565b81851215613249576132498686846130a4565b8383121561325c5761325c8684866130a4565b505050505050565b6000670de0b6b3a764000061327983856147e8565b6102b49190614640565b600081613279670de0b6b3a7640000856147e8565b60008060008360020b126132b8576132b3600284900b612686565b6132c8565b6132c86112c0600285900b614493565b90506132e36112c06132dd620d89e7196148db565b60020b90565b8111156133165760405162461bcd60e51b81526020600482015260016024820152601560fa1b6044820152606401610397565b60008160011660000361332d57600160801b61333f565b6ffffcb933bd6fad37aa2d162d1a5940015b70ffffffffffffffffffffffffffffffffff169050600282161561337e576080613379826ffff97272373d413259a46990580e213a614859565b901c90505b60048216156133a85760806133a3826ffff2e50f5f656932ef12357cf3c7fdcc614859565b901c90505b60088216156133d25760806133cd826fffe5caca7e10e4e61c3624eaa0941cd0614859565b901c90505b60108216156133fc5760806133f7826fffcb9843d60f6159c9db58835c926644614859565b901c90505b6020821615613426576080613421826fff973b41fa98c081472e6896dfb254c0614859565b901c90505b604082161561345057608061344b826fff2ea16466c96a3843ec78b326b52861614859565b901c90505b608082161561347a576080613475826ffe5dee046a99a2a811c461f1969c3053614859565b901c90505b6101008216156134a55760806134a0826ffcbe86c7900a88aedcffc83b479aa3a4614859565b901c90505b6102008216156134d05760806134cb826ff987a7253ac413176f2b074cf7815e54614859565b901c90505b6104008216156134fb5760806134f6826ff3392b0822b70005940c7a398e4b70f3614859565b901c90505b610800821615613526576080613521826fe7159475a2c29b7443b29c7fa6e889d9614859565b901c90505b61100082161561355157608061354c826fd097f3bdfd2022b8845ad8f792aa5825614859565b901c90505b61200082161561357c576080613577826fa9f746462d870fdf8a65dc1f90e061e5614859565b901c90505b6140008216156135a75760806135a2826f70d869a156d2a1b890bb3df62baf32f7614859565b901c90505b6180008216156135d25760806135cd826f31be135f97d08fd981231505542fcfa6614859565b901c90505b620100008216156135fe5760806135f9826f09aa508b5b7a84e1c677de54f3e99bc9614859565b901c90505b62020000821615613629576080613624826e5d6af8dedb81196699c329225ee604614859565b901c90505b6204000082161561365357608061364e826d2216e584f5fa1ea926041bedfe98614859565b901c90505b6208000082161561367b576080613676826b048a170391f7dc42444e8fa2614859565b901c90505b60008460020b131561369657613693816000196147c0565b90505b6102ce6136a8640100000000836147d4565b156136b45760016136b7565b60005b6136c89060ff16602084901c6147ad565b6139ba565b6000808060001985870985870292508281108382030391505080600003613749576000841161373e5760405162461bcd60e51b815260206004820152601960248201527f48616e646c65206e6f6e2d6f766572666c6f77206361736573000000000000006044820152606401610397565b5082900490506102b4565b8084116137985760405162461bcd60e51b815260206004820152601960248201527f70726576656e74732064656e6f6d696e61746f72203d3d2030000000000000006044820152606401610397565b60008486880980840393811190920391905060006137d06137b887612569565b6137c188612569565b6137ca90614493565b16612686565b9586900495938490049360008190030460010190506137ef8184614859565b909317926000613800876003614859565b600218905061380f8188614859565b61381a9060026140a7565b6138249082614859565b90506138308188614859565b61383b9060026140a7565b6138459082614859565b90506138518188614859565b61385c9060026140a7565b6138669082614859565b90506138728188614859565b61387d9060026140a7565b6138879082614859565b90506138938188614859565b61389e9060026140a7565b6138a89082614859565b90506138b48188614859565b6138bf9060026140a7565b6138c99082614859565b90506138d58186614859565b9998505050505050505050565b604080516001600160e01b0319831660248083019190915282518083039091018152604490910182526020810180516001600160e01b03166301ffc9a760e01b1790529051600091829182916001600160a01b0387169161394391906148fd565b6000604051808303816000865af19150503d8060008114613980576040519150601f19603f3d011682016040523d82523d6000602084013e613985565b606091505b50915091508161399a57600092505050610116565b80516000036139ae57600092505050610116565b60200151949350505050565b60006001600160a01b038211156125295760405163dccde8ed60e01b815260040160405180910390fd5b6040518060800160405280600081526020016000815260200160008152602001600081525090565b828054828255906000526020600020908101928215613a47579160200282015b82811115613a47578251825591602001919060010190613a2c565b506125299291505b808211156125295760008155600101613a4f565b600060208284031215613a7557600080fd5b5035919050565b8151815260208083015190820152604080830151908201526060808301519082015260808101610116565b634e487b7160e01b600052602160045260246000fd5b600a8110613acd57613acd613aa7565b9052565b60005b83811015613aec578181015183820152602001613ad4565b50506000910152565b60008151808452613b0d816020860160208601613ad1565b601f01601f19169290920160200192915050565b60006020808352613b358184018551613abd565b8084015160606040850152613b4d6080850182613af5565b6040860151858203601f19016060870152805180835290840192506000918401905b80831015613b8f5783518252928401926001929092019190840190613b6f565b509695505050505050565b634e487b7160e01b600052604160045260246000fd5b6040516080810167ffffffffffffffff81118282101715613bd357613bd3613b9a565b60405290565b604051601f8201601f1916810167ffffffffffffffff81118282101715613c0257613c02613b9a565b604052919050565b600067ffffffffffffffff821115613c2457613c24613b9a565b5060051b60200190565b600082601f830112613c3f57600080fd5b81356020613c54613c4f83613c0a565b613bd9565b8083825260208201915060208460051b870101935086841115613c7657600080fd5b602086015b84811015613b8f5780358352918301918301613c7b565b600080600060608486031215613ca757600080fd5b8335600a8110613cb657600080fd5b925060208481013567ffffffffffffffff80821115613cd457600080fd5b818701915087601f830112613ce857600080fd5b813581811115613cfa57613cfa613b9a565b613d0c601f8201601f19168501613bd9565b8181528985838601011115613d2057600080fd5b818585018683013760009181019094015291935060408601359180831115613d4757600080fd5b5050613d5586828701613c2e565b9150509250925092565b600080600060608486031215613d7457600080fd5b83359250602084013567ffffffffffffffff80821115613d9357600080fd5b613d9f87838801613c2e565b93506040860135915080821115613db557600080fd5b50613d5586828701613c2e565b600181811c90821680613dd657607f821691505b602082108103611d0757634e487b7160e01b600052602260045260246000fd5b60008151808452602080850194506020840160005b83811015613e2757815187529582019590820190600101613e0b565b509495945050505050565b848152613e426020820185613abd565b608060408201526000613e586080830185613af5565b828103606084015261189a8185613df6565b634e487b7160e01b600052603260045260246000fd5b600060208284031215613e9257600080fd5b8151600981106102b457600080fd5b6020810160098310613eb557613eb5613aa7565b91905290565b6001600160a01b0381168114613ed057600080fd5b50565b600060208284031215613ee557600080fd5b81516102b481613ebb565b608080825285518282018190526000919060209060a0850190828a01855b82811015613f5257613f42848351805182526020810151602083015260408101516040830152606081015160608301525050565b9285019290840190600101613f0e565b5050508481036020860152613f678189613af5565b925050508281036040840152613f7d8186613df6565b9050828103606084015261189a8185613df6565b600060808284031215613fa357600080fd5b613fab613bb0565b825181526020830151602082015260408301516040820152606083015160608201528091505092915050565b805160ff8116811461200457600080fd5b600080600060608486031215613ffd57600080fd5b835161400881613ebb565b6020850151909350915061401e60408501613fd7565b90509250925092565b805169ffffffffffffffffffff8116811461200457600080fd5b600080600080600060a0868803121561405957600080fd5b61406286614027565b945060208601519350604086015192506060860151915061408560808701614027565b90509295509295909350565b634e487b7160e01b600052601160045260246000fd5b8181038181111561011657610116614091565b60008060008060008060c087890312156140d357600080fd5b86516140de81613ebb565b60208801519096506140ef81613ebb565b94506140fd60408801613fd7565b935061410b60608801613fd7565b9250608087015161411b81613ebb565b60a088015190925063ffffffff8116811461413557600080fd5b809150509295509295509295565b6020808252825182820181905260009190848201906040850190845b8181101561418157835163ffffffff168352928401929184019160010161415f565b50909695505050505050565b600082601f83011261419e57600080fd5b815160206141ae613c4f83613c0a565b8083825260208201915060208460051b8701019350868411156141d057600080fd5b602086015b84811015613b8f5780516141e881613ebb565b83529183019183016141d5565b6000806040838503121561420857600080fd5b825167ffffffffffffffff8082111561422057600080fd5b818501915085601f83011261423457600080fd5b81516020614244613c4f83613c0a565b82815260059290921b8401810191818101908984111561426357600080fd5b948201945b838610156142915785518060060b81146142825760008081fd5b82529482019490820190614268565b918801519196509093505050808211156142aa57600080fd5b506142b78582860161418d565b9150509250929050565b600682810b9082900b03667fffffffffffff198112667fffffffffffff8213171561011657610116614091565b634e487b7160e01b600052601260045260246000fd5b60008160060b8360060b8061431b5761431b6142ee565b667fffffffffffff1982146000198214161561433957614339614091565b90059392505050565b600082614351576143516142ee565b500790565b60008160020b627fffff19810361436f5761436f614091565b6000190192915050565b600181815b808511156143b457816000190482111561439a5761439a614091565b808516156143a757918102915b93841c939080029061437e565b509250929050565b6000826143cb57506001610116565b816143d857506000610116565b81600181146143ee57600281146143f857614414565b6001915050610116565b60ff84111561440957614409614091565b50506001821b610116565b5060208310610133831016604e8410600b8410161715614437575081810a610116565b6144418383614379565b806000190482111561445557614455614091565b029392505050565b60006102b460ff8416836143bc565b818103600083128015838313168383128216171561448c5761448c614091565b5092915050565b6000600160ff1b82016144a8576144a8614091565b5060000390565b8051801515811461200457600080fd5b6000806000606084860312156144d457600080fd5b83516144df81613ebb565b6020850151909350915061401e604085016144af565b60006080828403121561450757600080fd5b61450f613bb0565b82518060070b811461452057600080fd5b8152602083015167ffffffffffffffff8116811461453d57600080fd5b60208201526040830151600381900b811461455757600080fd5b60408201526060928301519281019290925250919050565b808201828112600083128015821682158216171561458f5761458f614091565b505092915050565b6000806000606084860312156145ac57600080fd5b83516145b781613ebb565b602085015160409095015190969495509392505050565b60ff8416815267ffffffffffffffff831660208201526060604082015260006145fa6060830184613df6565b95945050505050565b6001600160a01b03831681526040602082018190526000906102ce90830184613af5565b60006020828403121561463957600080fd5b5051919050565b60008261464f5761464f6142ee565b600160ff1b82146000198414161561466957614669614091565b500590565b6146788185613abd565b60606020820152600061468e6060830185613af5565b8281036040840152610f4f8185613df6565b601f8211156146e8576000816000526020600020601f850160051c810160208610156146c95750805b601f850160051c820191505b8181101561325c578281556001016146d5565b505050565b815167ffffffffffffffff81111561470757614707613b9a565b61471b816147158454613dc2565b846146a0565b602080601f83116001811461475057600084156147385750858301515b600019600386901b1c1916600185901b17855561325c565b600085815260208120601f198616915b8281101561477f57888601518255948401946001909101908401614760565b508582101561479d5787850151600019600388901b60f8161c191681555b5050505050600190811b01905550565b8082018082111561011657610116614091565b6000826147cf576147cf6142ee565b500490565b6000826147e3576147e36142ee565b500690565b80820260008212600160ff1b8414161561480457614804614091565b818105831482151761011657610116614091565b600069ffffffffffffffffffff82168061436f5761436f614091565b60006001820161484657614846614091565b5060010190565b60006102b483836143bc565b808202811582820484141761011657610116614091565b60006020828403121561488257600080fd5b6102b4826144af565b60006020828403121561489d57600080fd5b6102b482613fd7565b60006001600160ff1b01820161484657614846614091565b6000600160ff1b82016148d3576148d3614091565b506000190190565b60008160020b627fffff1981036148f4576148f4614091565b60000392915050565b6000825161490f818460208701613ad1565b919091019291505056fea264697066735822122074f32fef384fdc296b0859f1c1f941c8e736c6cb972aa9e2b894956ebd6a80b364736f6c63430008160033","r":"0x1","s":"0x1","yParity":"0x0","hash":"0xbc73db80bf4b8784ba10a8910a0b7ef85f6846d102b41dd990969ea205335354"}}],"ommers":[]},{"header":{"parentHash":"0x026ae0c6ae91f186a9befa1ac8be30eea35e30e77de51a731085221e5cd39209","sha3Uncles":"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347","miner":"0x0000000000000000000000000000000000000000","stateRoot":"0xb6003e7ba07a15a9e35f63daa484728ec4ceeded0c4d10ac1b04e9552d412b3c","transactionsRoot":"0x6e4969a136061ca7a390d12830d47a151585325a8d396819fb2b958ff85e9f8f","receiptsRoot":"0xc3e81df67d3e2a6c8345a954ef250cfcc41abcc2292a5aa263071124533fc9ad","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","difficulty":"0x0","number":"0x3","gasLimit":"0x1c9c380","gasUsed":"0x3c0f6","timestamp":"0x66b200ce","mixHash":"0x0000000000000000000000000000000000000000000000000000000000000000","nonce":"0x0000000000000000","baseFeePerGas":"0x18993a68","blobGasUsed":"0x0","excessBlobGas":"0x0","extraData":"0x"},"transactions":[{"EIP1559":{"chainId":"0x343a","nonce":"0x0","gas":"0x3c0f6","maxFeePerGas":"0x5d4285cd","maxPriorityFeePerGas":"0x3b9aca00","value":"0x0","accessList":[],"input":"0x608060405234801561001057600080fd5b50610380806100206000396000f3fe6080604052600080357fffffffff0000000000000000000000000000000000000000000000000000000016905060008160e01c610251565b60006379ba509782101561015e5781631627540c811461009857632a952b2d81146100b457633659cfe681146100d0576350c946fe81146100ec576353a47bb781146101085763625ca21c81146101245763718fe928811461014057610158565b7347d08dad17ccb558b3ea74b1a0e73a9cc804a9dc9150610158565b738138ef7cf908021d117e542120b7a390650161079150610158565b7347d08dad17ccb558b3ea74b1a0e73a9cc804a9dc9150610158565b738138ef7cf908021d117e542120b7a390650161079150610158565b7347d08dad17ccb558b3ea74b1a0e73a9cc804a9dc9150610158565b738138ef7cf908021d117e542120b7a390650161079150610158565b7347d08dad17ccb558b3ea74b1a0e73a9cc804a9dc91505b5061024c565b816379ba509781146101a657638da5cb5b81146101c25763aaf10f4281146101de5763c7f62cda81146101fa5763daa250be81146102165763deba1b9881146102325761024a565b7347d08dad17ccb558b3ea74b1a0e73a9cc804a9dc915061024a565b7347d08dad17ccb558b3ea74b1a0e73a9cc804a9dc915061024a565b7347d08dad17ccb558b3ea74b1a0e73a9cc804a9dc915061024a565b7347d08dad17ccb558b3ea74b1a0e73a9cc804a9dc915061024a565b738138ef7cf908021d117e542120b7a39065016107915061024a565b738138ef7cf908021d117e542120b7a3906501610791505b505b919050565b61025a81610037565b915050600073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff16036102ce57816040517fc2a825f50000000000000000000000000000000000000000000000000000000081526004016102c5919061032f565b60405180910390fd5b3660008037600080366000845af43d6000803e80600081146102ef573d6000f35b3d6000fd5b60007fffffffff0000000000000000000000000000000000000000000000000000000082169050919050565b610329816102f4565b82525050565b60006020820190506103446000830184610320565b9291505056fea264697066735822122017a4b7fdaaab3897a7b47abaed8d2ee92d558883d3bb2a8454f9601b2ab2c3db64736f6c63430008150033","r":"0x1","s":"0x1","yParity":"0x0","hash":"0x2476e039803622aeb040f924f04c493f559aed3d6c9372ab405cb33c8c695328"}}],"ommers":[]},{"header":{"parentHash":"0x3d22100ac0ee8d5cde334f7f926191a861b0648971ebc179547df28a0224c6d0","sha3Uncles":"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347","miner":"0x0000000000000000000000000000000000000000","stateRoot":"0x9511d4711e5c30a72b0bff38a261daa75dcc5ba8b772d970a5c742244b4c861b","transactionsRoot":"0xba5fff578d3d6c2cd63acbe9bca353eaa6fe22a5c408956eff49106e0a96c507","receiptsRoot":"0xbae111f01cb07677e3a8c5031546138407c01bc964d3493d732dc4edf47d36d3","logsBloom":"0x00000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000020000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000001000000000000000000000400000001000010000000000000000000000000000000000000000000000000000000000000000000000000","difficulty":"0x0","number":"0x5","gasLimit":"0x1c9c380","gasUsed":"0xcae7","timestamp":"0x66b200cb","mixHash":"0x0000000000000000000000000000000000000000000000000000000000000000","nonce":"0x0000000000000000","baseFeePerGas":"0x12e09c7a","blobGasUsed":"0x0","excessBlobGas":"0x0","extraData":"0x"},"transactions":[{"EIP1559":{"chainId":"0x343a","nonce":"0x0","gas":"0xcc4d","maxFeePerGas":"0x557e5ec4","maxPriorityFeePerGas":"0x3b9aca00","to":"0x83a0444b93927c3afcbe46e522280390f748e171","value":"0x0","accessList":[],"input":"0x3659cfe6000000000000000000000000108f53faf774d7c4c56f5bce9ca6e605ce8aeadd","r":"0x1","s":"0x1","yParity":"0x0","hash":"0xf88e7b19ee347145c257e0cf7ac4ecc2bae83ca79d7edaa231e71d3213aeb151"}}],"ommers":[]},{"header":{"parentHash":"0x08abe6e453727534d8dd708843a7522b7d500338bdfe2402ca105dcdb05eebe9","sha3Uncles":"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347","miner":"0x0000000000000000000000000000000000000000","stateRoot":"0x9c8eaf493f8b4edce2ba1647343eadcc0989cf461e712c0a6253ff2ca1842bb7","transactionsRoot":"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421","receiptsRoot":"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","difficulty":"0x0","number":"0x1","gasLimit":"0x1c9c380","gasUsed":"0x0","timestamp":"0x66b200ca","mixHash":"0x0000000000000000000000000000000000000000000000000000000000000000","nonce":"0x0000000000000000","baseFeePerGas":"0x3b9aca00","blobGasUsed":"0x0","excessBlobGas":"0x0","extraData":"0x"},"transactions":[],"ommers":[]},{"header":{"parentHash":"0xdd07c07470e1deff3749831f0f1ad8d4b6e35505e83b3c6ea14181716197cd8a","sha3Uncles":"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347","miner":"0x0000000000000000000000000000000000000000","stateRoot":"0x29aa352e71b139e83b397bdd3dcf9b65d74770edaf3a9624d0dbc4f96f868680","transactionsRoot":"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421","receiptsRoot":"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","difficulty":"0x0","number":"0x2","gasLimit":"0x1c9c380","gasUsed":"0x0","timestamp":"0x66b200cb","mixHash":"0x0000000000000000000000000000000000000000000000000000000000000000","nonce":"0x0000000000000000","baseFeePerGas":"0x24a1ab52","blobGasUsed":"0x0","excessBlobGas":"0x0","extraData":"0x"},"transactions":[],"ommers":[]},{"header":{"parentHash":"0x0000000000000000000000000000000000000000000000000000000000000000","sha3Uncles":"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347","miner":"0x0000000000000000000000000000000000000000","stateRoot":"0x0000000000000000000000000000000000000000000000000000000000000000","transactionsRoot":"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421","receiptsRoot":"0x0000000000000000000000000000000000000000000000000000000000000000","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","difficulty":"0x0","number":"0x0","gasLimit":"0x1c9c380","gasUsed":"0x0","timestamp":"0x66b200c9","mixHash":"0x0000000000000000000000000000000000000000000000000000000000000000","nonce":"0x0000000000000000","baseFeePerGas":"0x3b9aca00","blobGasUsed":"0x0","excessBlobGas":"0x0","extraData":"0x"},"transactions":[],"ommers":[]},{"header":{"parentHash":"0xf6930be4847cac5017bbcbec2756eed19f36b4196526a98a88e311c296e3a9be","sha3Uncles":"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347","miner":"0x0000000000000000000000000000000000000000","stateRoot":"0x29aa352e71b139e83b397bdd3dcf9b65d74770edaf3a9624d0dbc4f96f868680","transactionsRoot":"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421","receiptsRoot":"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","difficulty":"0x0","number":"0x1","gasLimit":"0x1c9c380","gasUsed":"0x0","timestamp":"0x66b200cc","mixHash":"0x0000000000000000000000000000000000000000000000000000000000000000","nonce":"0x0000000000000000","baseFeePerGas":"0x200d75e8","blobGasUsed":"0x0","excessBlobGas":"0x0","extraData":"0x"},"transactions":[],"ommers":[]},{"header":{"parentHash":"0x08abe6e453727534d8dd708843a7522b7d500338bdfe2402ca105dcdb05eebe9","sha3Uncles":"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347","miner":"0x0000000000000000000000000000000000000000","stateRoot":"0xb6003e7ba07a15a9e35f63daa484728ec4ceeded0c4d10ac1b04e9552d412b3c","transactionsRoot":"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421","receiptsRoot":"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","difficulty":"0x0","number":"0x4","gasLimit":"0x1c9c380","gasUsed":"0x0","timestamp":"0x66b200ca","mixHash":"0x0000000000000000000000000000000000000000000000000000000000000000","nonce":"0x0000000000000000","baseFeePerGas":"0x1592fbf9","blobGasUsed":"0x0","excessBlobGas":"0x0","extraData":"0x"},"transactions":[],"ommers":[]},{"header":{"parentHash":"0x149d41e3b89d8324cef3feff98ef308e97bafe8745cc8461c60172bc7d4c44ba","sha3Uncles":"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347","miner":"0x0000000000000000000000000000000000000000","stateRoot":"0x510f2275449c013534a25ad0b13c867caf720947b68bcbcd4863f7b172a5d023","transactionsRoot":"0x0b44110186e52ff0ceb6b0776ca2992c94144a4ed712eef65ea038260ef0fcc7","receiptsRoot":"0xc2823b8eb4730d9f2657137cc2ddc2c4f22ab68e0ab826236cf6a1551ca2b3a5","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","difficulty":"0x0","number":"0x2","gasLimit":"0x1c9c380","gasUsed":"0xe61f9","timestamp":"0x66b200cb","mixHash":"0x0000000000000000000000000000000000000000000000000000000000000000","nonce":"0x0000000000000000","baseFeePerGas":"0x342770c0","blobGasUsed":"0x0","excessBlobGas":"0x0","extraData":"0x"},"transactions":[{"EIP1559":{"chainId":"0x343a","nonce":"0x0","gas":"0xe94d1","maxFeePerGas":"0x83215600","maxPriorityFeePerGas":"0x3b9aca00","to":"0x4e59b44847b379578588920ca78fbf26c0b4956c","value":"0x0","accessList":[],"input":"0x4786e4342646b3ba97c1790b6cf5a55087a36240b22570f5d3a5d6bcc929d93b608060405234801561001057600080fd5b5060008061002661006d60201b61081b1760201c565b60000160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555050610141565b60008060405160200161007f90610121565b6040516020818303038152906040528051906020012090508091505090565b600082825260208201905092915050565b7f696f2e73796e7468657469782e636f72652d636f6e7472616374732e4f776e6160008201527f626c650000000000000000000000000000000000000000000000000000000000602082015250565b600061010b60238361009e565b9150610116826100af565b604082019050919050565b6000602082019050818103600083015261013a816100fe565b9050919050565b611000806101506000396000f3fe608060405234801561001057600080fd5b50600436106100885760003560e01c806379ba50971161005b57806379ba5097146100ed5780638da5cb5b146100f7578063aaf10f4214610115578063c7f62cda1461013357610088565b80631627540c1461008d5780633659cfe6146100a957806353a47bb7146100c5578063718fe928146100e3575b600080fd5b6100a760048036038101906100a29190610d25565b61014f565b005b6100c360048036038101906100be9190610d25565b6102d0565b005b6100cd6102e4565b6040516100da9190610d61565b60405180910390f35b6100eb610317565b005b6100f56103fe565b005b6100ff61058b565b60405161010c9190610d61565b60405180910390f35b61011d6105be565b60405161012a9190610d61565b60405180910390f35b61014d60048036038101906101489190610d25565b6105f1565b005b61015761084c565b600061016161081b565b9050600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff16036101c9576040517fd92e233d00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b8060010160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1603610252576040517fa88ee57700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b818160010160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055507f906a1c6bd7e3091ea86693dd029a831c19049ce77f1dce2ce0bab1cacbabce22826040516102c49190610d61565b60405180910390a15050565b6102d861084c565b6102e1816108c5565b50565b60006102ee61081b565b60010160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16905090565b600061032161081b565b90503373ffffffffffffffffffffffffffffffffffffffff168160010160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16146103b757336040517fa0e5a0d70000000000000000000000000000000000000000000000000000000081526004016103ae9190610d61565b60405180910390fd5b60008160010160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555050565b600061040861081b565b905060008160010160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1690508073ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16146104a357336040517fa0e5a0d700000000000000000000000000000000000000000000000000000000815260040161049a9190610d61565b60405180910390fd5b7fb532073b38c83145e3e5135377a08bf9aab55bc0fd7c1179cd4fb995d2a5159c8260000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16826040516104f8929190610d7c565b60405180910390a1808260000160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555060008260010160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055505050565b600061059561081b565b60000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16905090565b60006105c8610b05565b60000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16905090565b60006105fb610b05565b905060018160000160146101000a81548160ff02191690831515021790555060008160000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff169050828260000160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555060008373ffffffffffffffffffffffffffffffffffffffff163073ffffffffffffffffffffffffffffffffffffffff16633659cfe6846040516024016106cc9190610d61565b604051602081830303815290604052915060e01b6020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff838183161783525050505060405161071b9190610e16565b600060405180830381855af49150503d8060008114610756576040519150601f19603f3d011682016040523d82523d6000602084013e61075b565b606091505b505090508015806107c357508173ffffffffffffffffffffffffffffffffffffffff16610786610b05565b60000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1614155b156107fa576040517fa1cfa5a800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60008360000160146101000a81548160ff0219169083151502179055600080fd5b60008060405160200161082d90610eb0565b6040516020818303038152906040528051906020012090508091505090565b610854610b36565b73ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16146108c357336040517f8e4a23d60000000000000000000000000000000000000000000000000000000081526004016108ba9190610d61565b60405180910390fd5b565b600073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff160361092b576040517fd92e233d00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b61093481610b69565b61097557806040517f8a8b41ec00000000000000000000000000000000000000000000000000000000815260040161096c9190610d61565b60405180910390fd5b600061097f610b05565b90508060000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1603610a0a576040517fa88ee57700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b8060000160149054906101000a900460ff16158015610a2e5750610a2d82610b7c565b5b15610a7057816040517f15504301000000000000000000000000000000000000000000000000000000008152600401610a679190610d61565b60405180910390fd5b818160000160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055503073ffffffffffffffffffffffffffffffffffffffff167f5d611f318680d00598bb735d61bacf0c514c6b50e1e5ad30040a4df2b12791c783604051610af99190610d61565b60405180910390a25050565b600080604051602001610b1790610f42565b6040516020818303038152906040528051906020012090508091505090565b6000610b4061081b565b60000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16905090565b600080823b905060008111915050919050565b60008060003073ffffffffffffffffffffffffffffffffffffffff163073ffffffffffffffffffffffffffffffffffffffff1663c7f62cda86604051602401610bc59190610d61565b604051602081830303815290604052915060e01b6020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff8381831617835250505050604051610c149190610e16565b600060405180830381855af49150503d8060008114610c4f576040519150601f19603f3d011682016040523d82523d6000602084013e610c54565b606091505b509150915081158015610cb9575063a1cfa5a860e01b604051602001610c7a9190610faf565b6040516020818303038152906040528051906020012081604051602001610ca19190610e16565b60405160208183030381529060405280519060200120145b92505050919050565b600080fd5b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b6000610cf282610cc7565b9050919050565b610d0281610ce7565b8114610d0d57600080fd5b50565b600081359050610d1f81610cf9565b92915050565b600060208284031215610d3b57610d3a610cc2565b5b6000610d4984828501610d10565b91505092915050565b610d5b81610ce7565b82525050565b6000602082019050610d766000830184610d52565b92915050565b6000604082019050610d916000830185610d52565b610d9e6020830184610d52565b9392505050565b600081519050919050565b600081905092915050565b60005b83811015610dd9578082015181840152602081019050610dbe565b60008484015250505050565b6000610df082610da5565b610dfa8185610db0565b9350610e0a818560208601610dbb565b80840191505092915050565b6000610e228284610de5565b915081905092915050565b600082825260208201905092915050565b7f696f2e73796e7468657469782e636f72652d636f6e7472616374732e4f776e6160008201527f626c650000000000000000000000000000000000000000000000000000000000602082015250565b6000610e9a602383610e2d565b9150610ea582610e3e565b604082019050919050565b60006020820190508181036000830152610ec981610e8d565b9050919050565b7f696f2e73796e7468657469782e636f72652d636f6e7472616374732e50726f7860008201527f7900000000000000000000000000000000000000000000000000000000000000602082015250565b6000610f2c602183610e2d565b9150610f3782610ed0565b604082019050919050565b60006020820190508181036000830152610f5b81610f1f565b9050919050565b60007fffffffff0000000000000000000000000000000000000000000000000000000082169050919050565b6000819050919050565b610fa9610fa482610f62565b610f8e565b82525050565b6000610fbb8284610f98565b6004820191508190509291505056fea264697066735822122023a7c33d7b91dce35ffbcf8837693364ab22a3905d0fc00016833e5fac45ca2f64736f6c63430008110033","r":"0x1","s":"0x1","yParity":"0x0","hash":"0x4feae6769d748b4f0f7c9bf21d782236c88f13906789a3ec602961296e4c3e43"}}],"ommers":[]},{"header":{"parentHash":"0xb3535af5103fd1c2bbd6dc7ff23f0799037a6542c231ebcb85abd776560fa512","sha3Uncles":"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347","miner":"0x0000000000000000000000000000000000000000","stateRoot":"0x23d74fb99ff6e42cbb5c33f92b078e37be6af2b6092459b103ff7059a6517ebc","transactionsRoot":"0x9eab45eca206fe11c107ea985c7d02fcfa442836aea3e04ba11dc4df587d5aa6","receiptsRoot":"0xe25abcfa973db8c55f73292137c626430de130a382ad4466337fefb0f7c8fde0","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","difficulty":"0x0","number":"0x2","gasLimit":"0x1c9c380","gasUsed":"0x3ce3f","timestamp":"0x66b200cd","mixHash":"0x0000000000000000000000000000000000000000000000000000000000000000","nonce":"0x0000000000000000","baseFeePerGas":"0x1c0bc72b","blobGasUsed":"0x0","excessBlobGas":"0x0","extraData":"0x"},"transactions":[{"EIP1559":{"chainId":"0x343a","nonce":"0x0","gas":"0x3d8a8","maxFeePerGas":"0x6211577c","maxPriorityFeePerGas":"0x3b9aca00","to":"0x4e59b44847b379578588920ca78fbf26c0b4956c","value":"0x0","accessList":[],"input":"0x4786e4342646b3ba97c1790b6cf5a55087a36240b22570f5d3a5d6bcc929d93b608060405234801561001057600080fd5b5060405161068538038061068583398181016040528101906100329190610275565b818181600073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff160361009b576040517fd92e233d00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6100ae8161019d60201b61004f1760201c565b6100ef57806040517f8a8b41ec0000000000000000000000000000000000000000000000000000000081526004016100e691906102c4565b60405180910390fd5b806100fe6101b060201b60201c565b60000160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555050806101536101e160201b6100621760201c565b60000160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555050505050610414565b600080823b905060008111915050919050565b6000806040516020016101c290610362565b6040516020818303038152906040528051906020012090508091505090565b6000806040516020016101f3906103f4565b6040516020818303038152906040528051906020012090508091505090565b600080fd5b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b600061024282610217565b9050919050565b61025281610237565b811461025d57600080fd5b50565b60008151905061026f81610249565b92915050565b6000806040838503121561028c5761028b610212565b5b600061029a85828601610260565b92505060206102ab85828601610260565b9150509250929050565b6102be81610237565b82525050565b60006020820190506102d960008301846102b5565b92915050565b600082825260208201905092915050565b7f696f2e73796e7468657469782e636f72652d636f6e7472616374732e50726f7860008201527f7900000000000000000000000000000000000000000000000000000000000000602082015250565b600061034c6021836102df565b9150610357826102f0565b604082019050919050565b6000602082019050818103600083015261037b8161033f565b9050919050565b7f696f2e73796e7468657469782e636f72652d636f6e7472616374732e4f776e6160008201527f626c650000000000000000000000000000000000000000000000000000000000602082015250565b60006103de6023836102df565b91506103e982610382565b604082019050919050565b6000602082019050818103600083015261040d816103d1565b9050919050565b610262806104236000396000f3fe6080604052366100135761001161001d565b005b61001b61001d565b005b6000610027610093565b90503660008037600080366000845af43d6000803e806000811461004a573d6000f35b3d6000fd5b600080823b905060008111915050919050565b6000806040516020016100749061017a565b6040516020818303038152906040528051906020012090508091505090565b600061009d6100c6565b60000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16905090565b6000806040516020016100d89061020c565b6040516020818303038152906040528051906020012090508091505090565b600082825260208201905092915050565b7f696f2e73796e7468657469782e636f72652d636f6e7472616374732e4f776e6160008201527f626c650000000000000000000000000000000000000000000000000000000000602082015250565b60006101646023836100f7565b915061016f82610108565b604082019050919050565b6000602082019050818103600083015261019381610157565b9050919050565b7f696f2e73796e7468657469782e636f72652d636f6e7472616374732e50726f7860008201527f7900000000000000000000000000000000000000000000000000000000000000602082015250565b60006101f66021836100f7565b91506102018261019a565b604082019050919050565b60006020820190508181036000830152610225816101e9565b905091905056fea2646970667358221220800da1f73cebd5e4afa07496d9bca6b6c4f526bdd3f4014ec15c70fe3a1c441364736f6c6343000811003300000000000000000000000047d08dad17ccb558b3ea74b1a0e73a9cc804a9dc000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb92266","r":"0x1","s":"0x1","yParity":"0x0","hash":"0xb6794d5c7abed6f91d447e8efb72ef2580595a6d7c8dee57ba1dbb330970146a"}}],"ommers":[]},{"header":{"parentHash":"0x08abe6e453727534d8dd708843a7522b7d500338bdfe2402ca105dcdb05eebe9","sha3Uncles":"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347","miner":"0x0000000000000000000000000000000000000000","stateRoot":"0x510f2275449c013534a25ad0b13c867caf720947b68bcbcd4863f7b172a5d023","transactionsRoot":"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421","receiptsRoot":"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","difficulty":"0x0","number":"0x3","gasLimit":"0x1c9c380","gasUsed":"0x0","timestamp":"0x66b200ca","mixHash":"0x0000000000000000000000000000000000000000000000000000000000000000","nonce":"0x0000000000000000","baseFeePerGas":"0x29dd5614","blobGasUsed":"0x0","excessBlobGas":"0x0","extraData":"0x"},"transactions":[],"ommers":[]}]} \ No newline at end of file diff --git a/crates/anvil/test-data/state-dump-legacy.json b/crates/anvil/test-data/state-dump-legacy.json index 273442701292e..26fa019b20fb5 100644 --- a/crates/anvil/test-data/state-dump-legacy.json +++ b/crates/anvil/test-data/state-dump-legacy.json @@ -1 +1 @@ -{"block":{"number":"0x2","coinbase":"0x0000000000000000000000000000000000000000","timestamp":"0x66cdc823","gas_limit":"0x1c9c380","basefee":"0x342a1c58","difficulty":"0x0","prevrandao":"0xb92480171c0235f8c6710a4047d7ee14a3be58c630839fb4422826ff3a013e44","blob_excess_gas_and_price":{"excess_blob_gas":0,"blob_gasprice":1}},"accounts":{"0x0000000000000000000000000000000000000000":{"nonce":0,"balance":"0xa410","code":"0x","storage":{}},"0x14dc79964da2c08b23698b3d3cc7ca32193d9955":{"nonce":0,"balance":"0x21e19e0c9bab2400000","code":"0x","storage":{}},"0x15d34aaf54267db7d7c367839aaf71a00a2c6a65":{"nonce":0,"balance":"0x21e19e0c9bab2400000","code":"0x","storage":{}},"0x23618e81e3f5cdf7f54c3d65f7fbc0abf5b21e8f":{"nonce":0,"balance":"0x21e19e0c9bab2400000","code":"0x","storage":{}},"0x3c44cdddb6a900fa2b585dd299e03d12fa4293bc":{"nonce":0,"balance":"0x21e19e0c9bab2400000","code":"0x","storage":{}},"0x4e59b44847b379578588920ca78fbf26c0b4956c":{"nonce":0,"balance":"0x0","code":"0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe03601600081602082378035828234f58015156039578182fd5b8082525050506014600cf3","storage":{}},"0x70997970c51812dc3a010c7d01b50e0d17dc79c8":{"nonce":1,"balance":"0x21e19e0b90393da9b38","code":"0x","storage":{}},"0x90f79bf6eb2c4f870365e785982e1f101e93b906":{"nonce":0,"balance":"0x21e19e0c9bab2400000","code":"0x","storage":{}},"0x976ea74026e726554db657fa54763abd0c3a0aa9":{"nonce":0,"balance":"0x21e19e0c9bab2400000","code":"0x","storage":{}},"0x9965507d1a55bcc2695c58ba16fb37d819b0a4dc":{"nonce":0,"balance":"0x21e19e0c9bab2400000","code":"0x","storage":{}},"0xa0ee7a142d267c1f36714e4a8f75612f20a79720":{"nonce":0,"balance":"0x21e19e0c9bab2400000","code":"0x","storage":{}},"0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266":{"nonce":1,"balance":"0x21e19e0b6a140b55df8","code":"0x","storage":{}}},"best_block_number":"0x2","blocks":[{"header":{"parentHash":"0xceb0fe420d6f14a8eeec4319515b89acbb0bb4861cad9983d529ab4b1e4af929","sha3Uncles":"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347","miner":"0x0000000000000000000000000000000000000000","stateRoot":"0xe1423fd180478ab4fd05a7103277d64496b15eb914ecafe71eeec871b552efd1","transactionsRoot":"0x2b5598ef261e5f88e4303bb2b3986b3d5c0ebf4cd9977daebccae82a6469b988","receiptsRoot":"0xf78dfb743fbd92ade140711c8bbc542b5e307f0ab7984eff35d751969fe57efa","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","difficulty":"0x0","number":"0x2","gasLimit":"0x1c9c380","gasUsed":"0x5208","timestamp":"0x66cdc823","mixHash":"0x0000000000000000000000000000000000000000000000000000000000000000","nonce":"0x0000000000000000","baseFeePerGas":"0x342a1c58","blobGasUsed":"0x0","excessBlobGas":"0x0","extraData":"0x"},"transactions":[{"EIP1559":{"chainId":"0x7a69","nonce":"0x0","gas":"0x5209","maxFeePerGas":"0x77359401","maxPriorityFeePerGas":"0x1","to":"0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266","value":"0x0","accessList":[],"input":"0x","r":"0x85c2794a580da137e24ccc823b45ae5cea99371ae23ee13860fcc6935f8305b0","s":"0x41de7fa4121dab284af4453d30928241208bafa90cdb701fe9bc7054759fe3cd","yParity":"0x0","hash":"0x8c9b68e8947ace33028dba167354fde369ed7bbe34911b772d09b3c64b861515"}}],"ommers":[]},{"header":{"parentHash":"0x0000000000000000000000000000000000000000000000000000000000000000","sha3Uncles":"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347","miner":"0x0000000000000000000000000000000000000000","stateRoot":"0x0000000000000000000000000000000000000000000000000000000000000000","transactionsRoot":"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421","receiptsRoot":"0x0000000000000000000000000000000000000000000000000000000000000000","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","difficulty":"0x0","number":"0x0","gasLimit":"0x1c9c380","gasUsed":"0x0","timestamp":"0x66cdc80e","mixHash":"0x0000000000000000000000000000000000000000000000000000000000000000","nonce":"0x0000000000000000","baseFeePerGas":"0x3b9aca00","blobGasUsed":"0x0","excessBlobGas":"0x0","extraData":"0x"},"transactions":[],"ommers":[]},{"header":{"parentHash":"0xa00dc0c9ee9a888e67ea32d8772f8cc28eff62448c9ec985ee941fcbc921ba59","sha3Uncles":"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347","miner":"0x0000000000000000000000000000000000000000","stateRoot":"0x6e5f60b37eeaece7dedfc42cc394731a0ae3ed3d3be93c402780b2e23e141175","transactionsRoot":"0x9ceaeb1b16b924afbf4bf4df4c2c49dc9cfbe23ac7a40bf26a704158ea2d352f","receiptsRoot":"0xf78dfb743fbd92ade140711c8bbc542b5e307f0ab7984eff35d751969fe57efa","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","difficulty":"0x0","number":"0x1","gasLimit":"0x1c9c380","gasUsed":"0x5208","timestamp":"0x66cdc814","mixHash":"0x0000000000000000000000000000000000000000000000000000000000000000","nonce":"0x0000000000000000","baseFeePerGas":"0x3b9aca00","blobGasUsed":"0x0","excessBlobGas":"0x0","extraData":"0x"},"transactions":[{"EIP1559":{"chainId":"0x7a69","nonce":"0x0","gas":"0x5209","maxFeePerGas":"0x77359401","maxPriorityFeePerGas":"0x1","to":"0x70997970c51812dc3a010c7d01b50e0d17dc79c8","value":"0x0","accessList":[],"input":"0x","r":"0x703a4b4d6dbff2fa2345df73263df2098faa7214863b5ec82c4c07162d87b853","s":"0x17dea762c4ce600ad1d9d2c9ae6dd35b9e526d03c875f868ad0792fd4fad72e0","yParity":"0x0","hash":"0xf8d5fb22350f52ae8c30cd7f6969eb73de849c8dc010f4215d4c5c24824fe2b3"}}],"ommers":[]}]} \ No newline at end of file +{"block":{"number":2,"beneficiary":"0x0000000000000000000000000000000000000000","timestamp":1724762147,"gas_limit":30000000,"basefee":875175000,"difficulty":"0x0","prevrandao":"0xb92480171c0235f8c6710a4047d7ee14a3be58c630839fb4422826ff3a013e44","blob_excess_gas_and_price":{"excess_blob_gas":0,"blob_gasprice":1}},"accounts":{"0x0000000000000000000000000000000000000000":{"nonce":0,"balance":"0xa410","code":"0x","storage":{}},"0x14dc79964da2c08b23698b3d3cc7ca32193d9955":{"nonce":0,"balance":"0x21e19e0c9bab2400000","code":"0x","storage":{}},"0x15d34aaf54267db7d7c367839aaf71a00a2c6a65":{"nonce":0,"balance":"0x21e19e0c9bab2400000","code":"0x","storage":{}},"0x23618e81e3f5cdf7f54c3d65f7fbc0abf5b21e8f":{"nonce":0,"balance":"0x21e19e0c9bab2400000","code":"0x","storage":{}},"0x3c44cdddb6a900fa2b585dd299e03d12fa4293bc":{"nonce":0,"balance":"0x21e19e0c9bab2400000","code":"0x","storage":{}},"0x4e59b44847b379578588920ca78fbf26c0b4956c":{"nonce":0,"balance":"0x0","code":"0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe03601600081602082378035828234f58015156039578182fd5b8082525050506014600cf3","storage":{}},"0x70997970c51812dc3a010c7d01b50e0d17dc79c8":{"nonce":1,"balance":"0x21e19e0b90393da9b38","code":"0x","storage":{}},"0x90f79bf6eb2c4f870365e785982e1f101e93b906":{"nonce":0,"balance":"0x21e19e0c9bab2400000","code":"0x","storage":{}},"0x976ea74026e726554db657fa54763abd0c3a0aa9":{"nonce":0,"balance":"0x21e19e0c9bab2400000","code":"0x","storage":{}},"0x9965507d1a55bcc2695c58ba16fb37d819b0a4dc":{"nonce":0,"balance":"0x21e19e0c9bab2400000","code":"0x","storage":{}},"0xa0ee7a142d267c1f36714e4a8f75612f20a79720":{"nonce":0,"balance":"0x21e19e0c9bab2400000","code":"0x","storage":{}},"0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266":{"nonce":1,"balance":"0x21e19e0b6a140b55df8","code":"0x","storage":{}}},"best_block_number":2,"blocks":[{"header":{"parentHash":"0xceb0fe420d6f14a8eeec4319515b89acbb0bb4861cad9983d529ab4b1e4af929","sha3Uncles":"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347","miner":"0x0000000000000000000000000000000000000000","stateRoot":"0xe1423fd180478ab4fd05a7103277d64496b15eb914ecafe71eeec871b552efd1","transactionsRoot":"0x2b5598ef261e5f88e4303bb2b3986b3d5c0ebf4cd9977daebccae82a6469b988","receiptsRoot":"0xf78dfb743fbd92ade140711c8bbc542b5e307f0ab7984eff35d751969fe57efa","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","difficulty":"0x0","number":"0x2","gasLimit":"0x1c9c380","gasUsed":"0x5208","timestamp":"0x66cdc823","mixHash":"0x0000000000000000000000000000000000000000000000000000000000000000","nonce":"0x0000000000000000","baseFeePerGas":"0x342a1c58","blobGasUsed":"0x0","excessBlobGas":"0x0","extraData":"0x"},"transactions":[{"EIP1559":{"chainId":"0x7a69","nonce":"0x0","gas":"0x5209","maxFeePerGas":"0x77359401","maxPriorityFeePerGas":"0x1","to":"0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266","value":"0x0","accessList":[],"input":"0x","r":"0x85c2794a580da137e24ccc823b45ae5cea99371ae23ee13860fcc6935f8305b0","s":"0x41de7fa4121dab284af4453d30928241208bafa90cdb701fe9bc7054759fe3cd","yParity":"0x0","hash":"0x8c9b68e8947ace33028dba167354fde369ed7bbe34911b772d09b3c64b861515"}}],"ommers":[]},{"header":{"parentHash":"0x0000000000000000000000000000000000000000000000000000000000000000","sha3Uncles":"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347","miner":"0x0000000000000000000000000000000000000000","stateRoot":"0x0000000000000000000000000000000000000000000000000000000000000000","transactionsRoot":"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421","receiptsRoot":"0x0000000000000000000000000000000000000000000000000000000000000000","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","difficulty":"0x0","number":"0x0","gasLimit":"0x1c9c380","gasUsed":"0x0","timestamp":"0x66cdc80e","mixHash":"0x0000000000000000000000000000000000000000000000000000000000000000","nonce":"0x0000000000000000","baseFeePerGas":"0x3b9aca00","blobGasUsed":"0x0","excessBlobGas":"0x0","extraData":"0x"},"transactions":[],"ommers":[]},{"header":{"parentHash":"0xa00dc0c9ee9a888e67ea32d8772f8cc28eff62448c9ec985ee941fcbc921ba59","sha3Uncles":"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347","miner":"0x0000000000000000000000000000000000000000","stateRoot":"0x6e5f60b37eeaece7dedfc42cc394731a0ae3ed3d3be93c402780b2e23e141175","transactionsRoot":"0x9ceaeb1b16b924afbf4bf4df4c2c49dc9cfbe23ac7a40bf26a704158ea2d352f","receiptsRoot":"0xf78dfb743fbd92ade140711c8bbc542b5e307f0ab7984eff35d751969fe57efa","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","difficulty":"0x0","number":"0x1","gasLimit":"0x1c9c380","gasUsed":"0x5208","timestamp":"0x66cdc814","mixHash":"0x0000000000000000000000000000000000000000000000000000000000000000","nonce":"0x0000000000000000","baseFeePerGas":"0x3b9aca00","blobGasUsed":"0x0","excessBlobGas":"0x0","extraData":"0x"},"transactions":[{"EIP1559":{"chainId":"0x7a69","nonce":"0x0","gas":"0x5209","maxFeePerGas":"0x77359401","maxPriorityFeePerGas":"0x1","to":"0x70997970c51812dc3a010c7d01b50e0d17dc79c8","value":"0x0","accessList":[],"input":"0x","r":"0x703a4b4d6dbff2fa2345df73263df2098faa7214863b5ec82c4c07162d87b853","s":"0x17dea762c4ce600ad1d9d2c9ae6dd35b9e526d03c875f868ad0792fd4fad72e0","yParity":"0x0","hash":"0xf8d5fb22350f52ae8c30cd7f6969eb73de849c8dc010f4215d4c5c24824fe2b3"}}],"ommers":[]}]} \ No newline at end of file diff --git a/crates/anvil/test-data/state-dump.json b/crates/anvil/test-data/state-dump.json index e868bf2efea51..4b7e66b17f56d 100644 --- a/crates/anvil/test-data/state-dump.json +++ b/crates/anvil/test-data/state-dump.json @@ -1 +1 @@ -{"block":{"number":"0x2","coinbase":"0x0000000000000000000000000000000000000000","timestamp":"0x66cdcc2b","gas_limit":"0x1c9c380","basefee":"0x342a1c58","difficulty":"0x0","prevrandao":"0xdb639d7f8af4f0ff2aa9cc49861820e72f5f8bfeeed677d1e3569f6b1625df4a","blob_excess_gas_and_price":{"excess_blob_gas":0,"blob_gasprice":1}},"accounts":{"0x0000000000000000000000000000000000000000":{"nonce":0,"balance":"0xa410","code":"0x","storage":{}},"0x14dc79964da2c08b23698b3d3cc7ca32193d9955":{"nonce":0,"balance":"0x21e19e0c9bab2400000","code":"0x","storage":{}},"0x15d34aaf54267db7d7c367839aaf71a00a2c6a65":{"nonce":0,"balance":"0x21e19e0c9bab2400000","code":"0x","storage":{}},"0x23618e81e3f5cdf7f54c3d65f7fbc0abf5b21e8f":{"nonce":0,"balance":"0x21e19e0c9bab2400000","code":"0x","storage":{}},"0x3c44cdddb6a900fa2b585dd299e03d12fa4293bc":{"nonce":0,"balance":"0x21e19e0c9bab2400000","code":"0x","storage":{}},"0x4e59b44847b379578588920ca78fbf26c0b4956c":{"nonce":0,"balance":"0x0","code":"0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe03601600081602082378035828234f58015156039578182fd5b8082525050506014600cf3","storage":{}},"0x70997970c51812dc3a010c7d01b50e0d17dc79c8":{"nonce":1,"balance":"0x21e19e0b90393da9b38","code":"0x","storage":{}},"0x90f79bf6eb2c4f870365e785982e1f101e93b906":{"nonce":0,"balance":"0x21e19e0c9bab2400000","code":"0x","storage":{}},"0x976ea74026e726554db657fa54763abd0c3a0aa9":{"nonce":0,"balance":"0x21e19e0c9bab2400000","code":"0x","storage":{}},"0x9965507d1a55bcc2695c58ba16fb37d819b0a4dc":{"nonce":0,"balance":"0x21e19e0c9bab2400000","code":"0x","storage":{}},"0xa0ee7a142d267c1f36714e4a8f75612f20a79720":{"nonce":0,"balance":"0x21e19e0c9bab2400000","code":"0x","storage":{}},"0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266":{"nonce":1,"balance":"0x21e19e0b6a140b55df8","code":"0x","storage":{}}},"best_block_number":"0x2","blocks":[{"header":{"parentHash":"0x0000000000000000000000000000000000000000000000000000000000000000","sha3Uncles":"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347","miner":"0x0000000000000000000000000000000000000000","stateRoot":"0x0000000000000000000000000000000000000000000000000000000000000000","transactionsRoot":"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421","receiptsRoot":"0x0000000000000000000000000000000000000000000000000000000000000000","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","difficulty":"0x0","number":"0x0","gasLimit":"0x1c9c380","gasUsed":"0x0","timestamp":"0x66cdcc25","mixHash":"0x0000000000000000000000000000000000000000000000000000000000000000","nonce":"0x0000000000000000","baseFeePerGas":"0x3b9aca00","blobGasUsed":"0x0","excessBlobGas":"0x0","extraData":"0x"},"transactions":[],"ommers":[]},{"header":{"parentHash":"0x3a52101c98a4319c419681131d3585d70a6cf13a9af25136be20d451eed5480a","sha3Uncles":"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347","miner":"0x0000000000000000000000000000000000000000","stateRoot":"0x6e5f60b37eeaece7dedfc42cc394731a0ae3ed3d3be93c402780b2e23e141175","transactionsRoot":"0x9ceaeb1b16b924afbf4bf4df4c2c49dc9cfbe23ac7a40bf26a704158ea2d352f","receiptsRoot":"0xf78dfb743fbd92ade140711c8bbc542b5e307f0ab7984eff35d751969fe57efa","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","difficulty":"0x0","number":"0x1","gasLimit":"0x1c9c380","gasUsed":"0x5208","timestamp":"0x66cdcc29","mixHash":"0x0000000000000000000000000000000000000000000000000000000000000000","nonce":"0x0000000000000000","baseFeePerGas":"0x3b9aca00","blobGasUsed":"0x0","excessBlobGas":"0x0","extraData":"0x"},"transactions":[{"transaction":{"EIP1559":{"chainId":"0x7a69","nonce":"0x0","gas":"0x5209","maxFeePerGas":"0x77359401","maxPriorityFeePerGas":"0x1","to":"0x70997970c51812dc3a010c7d01b50e0d17dc79c8","value":"0x0","accessList":[],"input":"0x","r":"0x703a4b4d6dbff2fa2345df73263df2098faa7214863b5ec82c4c07162d87b853","s":"0x17dea762c4ce600ad1d9d2c9ae6dd35b9e526d03c875f868ad0792fd4fad72e0","yParity":"0x0","hash":"0xf8d5fb22350f52ae8c30cd7f6969eb73de849c8dc010f4215d4c5c24824fe2b3"}},"impersonated_sender":null}],"ommers":[]},{"header":{"parentHash":"0x0d575f9ca968cd483549172245483a12343afc3cabef80f0fa39855b10b98c70","sha3Uncles":"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347","miner":"0x0000000000000000000000000000000000000000","stateRoot":"0xe1423fd180478ab4fd05a7103277d64496b15eb914ecafe71eeec871b552efd1","transactionsRoot":"0x2b5598ef261e5f88e4303bb2b3986b3d5c0ebf4cd9977daebccae82a6469b988","receiptsRoot":"0xf78dfb743fbd92ade140711c8bbc542b5e307f0ab7984eff35d751969fe57efa","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","difficulty":"0x0","number":"0x2","gasLimit":"0x1c9c380","gasUsed":"0x5208","timestamp":"0x66cdcc2b","mixHash":"0x0000000000000000000000000000000000000000000000000000000000000000","nonce":"0x0000000000000000","baseFeePerGas":"0x342a1c58","blobGasUsed":"0x0","excessBlobGas":"0x0","extraData":"0x"},"transactions":[{"transaction":{"EIP1559":{"chainId":"0x7a69","nonce":"0x0","gas":"0x5209","maxFeePerGas":"0x77359401","maxPriorityFeePerGas":"0x1","to":"0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266","value":"0x0","accessList":[],"input":"0x","r":"0x85c2794a580da137e24ccc823b45ae5cea99371ae23ee13860fcc6935f8305b0","s":"0x41de7fa4121dab284af4453d30928241208bafa90cdb701fe9bc7054759fe3cd","yParity":"0x0","hash":"0x8c9b68e8947ace33028dba167354fde369ed7bbe34911b772d09b3c64b861515"}},"impersonated_sender":null}],"ommers":[]}],"transactions":[{"info":{"transaction_hash":"0xf8d5fb22350f52ae8c30cd7f6969eb73de849c8dc010f4215d4c5c24824fe2b3","transaction_index":0,"from":"0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266","to":"0x70997970c51812dc3a010c7d01b50e0d17dc79c8","contract_address":null,"traces":[{"parent":null,"children":[],"idx":0,"trace":{"depth":0,"success":true,"caller":"0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266","address":"0x70997970c51812dc3a010c7d01b50e0d17dc79c8","maybe_precompile":null,"selfdestruct_refund_target":null,"selfdestruct_transferred_value":null,"kind":"CALL","value":"0x0","data":"0x","output":"0x","gas_used":0,"gas_limit":1,"status":"Stop","steps":[],"decoded":{"label":null,"return_data":null,"call_data":null}},"logs":[],"ordering":[]}],"exit":"Stop","out":"0x","nonce":0,"gas_used":21000},"receipt":{"type":"0x2","status":"0x1","cumulativeGasUsed":"0x5208","logs":[],"logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"},"block_hash":"0x0d575f9ca968cd483549172245483a12343afc3cabef80f0fa39855b10b98c70","block_number":1},{"info":{"transaction_hash":"0x8c9b68e8947ace33028dba167354fde369ed7bbe34911b772d09b3c64b861515","transaction_index":0,"from":"0x70997970c51812dc3a010c7d01b50e0d17dc79c8","to":"0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266","contract_address":null,"traces":[{"parent":null,"children":[],"idx":0,"trace":{"depth":0,"success":true,"caller":"0x70997970c51812dc3a010c7d01b50e0d17dc79c8","address":"0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266","maybe_precompile":null,"selfdestruct_refund_target":null,"selfdestruct_transferred_value":null,"kind":"CALL","value":"0x0","data":"0x","output":"0x","gas_used":0,"gas_limit":1,"status":"Stop","steps":[],"decoded":{"label":null,"return_data":null,"call_data":null}},"logs":[],"ordering":[]}],"exit":"Stop","out":"0x","nonce":0,"gas_used":21000},"receipt":{"type":"0x2","status":"0x1","cumulativeGasUsed":"0x5208","logs":[],"logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"},"block_hash":"0x1f435a603c1bf6d544a90156b572b96d7a1730028422d800839bae78bb3506d0","block_number":2}]} \ No newline at end of file +{"block":{"number":2,"beneficiary":"0x0000000000000000000000000000000000000000","timestamp":1724763179,"gas_limit":30000000,"basefee":875175000,"difficulty":"0x0","prevrandao":"0xdb639d7f8af4f0ff2aa9cc49861820e72f5f8bfeeed677d1e3569f6b1625df4a","blob_excess_gas_and_price":{"excess_blob_gas":0,"blob_gasprice":1}},"accounts":{"0x0000000000000000000000000000000000000000":{"nonce":0,"balance":"0xa410","code":"0x","storage":{}},"0x14dc79964da2c08b23698b3d3cc7ca32193d9955":{"nonce":0,"balance":"0x21e19e0c9bab2400000","code":"0x","storage":{}},"0x15d34aaf54267db7d7c367839aaf71a00a2c6a65":{"nonce":0,"balance":"0x21e19e0c9bab2400000","code":"0x","storage":{}},"0x23618e81e3f5cdf7f54c3d65f7fbc0abf5b21e8f":{"nonce":0,"balance":"0x21e19e0c9bab2400000","code":"0x","storage":{}},"0x3c44cdddb6a900fa2b585dd299e03d12fa4293bc":{"nonce":0,"balance":"0x21e19e0c9bab2400000","code":"0x","storage":{}},"0x4e59b44847b379578588920ca78fbf26c0b4956c":{"nonce":0,"balance":"0x0","code":"0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe03601600081602082378035828234f58015156039578182fd5b8082525050506014600cf3","storage":{}},"0x70997970c51812dc3a010c7d01b50e0d17dc79c8":{"nonce":1,"balance":"0x21e19e0b90393da9b38","code":"0x","storage":{}},"0x90f79bf6eb2c4f870365e785982e1f101e93b906":{"nonce":0,"balance":"0x21e19e0c9bab2400000","code":"0x","storage":{}},"0x976ea74026e726554db657fa54763abd0c3a0aa9":{"nonce":0,"balance":"0x21e19e0c9bab2400000","code":"0x","storage":{}},"0x9965507d1a55bcc2695c58ba16fb37d819b0a4dc":{"nonce":0,"balance":"0x21e19e0c9bab2400000","code":"0x","storage":{}},"0xa0ee7a142d267c1f36714e4a8f75612f20a79720":{"nonce":0,"balance":"0x21e19e0c9bab2400000","code":"0x","storage":{}},"0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266":{"nonce":1,"balance":"0x21e19e0b6a140b55df8","code":"0x","storage":{}}},"best_block_number":2,"blocks":[{"header":{"parentHash":"0x0000000000000000000000000000000000000000000000000000000000000000","sha3Uncles":"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347","miner":"0x0000000000000000000000000000000000000000","stateRoot":"0x0000000000000000000000000000000000000000000000000000000000000000","transactionsRoot":"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421","receiptsRoot":"0x0000000000000000000000000000000000000000000000000000000000000000","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","difficulty":"0x0","number":"0x0","gasLimit":"0x1c9c380","gasUsed":"0x0","timestamp":"0x66cdcc25","mixHash":"0x0000000000000000000000000000000000000000000000000000000000000000","nonce":"0x0000000000000000","baseFeePerGas":"0x3b9aca00","blobGasUsed":"0x0","excessBlobGas":"0x0","extraData":"0x"},"transactions":[],"ommers":[]},{"header":{"parentHash":"0x3a52101c98a4319c419681131d3585d70a6cf13a9af25136be20d451eed5480a","sha3Uncles":"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347","miner":"0x0000000000000000000000000000000000000000","stateRoot":"0x6e5f60b37eeaece7dedfc42cc394731a0ae3ed3d3be93c402780b2e23e141175","transactionsRoot":"0x9ceaeb1b16b924afbf4bf4df4c2c49dc9cfbe23ac7a40bf26a704158ea2d352f","receiptsRoot":"0xf78dfb743fbd92ade140711c8bbc542b5e307f0ab7984eff35d751969fe57efa","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","difficulty":"0x0","number":"0x1","gasLimit":"0x1c9c380","gasUsed":"0x5208","timestamp":"0x66cdcc29","mixHash":"0x0000000000000000000000000000000000000000000000000000000000000000","nonce":"0x0000000000000000","baseFeePerGas":"0x3b9aca00","blobGasUsed":"0x0","excessBlobGas":"0x0","extraData":"0x"},"transactions":[{"transaction":{"EIP1559":{"chainId":"0x7a69","nonce":"0x0","gas":"0x5209","maxFeePerGas":"0x77359401","maxPriorityFeePerGas":"0x1","to":"0x70997970c51812dc3a010c7d01b50e0d17dc79c8","value":"0x0","accessList":[],"input":"0x","r":"0x703a4b4d6dbff2fa2345df73263df2098faa7214863b5ec82c4c07162d87b853","s":"0x17dea762c4ce600ad1d9d2c9ae6dd35b9e526d03c875f868ad0792fd4fad72e0","yParity":"0x0","hash":"0xf8d5fb22350f52ae8c30cd7f6969eb73de849c8dc010f4215d4c5c24824fe2b3"}},"impersonated_sender":null}],"ommers":[]},{"header":{"parentHash":"0x0d575f9ca968cd483549172245483a12343afc3cabef80f0fa39855b10b98c70","sha3Uncles":"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347","miner":"0x0000000000000000000000000000000000000000","stateRoot":"0xe1423fd180478ab4fd05a7103277d64496b15eb914ecafe71eeec871b552efd1","transactionsRoot":"0x2b5598ef261e5f88e4303bb2b3986b3d5c0ebf4cd9977daebccae82a6469b988","receiptsRoot":"0xf78dfb743fbd92ade140711c8bbc542b5e307f0ab7984eff35d751969fe57efa","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","difficulty":"0x0","number":"0x2","gasLimit":"0x1c9c380","gasUsed":"0x5208","timestamp":"0x66cdcc2b","mixHash":"0x0000000000000000000000000000000000000000000000000000000000000000","nonce":"0x0000000000000000","baseFeePerGas":"0x342a1c58","blobGasUsed":"0x0","excessBlobGas":"0x0","extraData":"0x"},"transactions":[{"transaction":{"EIP1559":{"chainId":"0x7a69","nonce":"0x0","gas":"0x5209","maxFeePerGas":"0x77359401","maxPriorityFeePerGas":"0x1","to":"0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266","value":"0x0","accessList":[],"input":"0x","r":"0x85c2794a580da137e24ccc823b45ae5cea99371ae23ee13860fcc6935f8305b0","s":"0x41de7fa4121dab284af4453d30928241208bafa90cdb701fe9bc7054759fe3cd","yParity":"0x0","hash":"0x8c9b68e8947ace33028dba167354fde369ed7bbe34911b772d09b3c64b861515"}},"impersonated_sender":null}],"ommers":[]}],"transactions":[{"info":{"transaction_hash":"0xf8d5fb22350f52ae8c30cd7f6969eb73de849c8dc010f4215d4c5c24824fe2b3","transaction_index":0,"from":"0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266","to":"0x70997970c51812dc3a010c7d01b50e0d17dc79c8","contract_address":null,"traces":[{"parent":null,"children":[],"idx":0,"trace":{"depth":0,"success":true,"caller":"0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266","address":"0x70997970c51812dc3a010c7d01b50e0d17dc79c8","maybe_precompile":null,"selfdestruct_refund_target":null,"selfdestruct_transferred_value":null,"kind":"CALL","value":"0x0","data":"0x","output":"0x","gas_used":0,"gas_limit":1,"status":"Stop","steps":[],"decoded":{"label":null,"return_data":null,"call_data":null}},"logs":[],"ordering":[]}],"exit":"Stop","out":"0x","nonce":0,"gas_used":21000},"receipt":{"type":"0x2","status":"0x1","cumulativeGasUsed":"0x5208","logs":[],"logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"},"block_hash":"0x0d575f9ca968cd483549172245483a12343afc3cabef80f0fa39855b10b98c70","block_number":1},{"info":{"transaction_hash":"0x8c9b68e8947ace33028dba167354fde369ed7bbe34911b772d09b3c64b861515","transaction_index":0,"from":"0x70997970c51812dc3a010c7d01b50e0d17dc79c8","to":"0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266","contract_address":null,"traces":[{"parent":null,"children":[],"idx":0,"trace":{"depth":0,"success":true,"caller":"0x70997970c51812dc3a010c7d01b50e0d17dc79c8","address":"0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266","maybe_precompile":null,"selfdestruct_refund_target":null,"selfdestruct_transferred_value":null,"kind":"CALL","value":"0x0","data":"0x","output":"0x","gas_used":0,"gas_limit":1,"status":"Stop","steps":[],"decoded":{"label":null,"return_data":null,"call_data":null}},"logs":[],"ordering":[]}],"exit":"Stop","out":"0x","nonce":0,"gas_used":21000},"receipt":{"type":"0x2","status":"0x1","cumulativeGasUsed":"0x5208","logs":[],"logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"},"block_hash":"0x1f435a603c1bf6d544a90156b572b96d7a1730028422d800839bae78bb3506d0","block_number":2}]} \ No newline at end of file diff --git a/crates/anvil/tests/it/anvil.rs b/crates/anvil/tests/it/anvil.rs index 329caea844b45..721aa404b36e1 100644 --- a/crates/anvil/tests/it/anvil.rs +++ b/crates/anvil/tests/it/anvil.rs @@ -2,9 +2,10 @@ use alloy_consensus::EMPTY_ROOT_HASH; use alloy_eips::BlockNumberOrTag; +use alloy_hardforks::EthereumHardfork; use alloy_primitives::Address; use alloy_provider::Provider; -use anvil::{spawn, EthereumHardfork, NodeConfig}; +use anvil::{spawn, NodeConfig}; #[tokio::test(flavor = "multi_thread")] async fn test_can_change_mining_mode() { diff --git a/crates/anvil/tests/it/anvil_api.rs b/crates/anvil/tests/it/anvil_api.rs index 22179d505a9b6..47c63a12af048 100644 --- a/crates/anvil/tests/it/anvil_api.rs +++ b/crates/anvil/tests/it/anvil_api.rs @@ -6,6 +6,7 @@ use crate::{ utils::http_provider_with_signer, }; use alloy_consensus::{SignableTransaction, TxEip1559}; +use alloy_hardforks::EthereumHardfork; use alloy_network::{EthereumWallet, TransactionBuilder, TxSignerSync}; use alloy_primitives::{address, fixed_bytes, utils::Unit, Address, Bytes, TxKind, U256}; use alloy_provider::{ext::TxPoolApi, Provider}; @@ -21,7 +22,7 @@ use anvil::{ api::CLIENT_VERSION, backend::mem::{EXECUTOR, P256_DELEGATION_CONTRACT, P256_DELEGATION_RUNTIME_CODE}, }, - spawn, EthereumHardfork, NodeConfig, + spawn, NodeConfig, }; use anvil_core::{ eth::{ @@ -30,7 +31,7 @@ use anvil_core::{ }, types::{ReorgOptions, TransactionData}, }; -use foundry_evm::revm::primitives::SpecId; +use revm::primitives::hardfork::SpecId; use std::{ future::IntoFuture, str::FromStr, @@ -180,7 +181,7 @@ async fn can_impersonate_contract() { let res = provider.send_transaction(tx.clone()).await; res.unwrap_err(); - let greeting = greeter_contract.greet().call().await.unwrap()._0; + let greeting = greeter_contract.greet().call().await.unwrap(); assert_eq!("Hello World!", greeting); api.anvil_impersonate_account(impersonate).await.unwrap(); @@ -195,7 +196,7 @@ async fn can_impersonate_contract() { let res = provider.send_transaction(tx).await; res.unwrap_err(); - let greeting = greeter_contract.greet().call().await.unwrap()._0; + let greeting = greeter_contract.greet().call().await.unwrap(); assert_eq!("Hello World!", greeting); } @@ -430,12 +431,11 @@ async fn test_can_set_storage_bsc_fork() { let busd_contract = BUSD::new(busd_addr, &provider); - let BUSD::balanceOfReturn { _0 } = busd_contract + let balance = busd_contract .balanceOf(address!("0x0000000000000000000000000000000000000000")) .call() .await .unwrap(); - let balance = _0; assert_eq!(balance, U256::from(12345u64)); } @@ -449,7 +449,7 @@ async fn can_get_node_info() { let block_number = provider.get_block_number().await.unwrap(); let block = provider.get_block(BlockId::from(block_number)).await.unwrap().unwrap(); - let hard_fork: &str = SpecId::CANCUN.into(); + let hard_fork: &str = SpecId::PRAGUE.into(); let expected_node_info = NodeInfo { current_block_number: 0_u64, @@ -625,20 +625,16 @@ async fn test_fork_revert_call_latest_block_timestamp() { let multicall_contract = Multicall::new(address!("0xeefba1e63905ef1d7acba5a8513c70307c1ce441"), &provider); - let Multicall::getCurrentBlockTimestampReturn { timestamp } = - multicall_contract.getCurrentBlockTimestamp().call().await.unwrap(); + let timestamp = multicall_contract.getCurrentBlockTimestamp().call().await.unwrap(); assert_eq!(timestamp, U256::from(latest_block.header.timestamp)); - let Multicall::getCurrentBlockDifficultyReturn { difficulty } = - multicall_contract.getCurrentBlockDifficulty().call().await.unwrap(); + let difficulty = multicall_contract.getCurrentBlockDifficulty().call().await.unwrap(); assert_eq!(difficulty, U256::from(latest_block.header.difficulty)); - let Multicall::getCurrentBlockGasLimitReturn { gaslimit } = - multicall_contract.getCurrentBlockGasLimit().call().await.unwrap(); + let gaslimit = multicall_contract.getCurrentBlockGasLimit().call().await.unwrap(); assert_eq!(gaslimit, U256::from(latest_block.header.gas_limit)); - let Multicall::getCurrentBlockCoinbaseReturn { coinbase } = - multicall_contract.getCurrentBlockCoinbase().call().await.unwrap(); + let coinbase = multicall_contract.getCurrentBlockCoinbase().call().await.unwrap(); assert_eq!(coinbase, latest_block.header.beneficiary); } @@ -673,7 +669,7 @@ async fn can_remove_pool_transactions() { #[tokio::test(flavor = "multi_thread")] async fn test_reorg() { let (api, handle) = spawn(NodeConfig::test()).await; - let provider = handle.ws_provider(); + let provider = handle.http_provider(); let accounts = handle.dev_wallets().collect::>(); @@ -754,7 +750,7 @@ async fn test_reorg() { .await .unwrap(); api.anvil_reorg(ReorgOptions { depth: 3, tx_block_pairs: vec![] }).await.unwrap(); - let value = storage.getValue().call().await.unwrap()._0; + let value = storage.getValue().call().await.unwrap(); assert_eq!("initial value".to_string(), value); api.mine_one().await; diff --git a/crates/anvil/tests/it/api.rs b/crates/anvil/tests/it/api.rs index d1b9238813d14..ce807eed6aa88 100644 --- a/crates/anvil/tests/it/api.rs +++ b/crates/anvil/tests/it/api.rs @@ -243,7 +243,7 @@ async fn can_call_on_pending_block() { let block_number = BlockNumberOrTag::Number(anvil_block_number as u64); let block = api.block_by_number(block_number).await.unwrap().unwrap(); - let Multicall::getCurrentBlockTimestampReturn { timestamp: ret_timestamp, .. } = contract + let ret_timestamp = contract .getCurrentBlockTimestamp() .block(BlockId::number(anvil_block_number as u64)) .call() @@ -251,7 +251,7 @@ async fn can_call_on_pending_block() { .unwrap(); assert_eq!(block.header.timestamp, ret_timestamp.to::()); - let Multicall::getCurrentBlockGasLimitReturn { gaslimit: ret_gas_limit, .. } = contract + let ret_gas_limit = contract .getCurrentBlockGasLimit() .block(BlockId::number(anvil_block_number as u64)) .call() @@ -259,7 +259,7 @@ async fn can_call_on_pending_block() { .unwrap(); assert_eq!(block.header.gas_limit, ret_gas_limit.to::()); - let Multicall::getCurrentBlockCoinbaseReturn { coinbase: ret_coinbase, .. } = contract + let ret_coinbase = contract .getCurrentBlockCoinbase() .block(BlockId::number(anvil_block_number as u64)) .call() @@ -296,8 +296,7 @@ async fn can_call_with_undersized_max_fee_per_gas() { .from(wallet.address()) .call() .await - .unwrap() - ._0; + .unwrap(); assert_eq!(last_sender, Address::ZERO); } @@ -323,8 +322,7 @@ async fn can_call_with_state_override() { let balance = U256::from(42u64); let mut overrides = AddressHashMap::default(); overrides.insert(account, AccountOverride { balance: Some(balance), ..Default::default() }); - let result = - multicall_contract.getEthBalance(account).state(overrides).call().await.unwrap().balance; + let result = multicall_contract.getEthBalance(account).state(overrides).call().await.unwrap(); assert_eq!(result, balance); // Test the `state_diff` account override @@ -341,16 +339,16 @@ async fn can_call_with_state_override() { ); let last_sender = - simple_storage_contract.lastSender().state(HashMap::default()).call().await.unwrap()._0; + simple_storage_contract.lastSender().state(HashMap::default()).call().await.unwrap(); // No `sender` set without override assert_eq!(last_sender, Address::ZERO); let last_sender = - simple_storage_contract.lastSender().state(overrides.clone()).call().await.unwrap()._0; + simple_storage_contract.lastSender().state(overrides.clone()).call().await.unwrap(); // `sender` *is* set with override assert_eq!(last_sender, account); - let value = simple_storage_contract.getValue().state(overrides).call().await.unwrap()._0; + let value = simple_storage_contract.getValue().state(overrides).call().await.unwrap(); // `value` *is not* changed with state-diff assert_eq!(value, init_value); @@ -368,11 +366,11 @@ async fn can_call_with_state_override() { ); let last_sender = - simple_storage_contract.lastSender().state(overrides.clone()).call().await.unwrap()._0; + simple_storage_contract.lastSender().state(overrides.clone()).call().await.unwrap(); // `sender` *is* set with override assert_eq!(last_sender, account); - let value = simple_storage_contract.getValue().state(overrides).call().await.unwrap()._0; + let value = simple_storage_contract.getValue().state(overrides).call().await.unwrap(); // `value` *is* changed with state assert_eq!(value, ""); } diff --git a/crates/anvil/tests/it/eip4844.rs b/crates/anvil/tests/it/eip4844.rs index 09e58e37d51e0..2d713c3d955e3 100644 --- a/crates/anvil/tests/it/eip4844.rs +++ b/crates/anvil/tests/it/eip4844.rs @@ -1,15 +1,16 @@ use crate::utils::{http_provider, http_provider_with_signer}; use alloy_consensus::{SidecarBuilder, SimpleCoder, Transaction}; use alloy_eips::{ - eip4844::{BLOB_TX_MIN_BLOB_GASPRICE, DATA_GAS_PER_BLOB, MAX_DATA_GAS_PER_BLOCK}, + eip4844::{BLOB_TX_MIN_BLOB_GASPRICE, DATA_GAS_PER_BLOB, MAX_DATA_GAS_PER_BLOCK_DENCUN}, Typed2718, }; +use alloy_hardforks::EthereumHardfork; use alloy_network::{EthereumWallet, ReceiptResponse, TransactionBuilder, TransactionBuilder4844}; use alloy_primitives::{b256, Address, U256}; use alloy_provider::Provider; use alloy_rpc_types::{BlockId, TransactionRequest}; use alloy_serde::WithOtherFields; -use anvil::{spawn, EthereumHardfork, NodeConfig}; +use anvil::{spawn, NodeConfig}; #[tokio::test(flavor = "multi_thread")] async fn can_send_eip4844_transaction() { @@ -81,7 +82,7 @@ async fn can_send_multiple_blobs_in_one_tx() { let receipt = provider.send_transaction(tx).await.unwrap().get_receipt().await.unwrap(); - assert_eq!(receipt.blob_gas_used, Some(MAX_DATA_GAS_PER_BLOCK)); + assert_eq!(receipt.blob_gas_used, Some(MAX_DATA_GAS_PER_BLOCK_DENCUN)); assert_eq!(receipt.blob_gas_price, Some(0x1)); // 1 wei } diff --git a/crates/anvil/tests/it/eip7702.rs b/crates/anvil/tests/it/eip7702.rs index 85d45eb1c66b3..9424360119e4e 100644 --- a/crates/anvil/tests/it/eip7702.rs +++ b/crates/anvil/tests/it/eip7702.rs @@ -1,12 +1,13 @@ use crate::utils::http_provider; use alloy_consensus::{transaction::TxEip7702, SignableTransaction}; +use alloy_hardforks::EthereumHardfork; use alloy_network::{ReceiptResponse, TransactionBuilder, TxSignerSync}; use alloy_primitives::{bytes, U256}; -use alloy_provider::Provider; +use alloy_provider::{PendingTransactionConfig, Provider}; use alloy_rpc_types::{Authorization, TransactionRequest}; use alloy_serde::WithOtherFields; use alloy_signer::SignerSync; -use anvil::{spawn, EthereumHardfork, NodeConfig}; +use anvil::{spawn, NodeConfig}; #[tokio::test(flavor = "multi_thread")] async fn can_send_eip7702_tx() { @@ -76,3 +77,79 @@ async fn can_send_eip7702_tx() { assert_eq!(log.topics().len(), 0); assert_eq!(log.data().data, log_data); } + +#[tokio::test(flavor = "multi_thread")] +async fn can_send_eip7702_request() { + let node_config = NodeConfig::test().with_hardfork(Some(EthereumHardfork::Prague.into())); + let (api, handle) = spawn(node_config).await; + let provider = http_provider(&handle.http_endpoint()); + + let wallets = handle.dev_wallets().collect::>(); + + // deploy simple contract forwarding calldata to LOG0 + // PUSH7(CALLDATASIZE PUSH0 PUSH0 CALLDATACOPY CALLDATASIZE PUSH0 LOG0) PUSH0 MSTORE PUSH1(7) + // PUSH1(25) RETURN + let logger_bytecode = bytes!("66365f5f37365fa05f5260076019f3"); + + let eip1559_est = provider.estimate_eip1559_fees().await.unwrap(); + + let from = wallets[0].address(); + let tx = TransactionRequest::default() + .with_from(from) + .into_create() + .with_nonce(0) + .with_max_fee_per_gas(eip1559_est.max_fee_per_gas) + .with_max_priority_fee_per_gas(eip1559_est.max_priority_fee_per_gas) + .with_input(logger_bytecode); + + let receipt = provider + .send_transaction(WithOtherFields::new(tx)) + .await + .unwrap() + .get_receipt() + .await + .unwrap(); + + assert!(receipt.status()); + + let contract = receipt.contract_address.unwrap(); + let authorization = Authorization { + chain_id: U256::from(31337u64), + address: contract, + nonce: provider.get_transaction_count(from).await.unwrap(), + }; + let signature = wallets[0].sign_hash_sync(&authorization.signature_hash()).unwrap(); + let authorization = authorization.into_signed(signature); + + let log_data = bytes!("11112222"); + let tx = TxEip7702 { + max_fee_per_gas: eip1559_est.max_fee_per_gas, + max_priority_fee_per_gas: eip1559_est.max_priority_fee_per_gas, + gas_limit: 100000, + chain_id: 31337, + to: from, + input: bytes!("11112222"), + authorization_list: vec![authorization], + ..Default::default() + }; + + let sender = wallets[1].address(); + let request = TransactionRequest::from_transaction(tx).with_from(sender); + + api.anvil_impersonate_account(sender).await.unwrap(); + let txhash = api.send_transaction(WithOtherFields::new(request)).await.unwrap(); + + let txhash = provider + .watch_pending_transaction(PendingTransactionConfig::new(txhash)) + .await + .unwrap() + .await + .unwrap(); + + let receipt = provider.get_transaction_receipt(txhash).await.unwrap().unwrap(); + let log = &receipt.inner.inner.logs()[0]; + // assert that log was from EOA which signed authorization + assert_eq!(log.address(), from); + assert_eq!(log.topics().len(), 0); + assert_eq!(log.data().data, log_data); +} diff --git a/crates/anvil/tests/it/fork.rs b/crates/anvil/tests/it/fork.rs index 422bd2514cf6d..c0e2eba8181d2 100644 --- a/crates/anvil/tests/it/fork.rs +++ b/crates/anvil/tests/it/fork.rs @@ -11,6 +11,7 @@ use alloy_provider::Provider; use alloy_rpc_types::{ anvil::Forking, request::{TransactionInput, TransactionRequest}, + state::EvmOverrides, BlockId, BlockNumberOrTag, }; use alloy_serde::WithOtherFields; @@ -61,7 +62,7 @@ pub fn fork_config() -> NodeConfig { #[tokio::test(flavor = "multi_thread")] async fn test_fork_gas_limit_applied_from_config() { - let (api, _handle) = spawn(fork_config().with_gas_limit(Some(10_000_000_u128))).await; + let (api, _handle) = spawn(fork_config().with_gas_limit(Some(10_000_000))).await; assert_eq!(api.gas_limit(), uint!(10_000_000_U256)); } @@ -478,12 +479,12 @@ async fn can_deploy_greeter_on_fork() { let greeter_contract = Greeter::deploy(&provider, "Hello World!".to_string()).await.unwrap(); let greeting = greeter_contract.greet().call().await.unwrap(); - assert_eq!("Hello World!", greeting._0); + assert_eq!("Hello World!", greeting); let greeter_contract = Greeter::deploy(&provider, "Hello World!".to_string()).await.unwrap(); let greeting = greeter_contract.greet().call().await.unwrap(); - assert_eq!("Hello World!", greeting._0); + assert_eq!("Hello World!", greeting); } #[tokio::test(flavor = "multi_thread")] @@ -691,7 +692,7 @@ async fn test_fork_nft_set_approve_all() { let nouns = ERC721::new(nouns_addr, provider.clone()); let real_owner = nouns.ownerOf(token_id).call().await.unwrap(); - assert_eq!(real_owner._0, owner); + assert_eq!(real_owner, owner); let approval = nouns.setApprovalForAll(nouns_addr, true); let tx = TransactionRequest::default() .from(owner) @@ -704,13 +705,13 @@ async fn test_fork_nft_set_approve_all() { assert!(status); // transfer: impersonate real owner and transfer nft - api.anvil_impersonate_account(real_owner._0).await.unwrap(); + api.anvil_impersonate_account(real_owner).await.unwrap(); - api.anvil_set_balance(real_owner._0, U256::from(10000e18 as u64)).await.unwrap(); + api.anvil_set_balance(real_owner, U256::from(10000e18 as u64)).await.unwrap(); - let call = nouns.transferFrom(real_owner._0, signer, token_id); + let call = nouns.transferFrom(real_owner, signer, token_id); let tx = TransactionRequest::default() - .from(real_owner._0) + .from(real_owner) .to(nouns_addr) .with_input(call.calldata().to_owned()); let tx = WithOtherFields::new(tx); @@ -719,7 +720,7 @@ async fn test_fork_nft_set_approve_all() { assert!(status); let real_owner = nouns.ownerOf(token_id).call().await.unwrap(); - assert_eq!(real_owner._0, wallet.address()); + assert_eq!(real_owner, wallet.address()); } // @@ -870,7 +871,7 @@ async fn test_fork_call() { ..Default::default() }), None, - None, + EvmOverrides::default(), ) .await .unwrap(); @@ -1115,11 +1116,11 @@ async fn can_override_fork_chain_id() { Greeter::deploy(provider.clone(), "Hello World!".to_string()).await.unwrap(); let greeting = greeter_contract.greet().call().await.unwrap(); - assert_eq!("Hello World!", greeting._0); + assert_eq!("Hello World!", greeting); let greeter_contract = Greeter::deploy(provider.clone(), "Hello World!".to_string()).await.unwrap(); let greeting = greeter_contract.greet().call().await.unwrap(); - assert_eq!("Hello World!", greeting._0); + assert_eq!("Hello World!", greeting); let provider = handle.http_provider(); let chain_id = provider.get_chain_id().await.unwrap(); @@ -1313,7 +1314,7 @@ async fn test_fork_execution_reverted() { ..Default::default() }), Some(target.into()), - None, + EvmOverrides::default(), ) .await; @@ -1324,10 +1325,12 @@ async fn test_fork_execution_reverted() { // #[tokio::test(flavor = "multi_thread")] +#[ignore] async fn test_immutable_fork_transaction_hash() { use std::str::FromStr; // Fork to a block with a specific transaction + // let fork_tx_hash = TxHash::from_str("2ac736ce725d628ef20569a1bb501726b42b33f9d171f60b92b69de3ce705845") .unwrap(); @@ -1462,6 +1465,72 @@ async fn test_reset_dev_account_nonce() { assert!(receipt.status()); } +#[tokio::test(flavor = "multi_thread")] +async fn test_set_erc20_balance() { + let config: NodeConfig = fork_config(); + let address = config.genesis_accounts[0].address(); + let (api, handle) = spawn(config).await; + + let provider = handle.http_provider(); + + alloy_sol_types::sol! { + #[sol(rpc)] + contract ERC20 { + function balanceOf(address owner) public view returns (uint256); + } + } + let dai = address!("0x6B175474E89094C44Da98b954EedeAC495271d0F"); + let erc20 = ERC20::new(dai, provider); + let value = U256::from(500); + + api.anvil_deal_erc20(address, dai, value).await.unwrap(); + + let new_balance = erc20.balanceOf(address).call().await.unwrap(); + + assert_eq!(new_balance, value); +} + +#[tokio::test(flavor = "multi_thread")] +async fn test_set_erc20_allowance() { + let config: NodeConfig = fork_config(); + let owner = config.genesis_accounts[0].address(); + let spender = config.genesis_accounts[1].address(); + let (api, handle) = spawn(config).await; + + let provider = handle.http_provider(); + + alloy_sol_types::sol! { + #[sol(rpc)] + contract ERC20 { + function allowance(address owner, address spender) external view returns (uint256); + } + } + let dai = address!("0x6B175474E89094C44Da98b954EedeAC495271d0F"); + let erc20 = ERC20::new(dai, provider); + let value = U256::from(500); + + api.anvil_set_erc20_allowance(owner, spender, dai, value).await.unwrap(); + + let allowance = erc20.allowance(owner, spender).call().await.unwrap(); + assert_eq!(allowance, value); +} + +#[tokio::test(flavor = "multi_thread")] +async fn test_add_balance() { + let config: NodeConfig = fork_config(); + let address = config.genesis_accounts[0].address(); + let (api, _handle) = spawn(config).await; + + let start_balance = U256::from(100_000_u64); + api.anvil_set_balance(address, start_balance).await.unwrap(); + + let balance_increase = U256::from(50_000_u64); + api.anvil_add_balance(address, balance_increase).await.unwrap(); + + let new_balance = api.balance(address, None).await.unwrap(); + assert_eq!(new_balance, start_balance + balance_increase); +} + #[tokio::test(flavor = "multi_thread")] async fn test_reset_updates_cache_path_when_rpc_url_not_provided() { let config: NodeConfig = fork_config(); diff --git a/crates/anvil/tests/it/gas.rs b/crates/anvil/tests/it/gas.rs index 623654537d3d2..24f09d7358a0b 100644 --- a/crates/anvil/tests/it/gas.rs +++ b/crates/anvil/tests/it/gas.rs @@ -8,7 +8,7 @@ use alloy_rpc_types::{BlockId, TransactionRequest}; use alloy_serde::WithOtherFields; use anvil::{eth::fees::INITIAL_BASE_FEE, spawn, NodeConfig}; -const GAS_TRANSFER: u128 = 21_000; +const GAS_TRANSFER: u64 = 21_000; #[tokio::test(flavor = "multi_thread")] async fn test_gas_limit_applied_from_config() { @@ -215,3 +215,46 @@ async fn test_can_use_fee_history() { assert_eq!(latest_fee_history_fee, next_base_fee as u64); } } + +#[tokio::test(flavor = "multi_thread")] +async fn test_estimate_gas_empty_data() { + let (api, handle) = spawn(NodeConfig::test()).await; + let accounts = handle.dev_accounts().collect::>(); + let from = accounts[0]; + let to = accounts[1]; + + let tx_without_data = + TransactionRequest::default().with_from(from).with_to(to).with_value(U256::from(1)); + + let gas_without_data = api + .estimate_gas(WithOtherFields::new(tx_without_data), None, Default::default()) + .await + .unwrap(); + + let tx_with_empty_data = TransactionRequest::default() + .with_from(from) + .with_to(to) + .with_value(U256::from(1)) + .with_input(vec![]); + + let gas_with_empty_data = api + .estimate_gas(WithOtherFields::new(tx_with_empty_data), None, Default::default()) + .await + .unwrap(); + + let tx_with_data = TransactionRequest::default() + .with_from(from) + .with_to(to) + .with_value(U256::from(1)) + .with_input(vec![0x12, 0x34]); + + let gas_with_data = api + .estimate_gas(WithOtherFields::new(tx_with_data), None, Default::default()) + .await + .unwrap(); + + assert_eq!(gas_without_data, U256::from(GAS_TRANSFER)); + assert_eq!(gas_with_empty_data, U256::from(GAS_TRANSFER)); + assert!(gas_with_data > U256::from(GAS_TRANSFER)); + assert_eq!(gas_without_data, gas_with_empty_data); +} diff --git a/crates/anvil/tests/it/optimism.rs b/crates/anvil/tests/it/optimism.rs index 22d8b90526013..f871cca724a97 100644 --- a/crates/anvil/tests/it/optimism.rs +++ b/crates/anvil/tests/it/optimism.rs @@ -7,8 +7,8 @@ use alloy_primitives::{b256, Address, TxHash, TxKind, U256}; use alloy_provider::Provider; use alloy_rpc_types::TransactionRequest; use alloy_serde::WithOtherFields; -use anvil::{spawn, EthereumHardfork, NodeConfig}; -use anvil_core::eth::transaction::optimism::DepositTransaction; +use anvil::{spawn, NodeConfig}; +use op_alloy_consensus::TxDeposit; use op_alloy_rpc_types::OpTransactionFields; #[tokio::test(flavor = "multi_thread")] @@ -48,10 +48,7 @@ async fn test_deposits_not_supported_if_optimism_disabled() { #[tokio::test(flavor = "multi_thread")] async fn test_send_value_deposit_transaction() { // enable the Optimism flag - let (api, handle) = spawn( - NodeConfig::test().with_optimism(true).with_hardfork(Some(EthereumHardfork::Paris.into())), - ) - .await; + let (api, handle) = spawn(NodeConfig::test().with_optimism(true)).await; let accounts: Vec<_> = handle.dev_wallets().collect(); let signer: EthereumWallet = accounts[0].clone().into(); @@ -98,10 +95,7 @@ async fn test_send_value_deposit_transaction() { #[tokio::test(flavor = "multi_thread")] async fn test_send_value_raw_deposit_transaction() { // enable the Optimism flag - let (api, handle) = spawn( - NodeConfig::test().with_optimism(true).with_hardfork(Some(EthereumHardfork::Paris.into())), - ) - .await; + let (api, handle) = spawn(NodeConfig::test().with_optimism(true)).await; let accounts: Vec<_> = handle.dev_wallets().collect(); let signer: EthereumWallet = accounts[0].clone().into(); @@ -157,10 +151,7 @@ async fn test_send_value_raw_deposit_transaction() { #[tokio::test(flavor = "multi_thread")] async fn test_deposit_transaction_hash_matches_sepolia() { // enable the Optimism flag - let (_api, handle) = spawn( - NodeConfig::test().with_optimism(true).with_hardfork(Some(EthereumHardfork::Paris.into())), - ) - .await; + let (_api, handle) = spawn(NodeConfig::test().with_optimism(true)).await; let accounts: Vec<_> = handle.dev_wallets().collect(); let signer: EthereumWallet = accounts[0].clone().into(); @@ -191,10 +182,7 @@ async fn test_deposit_transaction_hash_matches_sepolia() { #[tokio::test(flavor = "multi_thread")] async fn test_deposit_tx_checks_sufficient_funds_after_applying_deposited_value() { // enable the Optimism flag - let (_api, handle) = spawn( - NodeConfig::test().with_optimism(true).with_hardfork(Some(EthereumHardfork::Paris.into())), - ) - .await; + let (_api, handle) = spawn(NodeConfig::test().with_optimism(true)).await; let provider = http_provider(&handle.http_endpoint()); @@ -208,15 +196,14 @@ async fn test_deposit_tx_checks_sufficient_funds_after_applying_deposited_value( let recipient_prev_balance = provider.get_balance(recipient).await.unwrap(); assert_eq!(recipient_prev_balance, U256::from(0)); - let deposit_tx = DepositTransaction { + let deposit_tx = TxDeposit { source_hash: b256!("0x0000000000000000000000000000000000000000000000000000000000000000"), from: sender, - nonce: 0, - kind: TxKind::Call(recipient), - mint: U256::from(send_value), + to: TxKind::Call(recipient), + mint: send_value, value: U256::from(send_value), gas_limit: 21_000, - is_system_tx: false, + is_system_transaction: false, input: Vec::new().into(), }; diff --git a/crates/anvil/tests/it/otterscan.rs b/crates/anvil/tests/it/otterscan.rs index eede93be15705..ffd70d1349ddb 100644 --- a/crates/anvil/tests/it/otterscan.rs +++ b/crates/anvil/tests/it/otterscan.rs @@ -1,6 +1,7 @@ //! Tests for otterscan endpoints. use crate::abi::Multicall; +use alloy_hardforks::EthereumHardfork; use alloy_network::TransactionResponse; use alloy_primitives::{address, Address, Bytes, U256}; use alloy_provider::Provider; @@ -10,7 +11,7 @@ use alloy_rpc_types::{ }; use alloy_serde::WithOtherFields; use alloy_sol_types::{sol, SolCall, SolError, SolValue}; -use anvil::{spawn, EthereumHardfork, NodeConfig}; +use anvil::{spawn, NodeConfig}; use std::collections::VecDeque; #[tokio::test(flavor = "multi_thread")] @@ -233,7 +234,7 @@ async fn test_call_ots_trace_transaction() { depth: 0, from: sender, to: contract_address, - value: U256::from(1337), + value: Some(U256::from(1337)), input: Contract::runCall::SELECTOR.into(), output: Bytes::new(), }, @@ -242,7 +243,7 @@ async fn test_call_ots_trace_transaction() { depth: 1, from: contract_address, to: contract_address, - value: U256::ZERO, + value: Some(U256::ZERO), input: Contract::do_staticcallCall::SELECTOR.into(), output: true.abi_encode().into(), }, @@ -251,7 +252,7 @@ async fn test_call_ots_trace_transaction() { depth: 1, from: contract_address, to: contract_address, - value: U256::ZERO, + value: Some(U256::ZERO), input: Contract::do_callCall::SELECTOR.into(), output: Bytes::new(), }, @@ -260,7 +261,7 @@ async fn test_call_ots_trace_transaction() { depth: 2, from: contract_address, to: sender, - value: U256::from(1337), + value: Some(U256::from(1337)), input: Bytes::new(), output: Bytes::new(), }, @@ -269,7 +270,7 @@ async fn test_call_ots_trace_transaction() { depth: 2, from: contract_address, to: contract_address, - value: U256::ZERO, + value: Some(U256::ZERO), input: Contract::do_delegatecallCall::SELECTOR.into(), output: Bytes::new(), }, diff --git a/crates/anvil/tests/it/pubsub.rs b/crates/anvil/tests/it/pubsub.rs index 44051401f2a78..04515d83aa02b 100644 --- a/crates/anvil/tests/it/pubsub.rs +++ b/crates/anvil/tests/it/pubsub.rs @@ -49,7 +49,7 @@ async fn test_sub_logs_legacy() { let contract = EmitLogs::new(contract_addr, provider.clone()); let val = contract.getValue().call().await.unwrap(); - assert_eq!(val._0, msg); + assert_eq!(val, msg); // subscribe to events from the contract let filter = Filter::new().address(contract.address().to_owned()); @@ -89,7 +89,7 @@ async fn test_sub_logs() { let contract = EmitLogs::new(contract_addr, provider.clone()); let val = contract.getValue().call().await.unwrap(); - assert_eq!(val._0, msg); + assert_eq!(val, msg); // subscribe to events from the contract let filter = Filter::new().address(contract.address().to_owned()); diff --git a/crates/anvil/tests/it/state.rs b/crates/anvil/tests/it/state.rs index 81235f18eebec..fdb4c8b128679 100644 --- a/crates/anvil/tests/it/state.rs +++ b/crates/anvil/tests/it/state.rs @@ -2,7 +2,7 @@ use crate::abi::Greeter; use alloy_network::{ReceiptResponse, TransactionBuilder}; -use alloy_primitives::{address, utils::Unit, Bytes, Uint, U256, U64}; +use alloy_primitives::{address, utils::Unit, Bytes, Uint, U256}; use alloy_provider::Provider; use alloy_rpc_types::{BlockId, TransactionRequest}; use alloy_serde::WithOtherFields; @@ -149,12 +149,12 @@ async fn can_preserve_historical_states_between_dump_and_load() { let greeter = Greeter::new(*address, provider); let greeting_at_init = - greeter.greet().block(BlockId::number(deploy_blk_num)).call().await.unwrap()._0; + greeter.greet().block(BlockId::number(deploy_blk_num)).call().await.unwrap(); assert_eq!(greeting_at_init, "Hello"); let greeting_after_change = - greeter.greet().block(BlockId::number(change_greeting_blk_num)).call().await.unwrap()._0; + greeter.greet().block(BlockId::number(change_greeting_blk_num)).call().await.unwrap(); assert_eq!(greeting_after_change, "World!"); } @@ -262,7 +262,7 @@ async fn test_fork_load_state_with_greater_state_block() { let serialized_state = api.serialized_state(false).await.unwrap(); - assert_eq!(serialized_state.best_block_number, Some(block_number.to::())); + assert_eq!(serialized_state.best_block_number, Some(block_number.to::())); let (api, _handle) = spawn( NodeConfig::test() @@ -276,3 +276,36 @@ async fn test_fork_load_state_with_greater_state_block() { assert_eq!(new_block_number, block_number); } + +// +#[tokio::test(flavor = "multi_thread")] +async fn computes_next_base_fee_after_loading_state() { + let tmp = tempfile::tempdir().unwrap(); + let state_file = tmp.path().join("state.json"); + + let (api, handle) = spawn(NodeConfig::test()).await; + + let bob = address!("0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266"); + let alice = address!("0x9276449EaC5b4f7Bc17cFC6700f7BeeB86F9bCd0"); + + let provider = handle.http_provider(); + + let base_fee_empty_chain = api.backend.fees().base_fee(); + + let value = Unit::ETHER.wei().saturating_mul(U256::from(1)); // 1 ether + let tx = TransactionRequest::default().with_to(alice).with_value(value).with_from(bob); + let tx = WithOtherFields::new(tx); + + let _receipt = provider.send_transaction(tx).await.unwrap().get_receipt().await.unwrap(); + + let base_fee_after_one_tx = api.backend.fees().base_fee(); + // the test is meaningless if this does not hold + assert_ne!(base_fee_empty_chain, base_fee_after_one_tx); + + let ser_state = api.serialized_state(true).await.unwrap(); + foundry_common::fs::write_json_file(&state_file, &ser_state).unwrap(); + + let (api, _handle) = spawn(NodeConfig::test().with_init_state_path(state_file)).await; + let base_fee_after_reload = api.backend.fees().base_fee(); + assert_eq!(base_fee_after_reload, base_fee_after_one_tx); +} diff --git a/crates/anvil/tests/it/traces.rs b/crates/anvil/tests/it/traces.rs index 297f188f7ddbd..2e073ca9c8735 100644 --- a/crates/anvil/tests/it/traces.rs +++ b/crates/anvil/tests/it/traces.rs @@ -4,6 +4,7 @@ use crate::{ utils::http_provider_with_signer, }; use alloy_eips::BlockId; +use alloy_hardforks::EthereumHardfork; use alloy_network::{EthereumWallet, TransactionBuilder}; use alloy_primitives::{ hex::{self, FromHex}, @@ -27,7 +28,7 @@ use alloy_rpc_types::{ }; use alloy_serde::WithOtherFields; use alloy_sol_types::sol; -use anvil::{spawn, EthereumHardfork, NodeConfig}; +use anvil::{spawn, NodeConfig}; #[tokio::test(flavor = "multi_thread")] async fn test_get_transfer_parity_traces() { @@ -138,7 +139,11 @@ async fn test_transfer_debug_trace_call() { let traces = handle .http_provider() - .debug_trace_call(tx, BlockId::latest(), GethDebugTracingCallOptions::default()) + .debug_trace_call( + WithOtherFields::new(tx), + BlockId::latest(), + GethDebugTracingCallOptions::default(), + ) .await .unwrap(); @@ -183,7 +188,7 @@ async fn test_call_tracer_debug_trace_call() { let internal_call_tx_traces = handle .http_provider() .debug_trace_call( - internal_call_tx.clone(), + WithOtherFields::new(internal_call_tx.clone()), BlockId::latest(), GethDebugTracingCallOptions::default().with_tracing_options( GethDebugTracingOptions::default() @@ -211,7 +216,7 @@ async fn test_call_tracer_debug_trace_call() { let internal_call_only_top_call_tx_traces = handle .http_provider() .debug_trace_call( - internal_call_tx.clone(), + WithOtherFields::new(internal_call_tx.clone()), BlockId::latest(), GethDebugTracingCallOptions::default().with_tracing_options( GethDebugTracingOptions::default() @@ -240,7 +245,7 @@ async fn test_call_tracer_debug_trace_call() { let direct_call_tx_traces = handle .http_provider() .debug_trace_call( - direct_call_tx, + WithOtherFields::new(direct_call_tx), BlockId::latest(), GethDebugTracingCallOptions::default().with_tracing_options( GethDebugTracingOptions::default() @@ -284,7 +289,7 @@ async fn test_debug_trace_call_state_override() { let tx_traces = handle .http_provider() .debug_trace_call( - tx.clone(), + WithOtherFields::new(tx.clone()), BlockId::latest(), GethDebugTracingCallOptions::default() .with_tracing_options(GethDebugTracingOptions::default()) diff --git a/crates/anvil/tests/it/transaction.rs b/crates/anvil/tests/it/transaction.rs index fc049614e3bdd..7cb681af85a14 100644 --- a/crates/anvil/tests/it/transaction.rs +++ b/crates/anvil/tests/it/transaction.rs @@ -2,15 +2,18 @@ use crate::{ abi::{Greeter, Multicall, SimpleStorage}, utils::{connect_pubsub, http_provider_with_signer}, }; +use alloy_hardforks::EthereumHardfork; use alloy_network::{EthereumWallet, TransactionBuilder, TransactionResponse}; use alloy_primitives::{address, hex, map::B256HashSet, Address, Bytes, FixedBytes, U256}; -use alloy_provider::Provider; +use alloy_provider::{Provider, WsConnect}; use alloy_rpc_types::{ - state::{AccountOverride, StateOverride}, - AccessList, AccessListItem, BlockId, BlockNumberOrTag, BlockTransactions, TransactionRequest, + state::{AccountOverride, EvmOverrides, StateOverride, StateOverridesBuilder}, + AccessList, AccessListItem, BlockId, BlockNumberOrTag, BlockOverrides, BlockTransactions, + TransactionRequest, }; use alloy_serde::WithOtherFields; -use anvil::{spawn, EthereumHardfork, NodeConfig}; +use alloy_sol_types::SolValue; +use anvil::{spawn, NodeConfig}; use eyre::Ok; use futures::{future::join_all, FutureExt, StreamExt}; use std::{str::FromStr, time::Duration}; @@ -237,7 +240,7 @@ async fn can_mine_large_gas_limit() { let from = accounts[0].address(); let to = accounts[1].address(); - let gas_limit = anvil::DEFAULT_GAS_LIMIT as u64; + let gas_limit = anvil::DEFAULT_GAS_LIMIT; let amount = handle.genesis_balance().checked_div(U256::from(3u64)).unwrap(); let tx = @@ -314,7 +317,7 @@ async fn can_deploy_greeter_http() { let greeting = alloy_greeter.greet().call().await.unwrap(); - assert_eq!("Hello World!", greeting._0); + assert_eq!("Hello World!", greeting); } #[tokio::test(flavor = "multi_thread")] @@ -351,7 +354,7 @@ async fn can_deploy_and_mine_manually() { let address = receipt.contract_address.unwrap(); let greeter_contract = Greeter::new(address, provider); let greeting = greeter_contract.greet().call().await.unwrap(); - assert_eq!("Hello World!", greeting._0); + assert_eq!("Hello World!", greeting); let set_greeting = greeter_contract.setGreeting("Another Message".to_string()); let tx = set_greeting.send().await.unwrap(); @@ -361,7 +364,7 @@ async fn can_deploy_and_mine_manually() { let _tx = tx.get_receipt().await.unwrap(); let greeting = greeter_contract.greet().call().await.unwrap(); - assert_eq!("Another Message", greeting._0); + assert_eq!("Another Message", greeting); } #[tokio::test(flavor = "multi_thread")] @@ -410,7 +413,7 @@ async fn can_call_greeter_historic() { let greeter_contract = Greeter::new(greeter_addr, provider.clone()); let greeting = greeter_contract.greet().call().await.unwrap(); - assert_eq!("Hello World!", greeting._0); + assert_eq!("Hello World!", greeting); let block_number = provider.get_block_number().await.unwrap(); @@ -424,7 +427,7 @@ async fn can_call_greeter_historic() { .unwrap(); let greeting = greeter_contract.greet().call().await.unwrap(); - assert_eq!("Another Message", greeting._0); + assert_eq!("Another Message", greeting); // min api.mine_one().await; @@ -432,7 +435,7 @@ async fn can_call_greeter_historic() { // returns previous state let greeting = greeter_contract.greet().block(BlockId::Number(block_number.into())).call().await.unwrap(); - assert_eq!("Hello World!", greeting._0); + assert_eq!("Hello World!", greeting); } #[tokio::test(flavor = "multi_thread")] @@ -452,7 +455,7 @@ async fn can_deploy_greeter_ws() { let greeter_contract = Greeter::new(greeter_addr, provider.clone()); let greeting = greeter_contract.greet().call().await.unwrap(); - assert_eq!("Hello World!", greeting._0); + assert_eq!("Hello World!", greeting); } #[tokio::test(flavor = "multi_thread")] @@ -479,41 +482,31 @@ async fn get_blocktimestamp_works() { let contract = Multicall::deploy(provider.clone()).await.unwrap(); - let timestamp = contract.getCurrentBlockTimestamp().call().await.unwrap().timestamp; + let timestamp = contract.getCurrentBlockTimestamp().call().await.unwrap(); assert!(timestamp > U256::from(1)); let latest_block = api.block_by_number(alloy_rpc_types::BlockNumberOrTag::Latest).await.unwrap().unwrap(); - let timestamp = contract.getCurrentBlockTimestamp().call().await.unwrap().timestamp; + let timestamp = contract.getCurrentBlockTimestamp().call().await.unwrap(); assert_eq!(timestamp.to::(), latest_block.header.timestamp); // repeat call same result - let timestamp = contract.getCurrentBlockTimestamp().call().await.unwrap().timestamp; + let timestamp = contract.getCurrentBlockTimestamp().call().await.unwrap(); assert_eq!(timestamp.to::(), latest_block.header.timestamp); // mock timestamp let next_timestamp = timestamp.to::() + 1337; api.evm_set_next_block_timestamp(next_timestamp).unwrap(); - let timestamp = contract - .getCurrentBlockTimestamp() - .block(BlockId::pending()) - .call() - .await - .unwrap() - .timestamp; + let timestamp = + contract.getCurrentBlockTimestamp().block(BlockId::pending()).call().await.unwrap(); assert_eq!(timestamp, U256::from(next_timestamp)); // repeat call same result - let timestamp = contract - .getCurrentBlockTimestamp() - .block(BlockId::pending()) - .call() - .await - .unwrap() - .timestamp; + let timestamp = + contract.getCurrentBlockTimestamp().block(BlockId::pending()).call().await.unwrap(); assert_eq!(timestamp, U256::from(next_timestamp)); } @@ -535,7 +528,7 @@ async fn call_past_state() { let deployed_block = provider.get_block_number().await.unwrap(); let value = contract.getValue().call().await.unwrap(); - assert_eq!(value._0, "initial value"); + assert_eq!(value, "initial value"); let gas_price = api.gas_price(); let set_tx = contract.setValue("hi".to_string()).gas_price(gas_price + 1); @@ -544,16 +537,16 @@ async fn call_past_state() { // assert new value let value = contract.getValue().call().await.unwrap(); - assert_eq!(value._0, "hi"); + assert_eq!(value, "hi"); // assert previous value let value = contract.getValue().block(BlockId::Number(deployed_block.into())).call().await.unwrap(); - assert_eq!(value._0, "initial value"); + assert_eq!(value, "initial value"); let hash = provider.get_block(BlockId::Number(1.into())).await.unwrap().unwrap().header.hash; let value = contract.getValue().block(BlockId::Hash(hash.into())).call().await.unwrap(); - assert_eq!(value._0, "initial value"); + assert_eq!(value, "initial value"); } #[tokio::test(flavor = "multi_thread")] @@ -715,6 +708,34 @@ async fn can_get_pending_transaction() { assert_eq!(mined.tx_hash(), pending.unwrap().unwrap().tx_hash()); } +#[tokio::test(flavor = "multi_thread")] +async fn can_listen_full_pending_transaction() { + let (api, handle) = spawn(NodeConfig::test()).await; + + // Disable auto-mining so transactions remain pending + api.anvil_set_auto_mine(false).await.unwrap(); + + let provider = alloy_provider::ProviderBuilder::new() + .connect_ws(WsConnect::new(handle.ws_endpoint())) + .await + .unwrap(); + + // Subscribe to full pending transactions + let sub = provider.subscribe_full_pending_transactions().await; + tokio::time::sleep(Duration::from_millis(1000)).await; + let mut stream = sub.expect("Failed to subscribe to pending tx").into_stream().take(5); + + let from = handle.dev_wallets().next().unwrap().address(); + let tx = TransactionRequest::default().from(from).value(U256::from(1337)).to(Address::random()); + + let tx = provider.send_transaction(tx).await.unwrap(); + + // Wait for the subscription to yield a transaction + let received = stream.next().await.expect("Failed to receive pending tx"); + + assert_eq!(received.tx_hash(), *tx.tx_hash()); +} + #[tokio::test(flavor = "multi_thread")] async fn can_get_raw_transaction() { let (api, handle) = spawn(NodeConfig::test()).await; @@ -1090,7 +1111,7 @@ async fn estimates_gas_on_pending_by_default() { .to(sender) .value(U256::from(1e10)) .input(Bytes::from(vec![0x42]).into()); - api.estimate_gas(WithOtherFields::new(tx), None, None).await.unwrap(); + api.estimate_gas(WithOtherFields::new(tx), None, EvmOverrides::default()).await.unwrap(); } #[tokio::test(flavor = "multi_thread")] @@ -1107,7 +1128,8 @@ async fn test_estimate_gas() { .value(U256::from(1e10)) .input(Bytes::from(vec![0x42]).into()); // Expect the gas estimation to fail due to insufficient funds. - let error_result = api.estimate_gas(WithOtherFields::new(tx.clone()), None, None).await; + let error_result = + api.estimate_gas(WithOtherFields::new(tx.clone()), None, EvmOverrides::default()).await; assert!(error_result.is_err(), "Expected an error due to insufficient funds"); let error_message = error_result.unwrap_err().to_string(); @@ -1125,7 +1147,7 @@ async fn test_estimate_gas() { // Estimate gas with state override implying sufficient funds. let gas_estimate = api - .estimate_gas(WithOtherFields::new(tx), None, Some(state_override)) + .estimate_gas(WithOtherFields::new(tx), None, EvmOverrides::new(Some(state_override), None)) .await .expect("Failed to estimate gas with state override"); @@ -1133,6 +1155,45 @@ async fn test_estimate_gas() { assert!(gas_estimate >= U256::from(21000), "Gas estimate is lower than expected minimum"); } +#[tokio::test(flavor = "multi_thread")] +async fn test_block_override() { + let (api, handle) = spawn(NodeConfig::test()).await; + + let wallet = handle.dev_wallets().next().unwrap(); + let sender = wallet.address(); + let recipient = Address::random(); + + let tx = TransactionRequest::default() + .from(sender) + .to(recipient) + .input(Bytes::from(hex!("42cbb15c").to_vec()).into()); + + // function getBlockNumber() external view returns (uint256) { + // return block.number; + // } + let code = hex!("6080604052348015600e575f5ffd5b50600436106026575f3560e01c806342cbb15c14602a575b5f5ffd5b60306044565b604051603b91906061565b60405180910390f35b5f43905090565b5f819050919050565b605b81604b565b82525050565b5f60208201905060725f8301846054565b9291505056fea26469706673582212207741266d8151c5e7d1a96fc1697f8fc94e60e730b3f2861d398339c74a2180d464736f6c634300081e0033"); + + let account_override = + AccountOverride { balance: Some(U256::from(1e18)), ..Default::default() }; + let state_override = StateOverridesBuilder::default() + .append(sender, account_override) + .append(recipient, AccountOverride::default().with_code(code.to_vec())) + .build(); + + let block_override = BlockOverrides { number: Some(U256::from(99)), ..Default::default() }; + + let output = api + .call( + WithOtherFields::new(tx), + None, + EvmOverrides::new(Some(state_override), Some(Box::new(block_override))), + ) + .await + .expect("Failed to estimate gas with state override"); + + assert_eq!(output, U256::from(99).abi_encode()); +} + #[tokio::test(flavor = "multi_thread")] async fn test_reject_gas_too_low() { let (_api, handle) = spawn(NodeConfig::test()).await; @@ -1163,7 +1224,7 @@ async fn can_call_with_high_gas_limit() { let greeter_contract = Greeter::deploy(provider, "Hello World!".to_string()).await.unwrap(); let greeting = greeter_contract.greet().gas(60_000_000).call().await.unwrap(); - assert_eq!("Hello World!", greeting._0); + assert_eq!("Hello World!", greeting); } #[tokio::test(flavor = "multi_thread")] @@ -1202,7 +1263,7 @@ async fn test_reject_eip1559_pre_london() { let greeter_contract = Greeter::new(greeter_contract_addr, provider); let greeting = greeter_contract.greet().call().await.unwrap(); - assert_eq!("Hello World!", greeting._0); + assert_eq!("Hello World!", greeting); } // https://github.com/foundry-rs/foundry/issues/6931 @@ -1242,5 +1303,5 @@ async fn estimates_gas_prague() { .with_input(hex!("0xcafebabe")) .with_from(address!("0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266")) .with_to(address!("0x70997970c51812dc3a010c7d01b50e0d17dc79c8")); - api.estimate_gas(WithOtherFields::new(req), None, None).await.unwrap(); + api.estimate_gas(WithOtherFields::new(req), None, EvmOverrides::default()).await.unwrap(); } diff --git a/crates/anvil/tests/it/txpool.rs b/crates/anvil/tests/it/txpool.rs index c329b27fa9130..a883380a5f9aa 100644 --- a/crates/anvil/tests/it/txpool.rs +++ b/crates/anvil/tests/it/txpool.rs @@ -26,10 +26,8 @@ async fn geth_txpool() { let tx = WithOtherFields::new(tx); // send a few transactions - let mut txs = Vec::new(); for _ in 0..10 { - let tx_hash = provider.send_transaction(tx.clone()).await.unwrap(); - txs.push(tx_hash); + let _ = provider.send_transaction(tx.clone()).await.unwrap(); } // we gave a 20s block time, should be plenty for us to get the txpool's content diff --git a/crates/cast/Cargo.toml b/crates/cast/Cargo.toml index 0f23a854f653b..d08d10fc6fce9 100644 --- a/crates/cast/Cargo.toml +++ b/crates/cast/Cargo.toml @@ -26,6 +26,7 @@ foundry-block-explorers.workspace = true foundry-common.workspace = true foundry-compilers.workspace = true foundry-config.workspace = true +foundry-evm-core.workspace = true foundry-evm.workspace = true foundry-wallets.workspace = true @@ -51,21 +52,21 @@ alloy-signer-local = { workspace = true, features = ["mnemonic", "keystore"] } alloy-signer.workspace = true alloy-sol-types.workspace = true alloy-transport.workspace = true +alloy-ens = { workspace = true, features = ["provider"] } + +op-alloy-flz.workspace = true +op-alloy-consensus = { workspace = true, features = ["alloy-compat"] } chrono.workspace = true eyre.workspace = true futures.workspace = true +revm.workspace = true rand.workspace = true +rand_08.workspace = true rayon.workspace = true serde_json.workspace = true serde.workspace = true -# aws-kms -aws-sdk-kms = { workspace = true, default-features = false, optional = true } - -# gcp-kms -gcloud-sdk = { version = "0.26.4", default-features = false, optional = true } - # bin foundry-cli.workspace = true @@ -84,17 +85,17 @@ tracing.workspace = true yansi.workspace = true evmole.workspace = true -[target.'cfg(unix)'.dependencies] -tikv-jemallocator = { workspace = true, optional = true } - [dev-dependencies] anvil.workspace = true foundry-test-utils.workspace = true +alloy-hardforks.workspace = true [features] default = ["jemalloc"] asm-keccak = ["alloy-primitives/asm-keccak"] -jemalloc = ["dep:tikv-jemallocator"] -aws-kms = ["foundry-wallets/aws-kms", "dep:aws-sdk-kms"] -gcp-kms = ["foundry-wallets/gcp-kms", "dep:gcloud-sdk"] -isolate-by-default = ["foundry-config/isolate-by-default"] +jemalloc = ["foundry-cli/jemalloc"] +mimalloc = ["foundry-cli/mimalloc"] +tracy-allocator = ["foundry-cli/tracy-allocator"] +aws-kms = ["foundry-wallets/aws-kms"] +gcp-kms = ["foundry-wallets/gcp-kms"] +isolate-by-default = ["foundry-config/isolate-by-default"] \ No newline at end of file diff --git a/crates/cast/bin/main.rs b/crates/cast/bin/main.rs index 5cf14dfe8e576..a554d01d7be44 100644 --- a/crates/cast/bin/main.rs +++ b/crates/cast/bin/main.rs @@ -3,9 +3,8 @@ use cast::args::run; -#[cfg(all(feature = "jemalloc", unix))] #[global_allocator] -static ALLOC: tikv_jemallocator::Jemalloc = tikv_jemallocator::Jemalloc; +static ALLOC: foundry_cli::utils::Allocator = foundry_cli::utils::new_allocator(); fn main() { if let Err(err) = run() { diff --git a/crates/cast/src/args.rs b/crates/cast/src/args.rs index fc6df76364a82..6b75b45927f64 100644 --- a/crates/cast/src/args.rs +++ b/crates/cast/src/args.rs @@ -3,8 +3,9 @@ use crate::{ traces::identifier::SignaturesIdentifier, Cast, SimpleCast, }; -use alloy_consensus::transaction::Recovered; +use alloy_consensus::transaction::{Recovered, SignerRecoverable}; use alloy_dyn_abi::{DynSolValue, ErrorExt, EventExt}; +use alloy_ens::{namehash, ProviderEnsExt}; use alloy_primitives::{eip191_hash_message, hex, keccak256, Address, B256}; use alloy_provider::Provider; use alloy_rpc_types::{BlockId, BlockNumberOrTag::Latest}; @@ -14,7 +15,6 @@ use eyre::Result; use foundry_cli::{handler, utils, utils::LoadConfig}; use foundry_common::{ abi::{get_error, get_event}, - ens::{namehash, ProviderEnsExt}, fmt::{format_tokens, format_tokens_raw, format_uint_exp}, fs, selectors::{ @@ -215,20 +215,19 @@ pub async fn run_command(args: CastArgs) -> Result<()> { } CastSubcommand::DecodeEvent { sig, data } => { let decoded_event = if let Some(event_sig) = sig { - get_event(event_sig.as_str())?.decode_log_parts(None, &hex::decode(data)?, false)? + let event = get_event(event_sig.as_str())?; + event.decode_log_parts(core::iter::once(event.selector()), &hex::decode(data)?)? } else { let data = data.strip_prefix("0x").unwrap_or(data.as_str()); let selector = data.get(..64).unwrap_or_default(); + let selector = selector.parse()?; let identified_event = - SignaturesIdentifier::new(false)?.identify_event(selector.parse()?).await; + SignaturesIdentifier::new(false)?.identify_event(selector).await; if let Some(event) = identified_event { let _ = sh_println!("{}", event.signature()); let data = data.get(64..).unwrap_or_default(); - get_event(event.signature().as_str())?.decode_log_parts( - None, - &hex::decode(data)?, - false, - )? + get_event(event.signature().as_str())? + .decode_log_parts(core::iter::once(selector), &hex::decode(data)?)? } else { eyre::bail!("No matching event signature found for selector `{selector}`") } @@ -369,12 +368,21 @@ pub async fn run_command(args: CastArgs) -> Result<()> { let who = who.resolve(&provider).await?; sh_println!("{}", Cast::new(provider).codesize(who, block).await?)? } - CastSubcommand::ComputeAddress { address, nonce, rpc } => { - let config = rpc.load_config()?; - let provider = utils::get_provider(&config)?; - + CastSubcommand::ComputeAddress { address, nonce, salt, init_code, init_code_hash, rpc } => { let address = stdin::unwrap_line(address)?; - let computed = Cast::new(provider).compute_address(address, nonce).await?; + let computed = { + // For CREATE2, init_code_hash is needed to compute the address + if let Some(init_code_hash) = init_code_hash { + address.create2(salt.unwrap_or(B256::ZERO), init_code_hash) + } else if let Some(init_code) = init_code { + address.create2(salt.unwrap_or(B256::ZERO), keccak256(hex::decode(init_code)?)) + } else { + // For CREATE, rpc is needed to compute the address + let config = rpc.load_config()?; + let provider = utils::get_provider(&config)?; + Cast::new(provider).compute_address(address, nonce).await? + } + }; sh_println!("Computed Address: {}", computed.to_checksum(None))? } CastSubcommand::Disassemble { bytecode } => { @@ -627,7 +635,7 @@ pub async fn run_command(args: CastArgs) -> Result<()> { }; } CastSubcommand::HashMessage { message } => { - let message = stdin::unwrap_line(message)?; + let message = stdin::unwrap(message, false)?; sh_println!("{}", eip191_hash_message(message))? } CastSubcommand::SigEvent { event_string } => { @@ -715,11 +723,10 @@ pub async fn run_command(args: CastArgs) -> Result<()> { sh_println!("{}", serde_json::to_string_pretty(&tx)?)?; } } - CastSubcommand::DecodeEof { eof } => { - let eof = stdin::unwrap_line(eof)?; - sh_println!("{}", SimpleCast::decode_eof(&eof)?)? - } CastSubcommand::TxPool { command } => command.run().await?, + CastSubcommand::DAEstimate(cmd) => { + cmd.run().await?; + } }; /// Prints slice of tokens using [`format_tokens`] or [`format_tokens_raw`] depending whether diff --git a/crates/cast/src/base.rs b/crates/cast/src/base.rs index 63ae59c1cbffb..1b71d65131b0f 100644 --- a/crates/cast/src/base.rs +++ b/crates/cast/src/base.rs @@ -676,13 +676,20 @@ mod tests { } } - // TODO: test for octal #[test] fn test_format_neg() { // underlying is 256 bits so we have to pad left manually let expected_2: Vec<_> = NEG_NUM.iter().map(|n| format!("{n:1>256b}")).collect(); - // let expected_8: Vec<_> = NEG_NUM.iter().map(|n| format!("1{:7>85o}", n)).collect(); + let expected_8: Vec<_> = NEG_NUM + .iter() + .map(|n| { + let i = I256::try_from(*n).unwrap(); + let mut u = NumberWithBase::from(i); + u.set_base(Octal); + u.format() + }) + .collect(); // Sign not included, see NumberWithBase::format let expected_10: Vec<_> = NEG_NUM.iter().map(|n| format!("{n:}").trim_matches('-').to_string()).collect(); @@ -693,7 +700,7 @@ mod tests { let mut num: NumberWithBase = I256::try_from(n).unwrap().into(); assert_eq!(num.set_base(Binary).format(), expected_2[i]); - // assert_eq!(num.set_base(Octal).format(), expected_8[i]); + assert_eq!(num.set_base(Octal).format(), expected_8[i]); assert_eq!(num.set_base(Decimal).format(), expected_10[i]); assert_eq!(num.set_base(Hexadecimal).format(), expected_l16[i]); assert_eq!(num.set_base(Hexadecimal).format().to_uppercase(), expected_u16[i]); diff --git a/crates/cast/src/cmd/access_list.rs b/crates/cast/src/cmd/access_list.rs index 3c5654fd09b55..700a37b22a887 100644 --- a/crates/cast/src/cmd/access_list.rs +++ b/crates/cast/src/cmd/access_list.rs @@ -2,6 +2,7 @@ use crate::{ tx::{CastTxBuilder, SenderKind}, Cast, }; +use alloy_ens::NameOrAddress; use alloy_rpc_types::BlockId; use clap::Parser; use eyre::Result; @@ -9,7 +10,6 @@ use foundry_cli::{ opts::{EthereumOpts, TransactionOpts}, utils::{self, LoadConfig}, }; -use foundry_common::ens::NameOrAddress; use std::str::FromStr; /// CLI arguments for `cast access-list`. diff --git a/crates/cast/src/cmd/artifact.rs b/crates/cast/src/cmd/artifact.rs index dc83cb2aea211..79ec880d34cfd 100644 --- a/crates/cast/src/cmd/artifact.rs +++ b/crates/cast/src/cmd/artifact.rs @@ -1,12 +1,11 @@ use super::{ - creation_code::{fetch_creation_code, parse_code_output}, + creation_code::{fetch_creation_code_from_etherscan, parse_code_output}, interface::{fetch_abi_from_etherscan, load_abi_from_file}, }; use alloy_primitives::Address; use alloy_provider::Provider; use clap::{command, Parser}; use eyre::Result; -use foundry_block_explorers::Client; use foundry_cli::{ opts::{EtherscanOpts, RpcOpts}, utils::{self, LoadConfig}, @@ -46,15 +45,12 @@ pub struct ArtifactArgs { impl ArtifactArgs { pub async fn run(self) -> Result<()> { - let Self { contract, etherscan, rpc, output: output_location, abi_path } = self; + let Self { contract, mut etherscan, rpc, output: output_location, abi_path } = self; - let mut etherscan = etherscan; let config = rpc.load_config()?; let provider = utils::get_provider(&config)?; - let api_key = etherscan.key().unwrap_or_default(); let chain = provider.get_chain_id().await?; etherscan.chain = Some(chain.into()); - let client = Client::new(chain.into(), api_key)?; let abi = if let Some(ref abi_path) = abi_path { load_abi_from_file(abi_path, None)? @@ -64,7 +60,7 @@ impl ArtifactArgs { let (abi, _) = abi.first().ok_or_else(|| eyre::eyre!("No ABI found"))?; - let bytecode = fetch_creation_code(contract, client, provider).await?; + let bytecode = fetch_creation_code_from_etherscan(contract, ðerscan, provider).await?; let bytecode = parse_code_output(bytecode, contract, ðerscan, abi_path.as_deref(), true, false) .await?; diff --git a/crates/cast/src/cmd/call.rs b/crates/cast/src/cmd/call.rs index b005a8af0f8c8..c329dbe3cc717 100644 --- a/crates/cast/src/cmd/call.rs +++ b/crates/cast/src/cmd/call.rs @@ -3,10 +3,11 @@ use crate::{ tx::{CastTxBuilder, SenderKind}, Cast, }; +use alloy_ens::NameOrAddress; use alloy_primitives::{Address, Bytes, TxKind, U256}; use alloy_rpc_types::{ state::{StateOverride, StateOverridesBuilder}, - BlockId, BlockNumberOrTag, + BlockId, BlockNumberOrTag, BlockOverrides, }; use clap::Parser; use eyre::Result; @@ -14,7 +15,7 @@ use foundry_cli::{ opts::{EthereumOpts, TransactionOpts}, utils::{self, handle_traces, parse_ether_value, TraceResult}, }; -use foundry_common::{ens::NameOrAddress, shell}; +use foundry_common::shell; use foundry_compilers::artifacts::EvmVersion; use foundry_config::{ figment::{ @@ -30,6 +31,7 @@ use foundry_evm::{ traces::{InternalTraceMode, TraceMode}, }; use regex::Regex; +use revm::context::TransactionType; use std::{str::FromStr, sync::LazyLock}; // matches override pattern
:: @@ -146,6 +148,14 @@ pub struct CallArgs { /// Format: address:slot:value #[arg(long = "override-state-diff", value_name = "ADDRESS:SLOT:VALUE")] pub state_diff_overrides: Option>, + + /// Override the block timestamp. + #[arg(long = "block.time", value_name = "TIME")] + pub block_time: Option, + + /// Override the block number. + #[arg(long = "block.number", value_name = "NUMBER")] + pub block_number: Option, } #[derive(Debug, Parser)] @@ -178,6 +188,7 @@ impl CallArgs { let evm_opts = figment.extract::()?; let mut config = Config::from_provider(figment)?.sanitized(); let state_overrides = self.get_state_overrides()?; + let block_overrides = self.get_block_overrides()?; let Self { to, @@ -242,8 +253,8 @@ impl CallArgs { TracingExecutor::get_fork_material(&config, evm_opts).await?; // modify settings that usually set in eth_call - env.cfg.disable_block_gas_limit = true; - env.block.gas_limit = U256::MAX; + env.evm_env.cfg_env.disable_block_gas_limit = true; + env.evm_env.block_env.gas_limit = u64::MAX; let trace_mode = TraceMode::Call .with_debug(debug) @@ -265,9 +276,18 @@ impl CallArgs { let value = tx.value.unwrap_or_default(); let input = tx.inner.input.into_input().unwrap_or_default(); let tx_kind = tx.inner.to.expect("set by builder"); + let env_tx = &mut executor.env_mut().tx; + + if let Some(tx_type) = tx.inner.transaction_type { + env_tx.tx_type = tx_type; + } if let Some(access_list) = tx.inner.access_list { - executor.env_mut().tx.access_list = access_list.0 + env_tx.access_list = access_list; + + if env_tx.tx_type == TransactionType::Legacy as u8 { + env_tx.tx_type = TransactionType::Eip2930 as u8; + } } let trace = match tx_kind { @@ -297,13 +317,29 @@ impl CallArgs { sh_println!( "{}", - Cast::new(provider).call(&tx, func.as_ref(), block, state_overrides).await? + Cast::new(provider) + .call(&tx, func.as_ref(), block, state_overrides, block_overrides) + .await? )?; Ok(()) } - /// Parse state overrides from command line arguments - pub fn get_state_overrides(&self) -> eyre::Result { + + /// Parse state overrides from command line arguments. + pub fn get_state_overrides(&self) -> eyre::Result> { + // Early return if no override set - + if [ + self.balance_overrides.as_ref(), + self.nonce_overrides.as_ref(), + self.code_overrides.as_ref(), + self.state_overrides.as_ref(), + ] + .iter() + .all(Option::is_none) + { + return Ok(None); + } + let mut state_overrides_builder = StateOverridesBuilder::default(); // Parse balance overrides @@ -341,7 +377,23 @@ impl CallArgs { state_overrides_builder.with_state_diff(addr, [(slot.into(), value.into())]); } - Ok(state_overrides_builder.build()) + Ok(Some(state_overrides_builder.build())) + } + + /// Parse block overrides from command line arguments. + pub fn get_block_overrides(&self) -> eyre::Result> { + let mut overrides = BlockOverrides::default(); + if let Some(number) = self.block_number { + overrides = overrides.with_number(U256::from(number)); + } + if let Some(time) = self.block_time { + overrides = overrides.with_time(time); + } + if overrides.is_empty() { + Ok(None) + } else { + Ok(Some(overrides)) + } } } diff --git a/crates/cast/src/cmd/constructor_args.rs b/crates/cast/src/cmd/constructor_args.rs index 2775e2e99ecfd..3d60673857f37 100644 --- a/crates/cast/src/cmd/constructor_args.rs +++ b/crates/cast/src/cmd/constructor_args.rs @@ -1,5 +1,5 @@ use super::{ - creation_code::fetch_creation_code, + creation_code::fetch_creation_code_from_etherscan, interface::{fetch_abi_from_etherscan, load_abi_from_file}, }; use alloy_dyn_abi::DynSolType; @@ -7,7 +7,6 @@ use alloy_primitives::{Address, Bytes}; use alloy_provider::Provider; use clap::{command, Parser}; use eyre::{eyre, OptionExt, Result}; -use foundry_block_explorers::Client; use foundry_cli::{ opts::{EtherscanOpts, RpcOpts}, utils::{self, LoadConfig}, @@ -37,12 +36,10 @@ impl ConstructorArgsArgs { let config = rpc.load_config()?; let provider = utils::get_provider(&config)?; - let api_key = etherscan.key().unwrap_or_default(); let chain = provider.get_chain_id().await?; etherscan.chain = Some(chain.into()); - let client = Client::new(chain.into(), api_key)?; - let bytecode = fetch_creation_code(contract, client, provider).await?; + let bytecode = fetch_creation_code_from_etherscan(contract, ðerscan, provider).await?; let args_arr = parse_constructor_args(bytecode, contract, ðerscan, abi_path).await?; for arg in args_arr { diff --git a/crates/cast/src/cmd/create2.rs b/crates/cast/src/cmd/create2.rs index 6cb21d1e80c77..3cab2caf0c8c9 100644 --- a/crates/cast/src/cmd/create2.rs +++ b/crates/cast/src/cmd/create2.rs @@ -184,7 +184,7 @@ impl Create2Args { if !no_random { let mut rng = match seed { Some(seed) => StdRng::from_seed(seed.0), - None => StdRng::from_entropy(), + None => StdRng::from_os_rng(), }; rng.fill_bytes(remaining); } diff --git a/crates/cast/src/cmd/creation_code.rs b/crates/cast/src/cmd/creation_code.rs index 9967f38fde9bb..db47b1bdec523 100644 --- a/crates/cast/src/cmd/creation_code.rs +++ b/crates/cast/src/cmd/creation_code.rs @@ -50,12 +50,10 @@ impl CreationCodeArgs { let config = rpc.load_config()?; let provider = utils::get_provider(&config)?; - let api_key = etherscan.key().unwrap_or_default(); let chain = provider.get_chain_id().await?; etherscan.chain = Some(chain.into()); - let client = Client::new(chain.into(), api_key)?; - let bytecode = fetch_creation_code(contract, client, provider).await?; + let bytecode = fetch_creation_code_from_etherscan(contract, ðerscan, provider).await?; let bytecode = parse_code_output( bytecode, @@ -131,11 +129,16 @@ pub async fn parse_code_output( } /// Fetches the creation code of a contract from Etherscan and RPC. -pub async fn fetch_creation_code( +pub async fn fetch_creation_code_from_etherscan( contract: Address, - client: Client, + etherscan: &EtherscanOpts, provider: RetryProvider, ) -> Result { + let config = etherscan.load_config()?; + let chain = config.chain.unwrap_or_default(); + let api_version = config.get_etherscan_api_version(Some(chain)); + let api_key = config.get_etherscan_api_key(Some(chain)).unwrap_or_default(); + let client = Client::new_with_api_version(chain, api_key, api_version)?; let creation_data = client.contract_creation_data(contract).await?; let creation_tx_hash = creation_data.transaction_hash; let tx_data = provider.get_transaction_by_hash(creation_tx_hash).await?; diff --git a/crates/cast/src/cmd/da_estimate.rs b/crates/cast/src/cmd/da_estimate.rs new file mode 100644 index 0000000000000..f4dfe98fe1d8f --- /dev/null +++ b/crates/cast/src/cmd/da_estimate.rs @@ -0,0 +1,48 @@ +//! Estimates the data availability size of a block for opstack. + +use alloy_consensus::BlockHeader; +use alloy_network::eip2718::Encodable2718; +use alloy_provider::Provider; +use alloy_rpc_types::BlockId; +use clap::Parser; +use foundry_cli::{ + opts::RpcOpts, + utils::{self, LoadConfig}, +}; +use op_alloy_consensus::OpTxEnvelope; + +/// CLI arguments for `cast da-estimate`. +#[derive(Debug, Parser)] +pub struct DAEstimateArgs { + /// The block to estimate the data availability size for. + pub block: BlockId, + #[command(flatten)] + pub rpc: RpcOpts, +} + +impl DAEstimateArgs { + /// Load the RPC URL from the config file. + pub async fn run(self) -> eyre::Result<()> { + let Self { block, rpc } = self; + let config = rpc.load_config()?; + let provider = utils::get_provider(&config)?; + let block = provider + .get_block(block) + .full() + .await? + .ok_or_else(|| eyre::eyre!("Block not found"))?; + + let block_number = block.header.number(); + let tx_count = block.transactions.len(); + let mut da_estimate = 0; + for tx in block.into_transactions_iter() { + // try to convert into opstack transaction + let tx = OpTxEnvelope::try_from(tx)?; + da_estimate += op_alloy_flz::tx_estimated_size_fjord(&tx.encoded_2718()); + } + + sh_println!("Estimated data availability size for block {block_number} with {tx_count} transactions: {da_estimate}")?; + + Ok(()) + } +} diff --git a/crates/cast/src/cmd/estimate.rs b/crates/cast/src/cmd/estimate.rs index 58f40efdec109..8bcd29806ee65 100644 --- a/crates/cast/src/cmd/estimate.rs +++ b/crates/cast/src/cmd/estimate.rs @@ -1,4 +1,5 @@ use crate::tx::{CastTxBuilder, SenderKind}; +use alloy_ens::NameOrAddress; use alloy_primitives::U256; use alloy_provider::Provider; use alloy_rpc_types::BlockId; @@ -8,7 +9,6 @@ use foundry_cli::{ opts::{EthereumOpts, TransactionOpts}, utils::{self, parse_ether_value, LoadConfig}, }; -use foundry_common::ens::NameOrAddress; use std::str::FromStr; /// CLI arguments for `cast estimate`. @@ -30,6 +30,12 @@ pub struct EstimateArgs { #[arg(long, short = 'B')] block: Option, + /// Calculate the cost of a transaction using the network gas price. + /// + /// If not specified the amount of gas will be estimated. + #[arg(long)] + cost: bool, + #[command(subcommand)] command: Option, @@ -66,7 +72,7 @@ pub enum EstimateSubcommands { impl EstimateArgs { pub async fn run(self) -> Result<()> { - let Self { to, mut sig, mut args, mut tx, block, eth, command } = self; + let Self { to, mut sig, mut args, mut tx, block, cost, eth, command } = self; let config = eth.load_config()?; let provider = utils::get_provider(&config)?; @@ -99,7 +105,14 @@ impl EstimateArgs { .await?; let gas = provider.estimate_gas(tx).block(block.unwrap_or_default()).await?; - sh_println!("{gas}")?; + if cost { + let gas_price_wei = provider.get_gas_price().await?; + let cost = gas_price_wei * gas as u128; + let cost_eth = cost as f64 / 1e18; + sh_println!("{cost_eth}")?; + } else { + sh_println!("{gas}")?; + } Ok(()) } } diff --git a/crates/cast/src/cmd/interface.rs b/crates/cast/src/cmd/interface.rs index f37f92864e534..992f5b833eac3 100644 --- a/crates/cast/src/cmd/interface.rs +++ b/crates/cast/src/cmd/interface.rs @@ -143,8 +143,9 @@ pub async fn fetch_abi_from_etherscan( ) -> Result> { let config = etherscan.load_config()?; let chain = config.chain.unwrap_or_default(); + let api_version = config.get_etherscan_api_version(Some(chain)); let api_key = config.get_etherscan_api_key(Some(chain)).unwrap_or_default(); - let client = Client::new(chain, api_key)?; + let client = Client::new_with_api_version(chain, api_key, api_version)?; let source = client.contract_source_code(address).await?; source.items.into_iter().map(|item| Ok((item.abi()?, item.contract_name))).collect() } diff --git a/crates/cast/src/cmd/logs.rs b/crates/cast/src/cmd/logs.rs index 1b3ce1fe9be66..beb9afbef2015 100644 --- a/crates/cast/src/cmd/logs.rs +++ b/crates/cast/src/cmd/logs.rs @@ -1,5 +1,6 @@ use crate::Cast; use alloy_dyn_abi::{DynSolType, DynSolValue, Specifier}; +use alloy_ens::NameOrAddress; use alloy_json_abi::Event; use alloy_network::AnyNetwork; use alloy_primitives::{hex::FromHex, Address, B256}; @@ -7,7 +8,6 @@ use alloy_rpc_types::{BlockId, BlockNumberOrTag, Filter, FilterBlockOption, Filt use clap::Parser; use eyre::Result; use foundry_cli::{opts::EthereumOpts, utils, utils::LoadConfig}; -use foundry_common::ens::NameOrAddress; use itertools::Itertools; use std::{io, str::FromStr}; diff --git a/crates/cast/src/cmd/mktx.rs b/crates/cast/src/cmd/mktx.rs index a17f52be8071b..ebfb5afeadd47 100644 --- a/crates/cast/src/cmd/mktx.rs +++ b/crates/cast/src/cmd/mktx.rs @@ -1,6 +1,8 @@ use crate::tx::{self, CastTxBuilder}; +use alloy_ens::NameOrAddress; use alloy_network::{eip2718::Encodable2718, EthereumWallet, TransactionBuilder}; use alloy_primitives::hex; +use alloy_provider::Provider; use alloy_signer::Signer; use clap::Parser; use eyre::{OptionExt, Result}; @@ -8,7 +10,6 @@ use foundry_cli::{ opts::{EthereumOpts, TransactionOpts}, utils::{get_provider, LoadConfig}, }; -use foundry_common::ens::NameOrAddress; use std::{path::PathBuf, str::FromStr}; /// CLI arguments for `cast mktx`. @@ -50,6 +51,10 @@ pub struct MakeTxArgs { /// Relaxes the wallet requirement. #[arg(long, requires = "from")] raw_unsigned: bool, + + /// Call `eth_signTransaction` using the `--from` argument or $ETH_FROM as sender + #[arg(long, requires = "from", conflicts_with = "raw_unsigned")] + ethsign: bool, } #[derive(Debug, Parser)] @@ -70,7 +75,7 @@ pub enum MakeTxSubcommands { impl MakeTxArgs { pub async fn run(self) -> Result<()> { - let Self { to, mut sig, mut args, command, tx, path, eth, raw_unsigned } = self; + let Self { to, mut sig, mut args, command, tx, path, eth, raw_unsigned, ethsign } = self; let blob_data = if let Some(path) = path { Some(std::fs::read(path)?) } else { None }; @@ -91,7 +96,7 @@ impl MakeTxArgs { let provider = get_provider(&config)?; - let tx_builder = CastTxBuilder::new(provider, tx, &config) + let tx_builder = CastTxBuilder::new(&provider, tx, &config) .await? .with_to(to) .await? @@ -108,7 +113,18 @@ impl MakeTxArgs { return Ok(()); } - // Retrieve the signer, and bail if it can't be constructed. + if ethsign { + // Use "eth_signTransaction" to sign the transaction only works if the node/RPC has + // unlocked accounts. + let (tx, _) = tx_builder.build(config.sender).await?; + let signed_tx = provider.sign_transaction(tx).await?; + + sh_println!("{signed_tx}")?; + return Ok(()); + } + + // Default to using the local signer. + // Get the signer from the wallet, and fail if it can't be constructed. let signer = eth.wallet.signer().await?; let from = signer.address(); diff --git a/crates/cast/src/cmd/mod.rs b/crates/cast/src/cmd/mod.rs index 292376f09ce22..482cb77b4e343 100644 --- a/crates/cast/src/cmd/mod.rs +++ b/crates/cast/src/cmd/mod.rs @@ -12,6 +12,7 @@ pub mod call; pub mod constructor_args; pub mod create2; pub mod creation_code; +pub mod da_estimate; pub mod estimate; pub mod find_block; pub mod interface; diff --git a/crates/cast/src/cmd/run.rs b/crates/cast/src/cmd/run.rs index 947704a2df5a1..04f52957ef668 100644 --- a/crates/cast/src/cmd/run.rs +++ b/crates/cast/src/cmd/run.rs @@ -1,9 +1,5 @@ -use crate::{ - revm::primitives::EnvWithHandlerCfg, utils::apply_chain_and_block_specific_env_changes, -}; use alloy_consensus::Transaction; use alloy_network::{AnyNetwork, TransactionResponse}; -use alloy_primitives::U256; use alloy_provider::Provider; use alloy_rpc_types::BlockTransactions; use clap::Parser; @@ -27,7 +23,11 @@ use foundry_evm::{ opts::EvmOpts, traces::{InternalTraceMode, TraceMode}, utils::configure_tx_env, + Env, }; +use foundry_evm_core::env::AsEnvMut; + +use crate::utils::apply_chain_and_block_specific_env_changes; /// CLI arguments for `cast run`. #[derive(Clone, Debug, Parser)] @@ -149,26 +149,26 @@ impl RunArgs { TracingExecutor::get_fork_material(&config, evm_opts).await?; let mut evm_version = self.evm_version; - env.cfg.disable_block_gas_limit = self.disable_block_gas_limit; - env.block.number = U256::from(tx_block_number); + env.evm_env.cfg_env.disable_block_gas_limit = self.disable_block_gas_limit; + env.evm_env.block_env.number = tx_block_number; if let Some(block) = &block { - env.block.timestamp = U256::from(block.header.timestamp); - env.block.coinbase = block.header.beneficiary; - env.block.difficulty = block.header.difficulty; - env.block.prevrandao = Some(block.header.mix_hash.unwrap_or_default()); - env.block.basefee = U256::from(block.header.base_fee_per_gas.unwrap_or_default()); - env.block.gas_limit = U256::from(block.header.gas_limit); + env.evm_env.block_env.timestamp = block.header.timestamp; + env.evm_env.block_env.beneficiary = block.header.beneficiary; + env.evm_env.block_env.difficulty = block.header.difficulty; + env.evm_env.block_env.prevrandao = Some(block.header.mix_hash.unwrap_or_default()); + env.evm_env.block_env.basefee = block.header.base_fee_per_gas.unwrap_or_default(); + env.evm_env.block_env.gas_limit = block.header.gas_limit; // TODO: we need a smarter way to map the block to the corresponding evm_version for // commonly used chains if evm_version.is_none() { // if the block has the excess_blob_gas field, we assume it's a Cancun block if block.header.excess_blob_gas.is_some() { - evm_version = Some(EvmVersion::Cancun); + evm_version = Some(EvmVersion::Prague); } } - apply_chain_and_block_specific_env_changes::(&mut env, block); + apply_chain_and_block_specific_env_changes::(env.as_env_mut(), block); } let trace_mode = TraceMode::Call @@ -187,8 +187,12 @@ impl RunArgs { odyssey, create2_deployer, )?; - let mut env = - EnvWithHandlerCfg::new_with_spec_id(Box::new(env.clone()), executor.spec_id()); + let mut env = Env::new_with_spec_id( + env.evm_env.cfg_env.clone(), + env.evm_env.block_env.clone(), + env.tx.clone(), + executor.spec_id(), + ); // Set the state to the moment right before the transaction if !self.quick { @@ -218,7 +222,7 @@ impl RunArgs { break; } - configure_tx_env(&mut env, &tx.inner); + configure_tx_env(&mut env.as_env_mut(), &tx.inner); if let Some(to) = Transaction::to(tx) { trace!(tx=?tx.tx_hash(),?to, "executing previous call transaction"); @@ -226,7 +230,7 @@ impl RunArgs { format!( "Failed to execute transaction: {:?} in block {}", tx.tx_hash(), - env.block.number + env.evm_env.block_env.number ) })?; } else { @@ -240,7 +244,7 @@ impl RunArgs { format!( "Failed to deploy transaction: {:?} in block {}", tx.tx_hash(), - env.block.number + env.evm_env.block_env.number ) }) } @@ -257,7 +261,7 @@ impl RunArgs { let result = { executor.set_trace_printer(self.trace_printer); - configure_tx_env(&mut env, &tx.inner); + configure_tx_env(&mut env.as_env_mut(), &tx.inner); if let Some(to) = Transaction::to(&tx) { trace!(tx=?tx.tx_hash(), to=?to, "executing call transaction"); @@ -299,6 +303,10 @@ impl figment::Provider for RunArgs { map.insert("etherscan_api_key".into(), api_key.as_str().into()); } + if let Some(api_version) = &self.etherscan.api_version { + map.insert("etherscan_api_version".into(), api_version.to_string().into()); + } + if let Some(evm_version) = self.evm_version { map.insert("evm_version".into(), figment::value::Value::serialize(evm_version)?); } diff --git a/crates/cast/src/cmd/send.rs b/crates/cast/src/cmd/send.rs index 7e175d8a7ae5d..500c65f6723c2 100644 --- a/crates/cast/src/cmd/send.rs +++ b/crates/cast/src/cmd/send.rs @@ -2,19 +2,19 @@ use crate::{ tx::{self, CastTxBuilder}, Cast, }; +use alloy_ens::NameOrAddress; use alloy_network::{AnyNetwork, EthereumWallet}; use alloy_provider::{Provider, ProviderBuilder}; use alloy_rpc_types::TransactionRequest; use alloy_serde::WithOtherFields; use alloy_signer::Signer; use clap::Parser; -use eyre::Result; +use eyre::{eyre, Result}; use foundry_cli::{ opts::{EthereumOpts, TransactionOpts}, utils, utils::LoadConfig, }; -use foundry_common::ens::NameOrAddress; use std::{path::PathBuf, str::FromStr}; /// CLI arguments for `cast send`. @@ -109,6 +109,17 @@ impl SendTxArgs { args: constructor_args, }) = command { + // ensure we don't violate settings for transactions that can't be CREATE: 7702 and 4844 + // which require mandatory target + if to.is_none() && tx.auth.is_some() { + return Err(eyre!("EIP-7702 transactions can't be CREATE transactions and require a destination address")); + } + // ensure we don't violate settings for transactions that can't be CREATE: 7702 and 4844 + // which require mandatory target + if to.is_none() && blob_data.is_some() { + return Err(eyre!("EIP-4844 transactions can't be CREATE transactions and require a destination address")); + } + sig = constructor_sig; args = constructor_args; Some(code) @@ -172,7 +183,7 @@ impl SendTxArgs { let wallet = EthereumWallet::from(signer); let provider = ProviderBuilder::<_, _, AnyNetwork>::default() .wallet(wallet) - .on_provider(&provider); + .connect_provider(&provider); cast_send(provider, tx, cast_async, confirmations, timeout).await } diff --git a/crates/cast/src/cmd/storage.rs b/crates/cast/src/cmd/storage.rs index 7f75b61fb146f..1bd3cf1468ebc 100644 --- a/crates/cast/src/cmd/storage.rs +++ b/crates/cast/src/cmd/storage.rs @@ -1,4 +1,5 @@ use crate::{opts::parse_slot, Cast}; +use alloy_ens::NameOrAddress; use alloy_network::AnyNetwork; use alloy_primitives::{Address, B256, U256}; use alloy_provider::Provider; @@ -15,7 +16,6 @@ use foundry_cli::{ use foundry_common::{ abi::find_source, compile::{etherscan_project, ProjectCompiler}, - ens::NameOrAddress, shell, }; use foundry_compilers::{ @@ -135,8 +135,9 @@ impl StorageArgs { } let chain = utils::get_chain(config.chain, &provider).await?; + let api_version = config.get_etherscan_api_version(Some(chain)); let api_key = config.get_etherscan_api_key(Some(chain)).unwrap_or_default(); - let client = Client::new(chain, api_key)?; + let client = Client::new_with_api_version(chain, api_key, api_version)?; let source = if let Some(proxy) = self.proxy { find_source(client, proxy.resolve(&provider).await?).await? } else { diff --git a/crates/cast/src/cmd/wallet/mod.rs b/crates/cast/src/cmd/wallet/mod.rs index 52d24c01b6941..e15c53ad33fea 100644 --- a/crates/cast/src/cmd/wallet/mod.rs +++ b/crates/cast/src/cmd/wallet/mod.rs @@ -1,8 +1,8 @@ -use crate::revm::primitives::Authorization; use alloy_chains::Chain; use alloy_dyn_abi::TypedData; -use alloy_primitives::{hex, Address, PrimitiveSignature as Signature, B256, U256}; +use alloy_primitives::{hex, Address, Signature, B256, U256}; use alloy_provider::Provider; +use alloy_rpc_types::Authorization; use alloy_signer::{ k256::{elliptic_curve::sec1::ToEncodedPoint, SecretKey}, Signer, @@ -17,7 +17,7 @@ use foundry_cli::{opts::RpcOpts, utils, utils::LoadConfig}; use foundry_common::{fs, sh_println, shell}; use foundry_config::Config; use foundry_wallets::{RawWalletOpts, WalletOpts, WalletSigner}; -use rand::thread_rng; +use rand_08::thread_rng; use serde_json::json; use std::path::Path; use yansi::Paint; @@ -147,6 +147,9 @@ pub enum WalletSubcommands { #[command(visible_alias = "v")] Verify { /// The original message. + /// + /// Treats 0x-prefixed strings as hex encoded bytes. + /// Non 0x-prefixed strings are treated as raw input message. message: String, /// The signature to verify. @@ -454,7 +457,29 @@ impl WalletSubcommands { } else { wallet.sign_message(&Self::hex_str_to_bytes(&message)?).await? }; - sh_println!("0x{}", hex::encode(sig.as_bytes()))?; + + if shell::verbosity() > 0 { + if shell::is_json() { + sh_println!( + "{}", + serde_json::to_string_pretty(&json!({ + "message": message, + "address": wallet.address(), + "signature": hex::encode(sig.as_bytes()), + }))? + )?; + } else { + sh_println!( + "Successfully signed!\n Message: {}\n Address: {}\n Signature: 0x{}", + message, + wallet.address(), + hex::encode(sig.as_bytes()), + )?; + } + } else { + // Pipe friendly output + sh_println!("0x{}", hex::encode(sig.as_bytes()))?; + } } Self::SignAuth { rpc, nonce, chain, wallet, address } => { let wallet = wallet.signer().await?; @@ -472,7 +497,31 @@ impl WalletSubcommands { let auth = Authorization { chain_id: U256::from(chain_id), address, nonce }; let signature = wallet.sign_hash(&auth.signature_hash()).await?; let auth = auth.into_signed(signature); - sh_println!("{}", hex::encode_prefixed(alloy_rlp::encode(&auth)))?; + + if shell::verbosity() > 0 { + if shell::is_json() { + sh_println!( + "{}", + serde_json::to_string_pretty(&json!({ + "nonce": nonce, + "chain_id": chain_id, + "address": wallet.address(), + "signature": hex::encode_prefixed(alloy_rlp::encode(&auth)), + }))? + )?; + } else { + sh_println!( + "Successfully signed!\n Nonce: {}\n Chain ID: {}\n Address: {}\n Signature: 0x{}", + nonce, + chain_id, + wallet.address(), + hex::encode_prefixed(alloy_rlp::encode(&auth)), + )?; + } + } else { + // Pipe friendly output + sh_println!("{}", hex::encode_prefixed(alloy_rlp::encode(&auth)))?; + } } Self::Verify { message, signature, address } => { let recovered_address = Self::recover_address_from_message(&message, &signature)?; @@ -713,11 +762,17 @@ flag to set your key via: Ok(()) } - /// Recovers an address from the specified message and signature + /// Recovers an address from the specified message and signature. + /// + /// Note: This attempts to decode the message as hex if it starts with 0x. fn recover_address_from_message(message: &str, signature: &Signature) -> Result
{ + let message = Self::hex_str_to_bytes(message)?; Ok(signature.recover_address_from_msg(message)?) } + /// Strips the 0x prefix from a hex string and decodes it to bytes. + /// + /// Treats the string as raw bytes if it doesn't start with 0x. fn hex_str_to_bytes(s: &str) -> Result> { Ok(match s.strip_prefix("0x") { Some(data) => hex::decode(data).wrap_err("Could not decode 0x-prefixed string.")?, diff --git a/crates/cast/src/cmd/wallet/vanity.rs b/crates/cast/src/cmd/wallet/vanity.rs index 2137feb42b31d..1c80e3165e858 100644 --- a/crates/cast/src/cmd/wallet/vanity.rs +++ b/crates/cast/src/cmd/wallet/vanity.rs @@ -221,7 +221,7 @@ pub fn wallet_generator() -> iter::Map, impl Fn(()) -> Generate /// Generates a random K-256 signing key and derives its Ethereum address. pub fn generate_wallet() -> GeneratedWallet { - let key = SigningKey::random(&mut rand::thread_rng()); + let key = SigningKey::random(&mut rand_08::thread_rng()); let address = secret_key_to_address(&key); (key, address) } diff --git a/crates/cast/src/lib.rs b/crates/cast/src/lib.rs index 66a0abc75f78f..ac8b08b5cc291 100644 --- a/crates/cast/src/lib.rs +++ b/crates/cast/src/lib.rs @@ -1,9 +1,11 @@ //! Cast is a Swiss Army knife for interacting with Ethereum applications from the command line. #![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] +#![cfg_attr(not(test), warn(unused_crate_dependencies))] use alloy_consensus::TxEnvelope; use alloy_dyn_abi::{DynSolType, DynSolValue, FunctionExt}; +use alloy_ens::NameOrAddress; use alloy_json_abi::Function; use alloy_network::{AnyNetwork, AnyRpcTransaction}; use alloy_primitives::{ @@ -17,7 +19,7 @@ use alloy_provider::{ }; use alloy_rlp::Decodable; use alloy_rpc_types::{ - state::StateOverride, BlockId, BlockNumberOrTag, Filter, TransactionRequest, + state::StateOverride, BlockId, BlockNumberOrTag, BlockOverrides, Filter, TransactionRequest, }; use alloy_serde::WithOtherFields; use alloy_sol_types::sol; @@ -28,15 +30,15 @@ use foundry_block_explorers::Client; use foundry_common::{ abi::{encode_function_args, get_func}, compile::etherscan_project, - ens::NameOrAddress, fmt::*, fs, get_pretty_tx_receipt_attr, shell, TransactionReceiptWithRevertReason, }; use foundry_compilers::flatten::Flattener; use foundry_config::Chain; +use foundry_evm_core::ic::decode_instructions; use futures::{future::Either, FutureExt, StreamExt}; +use op_alloy_consensus::OpTxEnvelope; use rayon::prelude::*; -use revm::primitives::Eof; use std::{ borrow::Cow, fmt::Write, @@ -47,7 +49,6 @@ use std::{ time::Duration, }; use tokio::signal::ctrl_c; -use utils::decode_instructions; use foundry_common::abi::encode_function_args_packed; pub use foundry_evm::*; @@ -94,7 +95,7 @@ impl> Cast

{ /// /// # async fn foo() -> eyre::Result<()> { /// let provider = - /// ProviderBuilder::<_, _, AnyNetwork>::default().on_builtin("http://localhost:8545").await?; + /// ProviderBuilder::<_, _, AnyNetwork>::default().connect("http://localhost:8545").await?; /// let cast = Cast::new(provider); /// # Ok(()) /// # } @@ -109,7 +110,7 @@ impl> Cast

{ /// /// ``` /// use alloy_primitives::{Address, U256, Bytes}; - /// use alloy_rpc_types::{TransactionRequest, state::{StateOverride, AccountOverride}}; + /// use alloy_rpc_types::{TransactionRequest, BlockOverrides, state::{StateOverride, AccountOverride}}; /// use alloy_serde::WithOtherFields; /// use cast::Cast; /// use alloy_provider::{RootProvider, ProviderBuilder, network::AnyNetwork}; @@ -135,9 +136,10 @@ impl> Cast

{ /// account_override.balance = Some(U256::from(1000)); /// state_override.insert(to, account_override); /// let state_override_object = StateOverridesBuilder::default().build(); + /// let block_override_object = BlockOverrides::default(); /// /// let cast = Cast::new(alloy_provider); - /// let data = cast.call(&tx, None, None, state_override_object).await?; + /// let data = cast.call(&tx, None, None, Some(state_override_object), Some(block_override_object)).await?; /// println!("{}", data); /// # Ok(()) /// # } @@ -147,20 +149,24 @@ impl> Cast

{ req: &WithOtherFields, func: Option<&Function>, block: Option, - state_override: StateOverride, + state_override: Option, + block_override: Option, ) -> Result { - let res = self + let mut call = self .provider .call(req.clone()) .block(block.unwrap_or_default()) - .overrides(state_override) - .await?; + .with_block_overrides_opt(block_override); + if let Some(state_override) = state_override { + call = call.overrides(state_override) + } + let res = call.await?; let mut decoded = vec![]; if let Some(func) = func { // decode args into tokens - decoded = match func.abi_decode_output(res.as_ref(), false) { + decoded = match func.abi_decode_output(res.as_ref()) { Ok(decoded) => decoded, Err(err) => { // ensure the address is a contract @@ -220,7 +226,7 @@ impl> Cast

{ /// ); /// /// # async fn foo() -> eyre::Result<()> { - /// let provider = ProviderBuilder::<_,_, AnyNetwork>::default().on_builtin("http://localhost:8545").await?;; + /// let provider = ProviderBuilder::<_,_, AnyNetwork>::default().connect("http://localhost:8545").await?;; /// let to = Address::from_str("0xB3C95ff08316fb2F2e3E52Ee82F8e7b605Aa1304")?; /// let greeting = greetingCall { i: U256::from(5) }.abi_encode(); /// let bytes = Bytes::from_iter(greeting.iter()); @@ -281,7 +287,7 @@ impl> Cast

{ /// ); /// /// # async fn foo() -> eyre::Result<()> { - /// let provider = ProviderBuilder::<_,_, AnyNetwork>::default().on_builtin("http://localhost:8545").await?;; + /// let provider = ProviderBuilder::<_,_, AnyNetwork>::default().connect("http://localhost:8545").await?;; /// let from = Address::from_str("0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045")?; /// let to = Address::from_str("0xB3C95ff08316fb2F2e3E52Ee82F8e7b605Aa1304")?; /// let greeting = greetCall { greeting: "hello".to_string() }.abi_encode(); @@ -316,7 +322,7 @@ impl> Cast

{ /// /// # async fn foo() -> eyre::Result<()> { /// let provider = - /// ProviderBuilder::<_, _, AnyNetwork>::default().on_builtin("http://localhost:8545").await?; + /// ProviderBuilder::<_, _, AnyNetwork>::default().connect("http://localhost:8545").await?; /// let cast = Cast::new(provider); /// let res = cast.publish("0x1234".to_string()).await?; /// println!("{:?}", res); @@ -345,7 +351,7 @@ impl> Cast

{ /// /// # async fn foo() -> eyre::Result<()> { /// let provider = - /// ProviderBuilder::<_, _, AnyNetwork>::default().on_builtin("http://localhost:8545").await?; + /// ProviderBuilder::<_, _, AnyNetwork>::default().connect("http://localhost:8545").await?; /// let cast = Cast::new(provider); /// let block = cast.block(5, true, None).await?; /// println!("{}", block); @@ -368,7 +374,7 @@ impl> Cast

{ let block = self .provider .get_block(block) - .full() + .kind(full.into()) .await? .ok_or_else(|| eyre::eyre!("block {:?} not found", block))?; @@ -501,7 +507,7 @@ impl> Cast

{ /// /// # async fn foo() -> eyre::Result<()> { /// let provider = - /// ProviderBuilder::<_, _, AnyNetwork>::default().on_builtin("http://localhost:8545").await?; + /// ProviderBuilder::<_, _, AnyNetwork>::default().connect("http://localhost:8545").await?; /// let cast = Cast::new(provider); /// let addr = Address::from_str("0x7eD52863829AB99354F3a0503A622e82AcD5F7d3")?; /// let nonce = cast.nonce(addr, None).await?; @@ -523,7 +529,7 @@ impl> Cast

{ /// /// # async fn foo() -> eyre::Result<()> { /// let provider = - /// ProviderBuilder::<_, _, AnyNetwork>::default().on_builtin("http://localhost:8545").await?; + /// ProviderBuilder::<_, _, AnyNetwork>::default().connect("http://localhost:8545").await?; /// let cast = Cast::new(provider); /// let addr = Address::from_str("0x7eD52863829AB99354F3a0503A622e82AcD5F7d3")?; /// let slots = vec![FixedBytes::from_str("0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421")?]; @@ -556,7 +562,7 @@ impl> Cast

{ /// /// # async fn foo() -> eyre::Result<()> { /// let provider = - /// ProviderBuilder::<_, _, AnyNetwork>::default().on_builtin("http://localhost:8545").await?; + /// ProviderBuilder::<_, _, AnyNetwork>::default().connect("http://localhost:8545").await?; /// let cast = Cast::new(provider); /// let addr = Address::from_str("0x7eD52863829AB99354F3a0503A622e82AcD5F7d3")?; /// let slots = vec![FixedBytes::from_str("0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421")?]; @@ -589,7 +595,7 @@ impl> Cast

{ /// /// # async fn foo() -> eyre::Result<()> { /// let provider = - /// ProviderBuilder::<_, _, AnyNetwork>::default().on_builtin("http://localhost:8545").await?; + /// ProviderBuilder::<_, _, AnyNetwork>::default().connect("http://localhost:8545").await?; /// let cast = Cast::new(provider); /// let addr = Address::from_str("0x7eD52863829AB99354F3a0503A622e82AcD5F7d3")?; /// let implementation = cast.implementation(addr, false, None).await?; @@ -638,7 +644,7 @@ impl> Cast

{ /// /// # async fn foo() -> eyre::Result<()> { /// let provider = - /// ProviderBuilder::<_, _, AnyNetwork>::default().on_builtin("http://localhost:8545").await?; + /// ProviderBuilder::<_, _, AnyNetwork>::default().connect("http://localhost:8545").await?; /// let cast = Cast::new(provider); /// let addr = Address::from_str("0x7eD52863829AB99354F3a0503A622e82AcD5F7d3")?; /// let admin = cast.admin(addr, None).await?; @@ -668,7 +674,7 @@ impl> Cast

{ /// /// # async fn foo() -> eyre::Result<()> { /// let provider = - /// ProviderBuilder::<_, _, AnyNetwork>::default().on_builtin("http://localhost:8545").await?; + /// ProviderBuilder::<_, _, AnyNetwork>::default().connect("http://localhost:8545").await?; /// let cast = Cast::new(provider); /// let addr = Address::from_str("7eD52863829AB99354F3a0503A622e82AcD5F7d3")?; /// let computed_address = cast.compute_address(addr, None).await?; @@ -691,7 +697,7 @@ impl> Cast

{ /// /// # async fn foo() -> eyre::Result<()> { /// let provider = - /// ProviderBuilder::<_, _, AnyNetwork>::default().on_builtin("http://localhost:8545").await?; + /// ProviderBuilder::<_, _, AnyNetwork>::default().connect("http://localhost:8545").await?; /// let cast = Cast::new(provider); /// let addr = Address::from_str("0x00000000219ab540356cbb839cbe05303d7705fa")?; /// let code = cast.code(addr, None, false).await?; @@ -727,7 +733,7 @@ impl> Cast

{ /// /// # async fn foo() -> eyre::Result<()> { /// let provider = - /// ProviderBuilder::<_, _, AnyNetwork>::default().on_builtin("http://localhost:8545").await?; + /// ProviderBuilder::<_, _, AnyNetwork>::default().connect("http://localhost:8545").await?; /// let cast = Cast::new(provider); /// let addr = Address::from_str("0x00000000219ab540356cbb839cbe05303d7705fa")?; /// let codesize = cast.codesize(addr, None).await?; @@ -790,7 +796,10 @@ impl> Cast

{ }; Ok(if raw { - format!("0x{}", hex::encode(tx.inner.inner.encoded_2718())) + // also consider opstack deposit transactions + let either_tx = tx.try_into_either::()?; + let encoded = either_tx.encoded_2718(); + format!("0x{}", hex::encode(encoded)) } else if let Some(field) = field { get_pretty_tx_attr(&tx.inner, field.as_str()) .ok_or_else(|| eyre::eyre!("invalid tx field: {}", field.to_string()))? @@ -810,7 +819,7 @@ impl> Cast

{ /// /// # async fn foo() -> eyre::Result<()> { /// let provider = - /// ProviderBuilder::<_, _, AnyNetwork>::default().on_builtin("http://localhost:8545").await?; + /// ProviderBuilder::<_, _, AnyNetwork>::default().connect("http://localhost:8545").await?; /// let cast = Cast::new(provider); /// let tx_hash = "0xf8d1713ea15a81482958fb7ddf884baee8d3bcc478c5f2f604e008dc788ee4fc"; /// let receipt = cast.receipt(tx_hash.to_string(), None, 1, None, false).await?; @@ -871,7 +880,7 @@ impl> Cast

{ /// /// # async fn foo() -> eyre::Result<()> { /// let provider = - /// ProviderBuilder::<_, _, AnyNetwork>::default().on_builtin("http://localhost:8545").await?; + /// ProviderBuilder::<_, _, AnyNetwork>::default().connect("http://localhost:8545").await?; /// let cast = Cast::new(provider); /// let result = cast /// .rpc("eth_getBalance", &["0xc94770007dda54cF92009BFF0dE90c06F603a09f", "latest"]) @@ -903,7 +912,7 @@ impl> Cast

{ /// /// # async fn foo() -> eyre::Result<()> { /// let provider = - /// ProviderBuilder::<_, _, AnyNetwork>::default().on_builtin("http://localhost:8545").await?; + /// ProviderBuilder::<_, _, AnyNetwork>::default().connect("http://localhost:8545").await?; /// let cast = Cast::new(provider); /// let addr = Address::from_str("0x00000000006c3852cbEf3e08E8dF289169EdE581")?; /// let slot = B256::ZERO; @@ -965,7 +974,7 @@ impl> Cast

{ /// /// # async fn foo() -> eyre::Result<()> { /// let provider = - /// ProviderBuilder::<_, _, AnyNetwork>::default().on_builtin("http://localhost:8545").await?; + /// ProviderBuilder::<_, _, AnyNetwork>::default().connect("http://localhost:8545").await?; /// let cast = Cast::new(provider); /// /// let block_number = cast.convert_block_number(Some(BlockId::number(5))).await?; @@ -1013,7 +1022,7 @@ impl> Cast

{ /// /// # async fn foo() -> eyre::Result<()> { /// let provider = - /// ProviderBuilder::<_, _, AnyNetwork>::default().on_builtin("wss://localhost:8545").await?; + /// ProviderBuilder::<_, _, AnyNetwork>::default().connect("wss://localhost:8545").await?; /// let cast = Cast::new(provider); /// /// let filter = @@ -1100,8 +1109,7 @@ impl> Cast

{ .balanceOf(owner) .block(block.unwrap_or_default()) .call() - .await? - ._0) + .await?) } } @@ -2216,24 +2224,6 @@ impl SimpleCast { let tx = TxEnvelope::decode_2718(&mut tx_hex.as_slice())?; Ok(tx) } - - /// Decodes EOF container bytes - /// Pretty prints the decoded EOF container contents - /// - /// # Example - /// - /// ``` - /// use cast::SimpleCast as Cast; - /// - /// let eof = "0xef0001010004020001005604002000008000046080806040526004361015e100035f80fd5f3560e01c63773d45e01415e1ffee6040600319360112e10028600435906024358201809211e100066020918152f3634e487b7160e01b5f52601160045260245ffd5f80fd0000000000000000000000000124189fc71496f8660db5189f296055ed757632"; - /// let decoded = Cast::decode_eof(&eof)?; - /// println!("{}", decoded); - /// # Ok::<(), eyre::Report>(()) - pub fn decode_eof(eof: &str) -> Result { - let eof_hex = hex::decode(eof)?; - let eof = Eof::decode(eof_hex.into())?; - Ok(pretty_eof(&eof)?) - } } fn strip_0x(s: &str) -> &str { @@ -2406,16 +2396,21 @@ mod tests { #[test] fn disassemble_incomplete_sequence() { let incomplete = &hex!("60"); // PUSH1 - let disassembled = Cast::disassemble(incomplete); - assert!(disassembled.is_err()); + let disassembled = Cast::disassemble(incomplete).unwrap(); + assert_eq!(disassembled, "00000000: PUSH1 0x00\n"); let complete = &hex!("6000"); // PUSH1 0x00 let disassembled = Cast::disassemble(complete); assert!(disassembled.is_ok()); let incomplete = &hex!("7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"); // PUSH32 with 31 bytes - let disassembled = Cast::disassemble(incomplete); - assert!(disassembled.is_err()); + + let disassembled = Cast::disassemble(incomplete).unwrap(); + + assert_eq!( + disassembled, + "00000000: PUSH32 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00\n" + ); let complete = &hex!("7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"); // PUSH32 with 32 bytes let disassembled = Cast::disassemble(complete); diff --git a/crates/cast/src/opts.rs b/crates/cast/src/opts.rs index 7594d0fe23fc5..f712f43bf0114 100644 --- a/crates/cast/src/opts.rs +++ b/crates/cast/src/opts.rs @@ -1,19 +1,17 @@ use crate::cmd::{ access_list::AccessListArgs, artifact::ArtifactArgs, bind::BindArgs, call::CallArgs, constructor_args::ConstructorArgsArgs, create2::Create2Args, creation_code::CreationCodeArgs, - estimate::EstimateArgs, find_block::FindBlockArgs, interface::InterfaceArgs, logs::LogsArgs, - mktx::MakeTxArgs, rpc::RpcArgs, run::RunArgs, send::SendTxArgs, storage::StorageArgs, - txpool::TxPoolSubcommands, wallet::WalletSubcommands, + da_estimate::DAEstimateArgs, estimate::EstimateArgs, find_block::FindBlockArgs, + interface::InterfaceArgs, logs::LogsArgs, mktx::MakeTxArgs, rpc::RpcArgs, run::RunArgs, + send::SendTxArgs, storage::StorageArgs, txpool::TxPoolSubcommands, wallet::WalletSubcommands, }; +use alloy_ens::NameOrAddress; use alloy_primitives::{Address, Selector, B256, U256}; use alloy_rpc_types::BlockId; use clap::{Parser, Subcommand, ValueHint}; use eyre::Result; use foundry_cli::opts::{EtherscanOpts, GlobalArgs, RpcOpts}; -use foundry_common::{ - ens::NameOrAddress, - version::{LONG_VERSION, SHORT_VERSION}, -}; +use foundry_common::version::{LONG_VERSION, SHORT_VERSION}; use std::{path::PathBuf, str::FromStr}; /// A Swiss Army knife for interacting with Ethereum applications from the command line. @@ -424,9 +422,31 @@ pub enum CastSubcommand { address: Option

, /// The nonce of the deployer address. - #[arg(long)] + #[arg( + long, + conflicts_with = "salt", + conflicts_with = "init_code", + conflicts_with = "init_code_hash" + )] nonce: Option, + /// The salt for CREATE2 address computation. + #[arg(long, conflicts_with = "nonce")] + salt: Option, + + /// The init code for CREATE2 address computation. + #[arg( + long, + requires = "salt", + conflicts_with = "init_code_hash", + conflicts_with = "nonce" + )] + init_code: Option, + + /// The init code hash for CREATE2 address computation. + #[arg(long, requires = "salt", conflicts_with = "init_code", conflicts_with = "nonce")] + init_code_hash: Option, + #[command(flatten)] rpc: RpcOpts, }, @@ -1061,16 +1081,15 @@ pub enum CastSubcommand { resolve: bool, }, - /// Decodes EOF container bytes - #[command()] - DecodeEof { eof: Option }, - /// Inspect the TxPool of a node. #[command(visible_alias = "tp")] TxPool { #[command(subcommand)] command: TxPoolSubcommands, }, + /// Estimates the data availability size of a given opstack block. + #[command(name = "da-estimate")] + DAEstimate(DAEstimateArgs), } /// CLI arguments for `cast --to-base`. diff --git a/crates/cast/src/tx.rs b/crates/cast/src/tx.rs index 1097da794e15f..829d8e571ca80 100644 --- a/crates/cast/src/tx.rs +++ b/crates/cast/src/tx.rs @@ -1,6 +1,7 @@ use crate::traces::identifier::SignaturesIdentifier; use alloy_consensus::{SidecarBuilder, SignableTransaction, SimpleCoder}; use alloy_dyn_abi::ErrorExt; +use alloy_ens::NameOrAddress; use alloy_json_abi::Function; use alloy_network::{ AnyNetwork, AnyTypedTransaction, TransactionBuilder, TransactionBuilder4844, @@ -13,11 +14,12 @@ use alloy_serde::WithOtherFields; use alloy_signer::Signer; use alloy_transport::TransportError; use eyre::Result; +use foundry_block_explorers::EtherscanApiVersion; use foundry_cli::{ opts::{CliAuthorizationList, TransactionOpts}, utils::{self, parse_function_args}, }; -use foundry_common::{ens::NameOrAddress, fmt::format_tokens}; +use foundry_common::fmt::format_tokens; use foundry_config::{Chain, Config}; use foundry_wallets::{WalletOpts, WalletSigner}; use itertools::Itertools; @@ -136,11 +138,13 @@ pub struct InputState { pub struct CastTxBuilder { provider: P, tx: WithOtherFields, + /// Whether the transaction should be sent as a legacy transaction. legacy: bool, blob: bool, auth: Option, chain: Chain, etherscan_api_key: Option, + etherscan_api_version: EtherscanApiVersion, access_list: Option>, state: S, } @@ -152,8 +156,10 @@ impl> CastTxBuilder { let mut tx = WithOtherFields::::default(); let chain = utils::get_chain(config.chain, &provider).await?; + let etherscan_api_version = config.get_etherscan_api_version(Some(chain)); let etherscan_api_key = config.get_etherscan_api_key(Some(chain)); - let legacy = tx_opts.legacy || chain.is_legacy(); + // mark it as legacy if requested or the chain is legacy and no 7702 is provided. + let legacy = tx_opts.legacy || (chain.is_legacy() && tx_opts.auth.is_none()); if let Some(gas_limit) = tx_opts.gas_limit { tx.set_gas_limit(gas_limit.to()); @@ -192,6 +198,7 @@ impl> CastTxBuilder { blob: tx_opts.blob, chain, etherscan_api_key, + etherscan_api_version, auth: tx_opts.auth, access_list: tx_opts.access_list, state: InitState, @@ -208,6 +215,7 @@ impl> CastTxBuilder { blob: self.blob, chain: self.chain, etherscan_api_key: self.etherscan_api_key, + etherscan_api_version: self.etherscan_api_version, auth: self.auth, access_list: self.access_list, state: ToState { to }, @@ -233,6 +241,7 @@ impl> CastTxBuilder { self.chain, &self.provider, self.etherscan_api_key.as_deref(), + self.etherscan_api_version, ) .await? } else { @@ -264,6 +273,7 @@ impl> CastTxBuilder { blob: self.blob, chain: self.chain, etherscan_api_key: self.etherscan_api_key, + etherscan_api_version: self.etherscan_api_version, auth: self.auth, access_list: self.access_list, state: InputState { kind: self.state.to.into(), input, func }, diff --git a/crates/cast/tests/cli/main.rs b/crates/cast/tests/cli/main.rs index be2e9a7ed7101..bf825cdd9f95f 100644 --- a/crates/cast/tests/cli/main.rs +++ b/crates/cast/tests/cli/main.rs @@ -1,15 +1,16 @@ //! Contains various tests for checking cast commands use alloy_chains::NamedChain; +use alloy_hardforks::EthereumHardfork; use alloy_network::{TransactionBuilder, TransactionResponse}; use alloy_primitives::{address, b256, Bytes, B256}; use alloy_provider::{Provider, ProviderBuilder}; use alloy_rpc_types::{BlockNumberOrTag, Index, TransactionRequest}; -use anvil::{EthereumHardfork, NodeConfig}; +use anvil::NodeConfig; use foundry_test_utils::{ rpc::{ next_etherscan_api_key, next_http_archive_rpc_url, next_http_rpc_endpoint, - next_mainnet_etherscan_api_key, next_rpc_endpoint, next_ws_rpc_endpoint, + next_rpc_endpoint, next_ws_rpc_endpoint, }, str, util::OutputExt, @@ -55,7 +56,7 @@ Options: -j, --threads Number of threads to use. Specifying 0 defaults to the number of logical cores - [aliases: jobs] + [aliases: --jobs] -V, --version Print version @@ -290,6 +291,66 @@ casttest!(wallet_sign_message_hex_data, |_prj, cmd| { "#]]); }); +// +// tests that `cast wallet sign` and `cast wallet verify` work with the same message as input +casttest!(wallet_sign_and_verify_message_hex_data, |_prj, cmd| { + // message="$1" + // mnemonic="test test test test test test test test test test test junk" + // key=$(cast wallet private-key --mnemonic "$mnemonic") + // address=$(cast wallet address --mnemonic "$mnemonic") + // signature=$(cast wallet sign --private-key "$key" "$message") + // cast wallet verify --address "$address" "$message" "$signature" + let mnemonic = "test test test test test test test test test test test junk"; + let key = "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80"; + let address = "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266"; + cmd.args(["wallet", "private-key", "--mnemonic", mnemonic]).assert_success().stdout_eq(str![[ + r#" +0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 + +"# + ]]); + cmd.cast_fuse().args(["wallet", "address", "--mnemonic", mnemonic]).assert_success().stdout_eq( + str![[r#" +0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 + +"#]], + ); + + let msg_hex = "0x0000000000000000000000000000000000000000000000000000000000000001"; + let signature_hex = "0xed769da87f78d0166b30aebf2767ceed5a3867da21b2fba8c6527af256bbcebe24a1e758ec8ad1ffc29cfefa540ea7ba7966c0edf6907af82348f894ba4f40fa1b"; + cmd.cast_fuse().args([ + "wallet", "sign", "--private-key",key, msg_hex + ]).assert_success().stdout_eq(str![[r#" +0xed769da87f78d0166b30aebf2767ceed5a3867da21b2fba8c6527af256bbcebe24a1e758ec8ad1ffc29cfefa540ea7ba7966c0edf6907af82348f894ba4f40fa1b + +"#]]); + + cmd.cast_fuse() + .args(["wallet", "verify", "--address", address, msg_hex, signature_hex]) + .assert_success() + .stdout_eq(str![[r#" +Validation succeeded. Address 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 signed this message. + +"#]]); + + let msg_raw = "0000000000000000000000000000000000000000000000000000000000000001"; + let signature_raw = "0x27a97b378477d9d004bd19cbd838d59bbb9847074ae4cc5b5975cc5566065eea76ee5b752fcdd483073e1baba548d82d9accc8603b3781bcc9abf195614cd3411c"; + cmd.cast_fuse().args([ + "wallet", "sign", "--private-key",key, msg_raw + ]).assert_success().stdout_eq(str![[r#" +0x27a97b378477d9d004bd19cbd838d59bbb9847074ae4cc5b5975cc5566065eea76ee5b752fcdd483073e1baba548d82d9accc8603b3781bcc9abf195614cd3411c + +"#]]); + + cmd.cast_fuse() + .args(["wallet", "verify", "--address", address, msg_raw, signature_raw]) + .assert_success() + .stdout_eq(str![[r#" +Validation succeeded. Address 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 signed this message. + +"#]]); +}); + // tests that `cast wallet sign typed-data` outputs the expected signature, given a JSON string casttest!(wallet_sign_typed_data_string, |_prj, cmd| { cmd.args([ @@ -699,6 +760,31 @@ casttest!(estimate_function_gas, |_prj, cmd| { assert!(output.ge(&0)); }); +// tests that `cast estimate --cost` is working correctly. +casttest!(estimate_function_cost, |_prj, cmd| { + let eth_rpc_url = next_http_rpc_endpoint(); + + // ensure we get a positive non-error value for cost estimate + let output: f64 = cmd + .args([ + "estimate", + "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045", // vitalik.eth + "--value", + "100", + "deposit()", + "--rpc-url", + eth_rpc_url.as_str(), + "--cost", + ]) + .assert_success() + .get_output() + .stdout_lossy() + .trim() + .parse() + .unwrap(); + assert!(output > 0f64); +}); + // tests that `cast estimate --create` is working correctly. casttest!(estimate_contract_deploy_gas, |_prj, cmd| { let eth_rpc_url = next_http_rpc_endpoint(); @@ -1236,6 +1322,37 @@ casttest!(mktx_raw_unsigned, |_prj, cmd| { ]]); }); +casttest!(mktx_ethsign, async |_prj, cmd| { + let (_api, handle) = anvil::spawn(NodeConfig::test()).await; + let rpc = handle.http_endpoint(); + cmd.args([ + "mktx", + "--from", + "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", + "--chain", + "31337", + "--nonce", + "0", + "--gas-limit", + "21000", + "--gas-price", + "10000000000", + "--priority-gas-price", + "1000000000", + "0x0000000000000000000000000000000000000001", + "--ethsign", + "--rpc-url", + rpc.as_str(), + ]) + .assert_success() + .stdout_eq(str![[ + r#" +0x02f86d827a6980843b9aca008502540be4008252089400000000000000000000000000000000000000018080c001a0b8eeb1ded87b085859c510c5692bed231e3ee8b068ccf71142bbf28da0e95987a07813b676a248ae8055f28495021d78dee6695479d339a6ad9d260d9eaf20674c + +"# + ]]); +}); + // tests that the raw encoded transaction is returned casttest!(tx_raw, |_prj, cmd| { let rpc = next_http_rpc_endpoint(); @@ -1318,6 +1435,17 @@ Error: Must specify a recipient address or contract code to deploy "#]]); }); +// +casttest!(send_7702_conflicts_with_create, |_prj, cmd| { + cmd.args([ + "send", "--private-key", "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80" ,"--auth", "0xf85c827a6994f39fd6e51aad88f6f4ce6ab8827279cfffb922668001a03e1a66234e71242afcc7bc46c8950c3b2997b102db257774865f1232d2e7bf48a045e252dad189b27b2306792047745eba86bff0dd18aca813dbf3fba8c4e94576", "--create", "0x60806040523373ffffffffffffffffffffffffffffffffffffffff163273ffffffffffffffffffffffffffffffffffffffff1614610072576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610069906100e5565b60405180910390fd5b3373ffffffffffffffffffffffffffffffffffffffff16ff5b5f82825260208201905092915050565b7f74782e6f726967696e203d3d206d73672e73656e6465720000000000000000005f82015250565b5f6100cf60178361008b565b91506100da8261009b565b602082019050919050565b5f6020820190508181035f8301526100fc816100c3565b905091905056fe" + ]); + cmd.assert_failure().stderr_eq(str![[r#" +Error: EIP-7702 transactions can't be CREATE transactions and require a destination address + +"#]]); +}); + casttest!(storage, |_prj, cmd| { let rpc = next_http_archive_rpc_url(); cmd.args(["storage", "vitalik.eth", "1", "--rpc-url", &rpc]).assert_success().stdout_eq(str![ @@ -1378,7 +1506,7 @@ casttest!(storage_layout_simple, |_prj, cmd| { "--block", "21034138", "--etherscan-api-key", - next_mainnet_etherscan_api_key().as_str(), + next_etherscan_api_key().as_str(), "0x13b0D85CcB8bf860b6b79AF3029fCA081AE9beF2", ]) .assert_success() @@ -1405,7 +1533,7 @@ casttest!(storage_layout_simple_json, |_prj, cmd| { "--block", "21034138", "--etherscan-api-key", - next_mainnet_etherscan_api_key().as_str(), + next_etherscan_api_key().as_str(), "0x13b0D85CcB8bf860b6b79AF3029fCA081AE9beF2", "--json", ]) @@ -1422,7 +1550,7 @@ casttest!(storage_layout_complex, |_prj, cmd| { "--block", "21034138", "--etherscan-api-key", - next_mainnet_etherscan_api_key().as_str(), + next_etherscan_api_key().as_str(), "0xBA12222222228d8Ba445958a75a0704d566BF2C8", ]) .assert_success() @@ -1470,7 +1598,7 @@ casttest!(storage_layout_complex_proxy, |_prj, cmd| { "--block", "7857852", "--etherscan-api-key", - next_mainnet_etherscan_api_key().as_str(), + next_etherscan_api_key().as_str(), "0xE2588A9CAb7Ea877206E35f615a39f84a64A7A3b", "--proxy", "0x29fcb43b46531bca003ddc8fcb67ffe91900c762" @@ -1512,7 +1640,7 @@ casttest!(storage_layout_complex_json, |_prj, cmd| { "--block", "21034138", "--etherscan-api-key", - next_mainnet_etherscan_api_key().as_str(), + next_etherscan_api_key().as_str(), "0xBA12222222228d8Ba445958a75a0704d566BF2C8", "--json", ]) @@ -1601,7 +1729,7 @@ casttest!(fetch_weth_interface_from_etherscan, |_prj, cmd| { cmd.args([ "interface", "--etherscan-api-key", - &next_mainnet_etherscan_api_key(), + &next_etherscan_api_key(), "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", ]) .assert_success() @@ -1793,8 +1921,7 @@ Transaction successfully executed. casttest!(send_eip7702, async |_prj, cmd| { let (_api, handle) = - anvil::spawn(NodeConfig::test().with_hardfork(Some(EthereumHardfork::PragueEOF.into()))) - .await; + anvil::spawn(NodeConfig::test().with_hardfork(Some(EthereumHardfork::Prague.into()))).await; let endpoint = handle.http_endpoint(); cmd.args([ @@ -1880,7 +2007,7 @@ casttest!(fetch_creation_code_from_etherscan, |_prj, cmd| { cmd.args([ "creation-code", "--etherscan-api-key", - &next_mainnet_etherscan_api_key(), + &next_etherscan_api_key(), "0x0923cad07f06b2d0e5e49e63b8b35738d4156b95", "--rpc-url", eth_rpc_url.as_str(), @@ -1899,7 +2026,7 @@ casttest!(fetch_creation_code_only_args_from_etherscan, |_prj, cmd| { cmd.args([ "creation-code", "--etherscan-api-key", - &next_mainnet_etherscan_api_key(), + &next_etherscan_api_key(), "0x6982508145454ce325ddbe47a25d4ec3d2311933", "--rpc-url", eth_rpc_url.as_str(), @@ -1919,7 +2046,7 @@ casttest!(fetch_constructor_args_from_etherscan, |_prj, cmd| { cmd.args([ "constructor-args", "--etherscan-api-key", - &next_mainnet_etherscan_api_key(), + &next_etherscan_api_key(), "0x6982508145454ce325ddbe47a25d4ec3d2311933", "--rpc-url", eth_rpc_url.as_str(), @@ -1940,7 +2067,7 @@ casttest!(test_non_mainnet_traces, |prj, cmd| { "--rpc-url", next_rpc_endpoint(NamedChain::Optimism).as_str(), "--etherscan-api-key", - next_etherscan_api_key(NamedChain::Optimism).as_str(), + next_etherscan_api_key().as_str(), ]) .assert_success() .stdout_eq(str![[r#" @@ -1963,7 +2090,7 @@ casttest!(fetch_artifact_from_etherscan, |_prj, cmd| { cmd.args([ "artifact", "--etherscan-api-key", - &next_mainnet_etherscan_api_key(), + &next_etherscan_api_key(), "0x0923cad07f06b2d0e5e49e63b8b35738d4156b95", "--rpc-url", eth_rpc_url.as_str(), @@ -2227,7 +2354,7 @@ contract CounterInExternalLibScript is Script { ... Traces: [..] → new @0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512 - ├─ [..] 0x52F3e85EC3F0f9D0a2200D646482fcD134D5adc9::updateCounterInExternalLib(0, 100) [delegatecall] + ├─ [..] 0x6fD8bf6770F4bEe578348D24028000cE9c4D2bB9::updateCounterInExternalLib(0, 100) [delegatecall] │ └─ ← [Stop] └─ ← [Return] 62 bytes of code @@ -2394,7 +2521,7 @@ forgetest_async!(cast_run_impersonated_tx, |_prj, cmd| { let http_endpoint = handle.http_endpoint(); - let provider = ProviderBuilder::new().on_http(http_endpoint.parse().unwrap()); + let provider = ProviderBuilder::new().connect_http(http_endpoint.parse().unwrap()); // send impersonated tx let tx = TransactionRequest::default() @@ -2444,7 +2571,7 @@ contract WETH9 { casttest!(fetch_src_default, |_prj, cmd| { let weth = address!("0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"); - let etherscan_api_key = next_mainnet_etherscan_api_key(); + let etherscan_api_key = next_etherscan_api_key(); cmd.args(["source", &weth.to_string(), "--flatten", "--etherscan-api-key", ðerscan_api_key]) .assert_success() @@ -2457,6 +2584,85 @@ contract WETH9 { ..."#]]); }); +// +// +casttest!(odyssey_can_run_p256_precompile, |_prj, cmd| { + cmd.args([ + "run", + "0x17b2de59ebd7dfd2452a3638a16737b6b65ae816c1c5571631dc0d80b63c41de", + "--rpc-url", + next_rpc_endpoint(NamedChain::Base).as_str(), + "--quick", + "--odyssey", + ]) + .assert_success() + .stdout_eq(str![[r#" +Traces: + [88087] 0xc2FF493F28e894742b968A7DB5D3F21F0aD80C6c::execute(0x0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000a12384c5e52fd646e7bc7f6b3b33a605651f566e000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000170000000000000000000000000000000000000000000000000000000000000000000000000000000000000000833589fcd6edb6e08f4c7c32d4f71b54bda02913000000000000000000000000000000000000000000000000000000000000060f000000000000000000000000000000000000000000000000000000000000060f0000000000000000000000000000000000000000000000000000000000036cd000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000320000000000000000000000000000000000000000000000000000000000000060f000000000000000000000000000000000000000000000000000000000000060f000000000000000000000000327a25ad5cfe5c4d4339c1a4267d4a83e8c93312000000000000000000000000000000000000000000000000000000000000034000000000000000000000000000000000000000000000000000000000000005a00000000000000000000000000b55b053230e4effb6609de652fca73fd1c2980400000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000221000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000006cdd519280ec730727f07aa36550bde31a1d5f3097818f3425c2f083ed33a91f080fa2afac0071f6e1af9a0e9c09b851bf01e68bc8a1c1f89f686c48205762f92500000000000000000000000000000000000000000000000000000000000000244242424242424242424242424242424242424242424242424242424242424242010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000827b226368616c6c656e6765223a224b51704d51446e7841757a726f68522d483878472d5a536b625249702d76515f5f5f4a714259357a655038222c2263726f73734f726967696e223a66616c73652c226f726967696e223a2268747470732f2f6974686163612e78797a222c2274797065223a22776562617574686e2e676574227d0000000000000000000000000000000000000000000000000000000000001bde17b8de18819c9eb86cefc3920ddb5d3d4254de276e3d6e18dd2b399f732b00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000) + ├─ [2241] 0xA12384c5E52fD646E7BC7F6B3b33A605651F566E::fallback(00) [staticcall] + │ └─ ← [Return] 0x0000000000000000000000000b55b053230e4effb6609de652fca73fd1c29804 + ├─ [9750] 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913::balanceOf(0xA12384c5E52fD646E7BC7F6B3b33A605651F566E) [staticcall] + │ ├─ [2553] 0x2Ce6311ddAE708829bc0784C967b7d77D19FD779::balanceOf(0xA12384c5E52fD646E7BC7F6B3b33A605651F566E) [delegatecall] + │ │ └─ ← [Return] 0x000000000000000000000000000000000000000000000000000000000000f3b9 + │ └─ ← [Return] 0x000000000000000000000000000000000000000000000000000000000000f3b9 + ├─ [61992] 0xc2FF493F28e894742b968A7DB5D3F21F0aD80C6c::00000000(00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000a12384c5e52fd646e7bc7f6b3b33a605651f566e000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000170000000000000000000000000000000000000000000000000000000000000000000000000000000000000000833589fcd6edb6e08f4c7c32d4f71b54bda02913000000000000000000000000000000000000000000000000000000000000060f000000000000000000000000000000000000000000000000000000000000060f0000000000000000000000000000000000000000000000000000000000036cd000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000320000000000000000000000000000000000000000000000000000000000000060f000000000000000000000000000000000000000000000000000000000000060f000000000000000000000000327a25ad5cfe5c4d4339c1a4267d4a83e8c93312000000000000000000000000000000000000000000000000000000000000034000000000000000000000000000000000000000000000000000000000000005a00000000000000000000000000b55b053230e4effb6609de652fca73fd1c2980400000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000221000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000006cdd519280ec730727f07aa36550bde31a1d5f3097818f3425c2f083ed33a91f080fa2afac0071f6e1af9a0e9c09b851bf01e68bc8a1c1f89f686c48205762f92500000000000000000000000000000000000000000000000000000000000000244242424242424242424242424242424242424242424242424242424242424242010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000827b226368616c6c656e6765223a224b51704d51446e7841757a726f68522d483878472d5a536b625249702d76515f5f5f4a714259357a655038222c2263726f73734f726967696e223a66616c73652c226f726967696e223a2268747470732f2f6974686163612e78797a222c2274797065223a22776562617574686e2e676574227d0000000000000000000000000000000000000000000000000000000000001bde17b8de18819c9eb86cefc3920ddb5d3d4254de276e3d6e18dd2b399f732b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000) + │ ├─ [21620] 0xA12384c5E52fD646E7BC7F6B3b33A605651F566E::unwrapAndValidateSignature(0x290a4c4039f102eceba2147e1fcc46f994a46d1229faf43ffff26a058e7378ff, 0x000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000006cdd519280ec730727f07aa36550bde31a1d5f3097818f3425c2f083ed33a91f080fa2afac0071f6e1af9a0e9c09b851bf01e68bc8a1c1f89f686c48205762f92500000000000000000000000000000000000000000000000000000000000000244242424242424242424242424242424242424242424242424242424242424242010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000827b226368616c6c656e6765223a224b51704d51446e7841757a726f68522d483878472d5a536b625249702d76515f5f5f4a714259357a655038222c2263726f73734f726967696e223a66616c73652c226f726967696e223a2268747470732f2f6974686163612e78797a222c2274797065223a22776562617574686e2e676574227d0000000000000000000000000000000000000000000000000000000000001bde17b8de18819c9eb86cefc3920ddb5d3d4254de276e3d6e18dd2b399f732b00) [staticcall] + │ │ ├─ [18617] 0x0B55b053230E4EFFb6609de652fCa73Fd1C29804::unwrapAndValidateSignature(0x290a4c4039f102eceba2147e1fcc46f994a46d1229faf43ffff26a058e7378ff, 0x000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000006cdd519280ec730727f07aa36550bde31a1d5f3097818f3425c2f083ed33a91f080fa2afac0071f6e1af9a0e9c09b851bf01e68bc8a1c1f89f686c48205762f92500000000000000000000000000000000000000000000000000000000000000244242424242424242424242424242424242424242424242424242424242424242010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000827b226368616c6c656e6765223a224b51704d51446e7841757a726f68522d483878472d5a536b625249702d76515f5f5f4a714259357a655038222c2263726f73734f726967696e223a66616c73652c226f726967696e223a2268747470732f2f6974686163612e78797a222c2274797065223a22776562617574686e2e676574227d0000000000000000000000000000000000000000000000000000000000001bde17b8de18819c9eb86cefc3920ddb5d3d4254de276e3d6e18dd2b399f732b00) [delegatecall] + │ │ │ ├─ [2369] 0xc2FF493F28e894742b968A7DB5D3F21F0aD80C6c::pauseFlag() [staticcall] + │ │ │ │ └─ ← [Return] 0x0000000000000000000000000000000000000000000000000000000000000000 + │ │ │ ├─ [120] PRECOMPILES::sha256(0x7b226368616c6c656e6765223a224b51704d51446e7841757a726f68522d483878472d5a536b625249702d76515f5f5f4a714259357a655038222c2263726f73734f726967696e223a66616c73652c226f726967696e223a2268747470732f2f6974686163612e78797a222c2274797065223a22776562617574686e2e676574227d) [staticcall] + │ │ │ │ └─ ← [Return] 0xc13089327d3c20c0ce35f2f058c423de29977e6950e406c095e366a8fabd463f + │ │ │ ├─ [96] PRECOMPILES::sha256(0x424242424242424242424242424242424242424242424242424242424242424201000000c13089327d3c20c0ce35f2f058c423de29977e6950e406c095e366a8fabd463f) [staticcall] + │ │ │ │ └─ ← [Return] 0xc544bd9a4ea526dda3a008f43c21b6f0be3031b1ff71832b9876915dc91deea0 + │ │ │ ├─ [3450] 0x0000000000000000000000000000000000000100::c544bd9a(4ea526dda3a008f43c21b6f0be3031b1ff71832b9876915dc91deea0dd519280ec730727f07aa36550bde31a1d5f3097818f3425c2f083ed33a91f080fa2afac0071f6e1af9a0e9c09b851bf01e68bc8a1c1f89f686c48205762f925bf54fa13f88658092efa36c51b1e3c4db31d3afb92812fb852dac7cf9614bc479bf5da7241d9c4ab1b431b57ec3369587b4c831d7a564438990da053708c3289) [staticcall] + │ │ │ │ └─ ← [Return] 0x0000000000000000000000000000000000000000000000000000000000000001 + │ │ │ └─ ← [Return] 0x00000000000000000000000000000000000000000000000000000000000000011bde17b8de18819c9eb86cefc3920ddb5d3d4254de276e3d6e18dd2b399f732b + │ │ └─ ← [Return] 0x00000000000000000000000000000000000000000000000000000000000000011bde17b8de18819c9eb86cefc3920ddb5d3d4254de276e3d6e18dd2b399f732b + │ ├─ [5994] 0xA12384c5E52fD646E7BC7F6B3b33A605651F566E::checkAndIncrementNonce(23) + │ │ ├─ [5608] 0x0B55b053230E4EFFb6609de652fCa73Fd1C29804::checkAndIncrementNonce(23) [delegatecall] + │ │ │ └─ ← [Stop] + │ │ └─ ← [Return] + │ ├─ [3250] 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913::balanceOf(0x327a25aD5Cfe5c4D4339C1A4267D4a83E8c93312) [staticcall] + │ │ ├─ [2553] 0x2Ce6311ddAE708829bc0784C967b7d77D19FD779::balanceOf(0x327a25aD5Cfe5c4D4339C1A4267D4a83E8c93312) [delegatecall] + │ │ │ └─ ← [Return] 0x000000000000000000000000000000000000000000000000000000000000968b + │ │ └─ ← [Return] 0x000000000000000000000000000000000000000000000000000000000000968b + │ ├─ [16411] 0xA12384c5E52fD646E7BC7F6B3b33A605651F566E::pay(1551, 0x1bde17b8de18819c9eb86cefc3920ddb5d3d4254de276e3d6e18dd2b399f732b, 0x290a4c4039f102eceba2147e1fcc46f994a46d1229faf43ffff26a058e7378ff, 0x0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000a12384c5e52fd646e7bc7f6b3b33a605651f566e000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000170000000000000000000000000000000000000000000000000000000000000000000000000000000000000000833589fcd6edb6e08f4c7c32d4f71b54bda02913000000000000000000000000000000000000000000000000000000000000060f000000000000000000000000000000000000000000000000000000000000060f0000000000000000000000000000000000000000000000000000000000036cd000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000320000000000000000000000000000000000000000000000000000000000000060f000000000000000000000000000000000000000000000000000000000000060f000000000000000000000000327a25ad5cfe5c4d4339c1a4267d4a83e8c93312000000000000000000000000000000000000000000000000000000000000034000000000000000000000000000000000000000000000000000000000000005a00000000000000000000000000b55b053230e4effb6609de652fca73fd1c2980400000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000221000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000006cdd519280ec730727f07aa36550bde31a1d5f3097818f3425c2f083ed33a91f080fa2afac0071f6e1af9a0e9c09b851bf01e68bc8a1c1f89f686c48205762f92500000000000000000000000000000000000000000000000000000000000000244242424242424242424242424242424242424242424242424242424242424242010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000827b226368616c6c656e6765223a224b51704d51446e7841757a726f68522d483878472d5a536b625249702d76515f5f5f4a714259357a655038222c2263726f73734f726967696e223a66616c73652c226f726967696e223a2268747470732f2f6974686163612e78797a222c2274797065223a22776562617574686e2e676574227d0000000000000000000000000000000000000000000000000000000000001bde17b8de18819c9eb86cefc3920ddb5d3d4254de276e3d6e18dd2b399f732b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000) + │ │ ├─ [15711] 0x0B55b053230E4EFFb6609de652fCa73Fd1C29804::pay(1551, 0x1bde17b8de18819c9eb86cefc3920ddb5d3d4254de276e3d6e18dd2b399f732b, 0x290a4c4039f102eceba2147e1fcc46f994a46d1229faf43ffff26a058e7378ff, 0x0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000a12384c5e52fd646e7bc7f6b3b33a605651f566e000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000170000000000000000000000000000000000000000000000000000000000000000000000000000000000000000833589fcd6edb6e08f4c7c32d4f71b54bda02913000000000000000000000000000000000000000000000000000000000000060f000000000000000000000000000000000000000000000000000000000000060f0000000000000000000000000000000000000000000000000000000000036cd000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000320000000000000000000000000000000000000000000000000000000000000060f000000000000000000000000000000000000000000000000000000000000060f000000000000000000000000327a25ad5cfe5c4d4339c1a4267d4a83e8c93312000000000000000000000000000000000000000000000000000000000000034000000000000000000000000000000000000000000000000000000000000005a00000000000000000000000000b55b053230e4effb6609de652fca73fd1c2980400000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000221000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000006cdd519280ec730727f07aa36550bde31a1d5f3097818f3425c2f083ed33a91f080fa2afac0071f6e1af9a0e9c09b851bf01e68bc8a1c1f89f686c48205762f92500000000000000000000000000000000000000000000000000000000000000244242424242424242424242424242424242424242424242424242424242424242010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000827b226368616c6c656e6765223a224b51704d51446e7841757a726f68522d483878472d5a536b625249702d76515f5f5f4a714259357a655038222c2263726f73734f726967696e223a66616c73652c226f726967696e223a2268747470732f2f6974686163612e78797a222c2274797065223a22776562617574686e2e676574227d0000000000000000000000000000000000000000000000000000000000001bde17b8de18819c9eb86cefc3920ddb5d3d4254de276e3d6e18dd2b399f732b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000) [delegatecall] + │ │ │ ├─ [12963] 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913::transfer(0x327a25aD5Cfe5c4D4339C1A4267D4a83E8c93312, 1551) + │ │ │ │ ├─ [12263] 0x2Ce6311ddAE708829bc0784C967b7d77D19FD779::transfer(0x327a25aD5Cfe5c4D4339C1A4267D4a83E8c93312, 1551) [delegatecall] + │ │ │ │ │ ├─ emit Transfer(param0: 0xA12384c5E52fD646E7BC7F6B3b33A605651F566E, param1: 0x327a25aD5Cfe5c4D4339C1A4267D4a83E8c93312, param2: 1551) + │ │ │ │ │ └─ ← [Return] 0x0000000000000000000000000000000000000000000000000000000000000001 + │ │ │ │ └─ ← [Return] 0x0000000000000000000000000000000000000000000000000000000000000001 + │ │ │ └─ ← [Stop] + │ │ └─ ← [Return] + │ ├─ [1250] 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913::balanceOf(0x327a25aD5Cfe5c4D4339C1A4267D4a83E8c93312) [staticcall] + │ │ ├─ [553] 0x2Ce6311ddAE708829bc0784C967b7d77D19FD779::balanceOf(0x327a25aD5Cfe5c4D4339C1A4267D4a83E8c93312) [delegatecall] + │ │ │ └─ ← [Return] 0x0000000000000000000000000000000000000000000000000000000000009c9a + │ │ └─ ← [Return] 0x0000000000000000000000000000000000000000000000000000000000009c9a + │ ├─ [5675] 0xc2FF493F28e894742b968A7DB5D3F21F0aD80C6c::00000001(00000000000000000000000000000000000000000000000000000000000000001bde17b8de18819c9eb86cefc3920ddb5d3d4254de276e3d6e18dd2b399f732b290a4c4039f102eceba2147e1fcc46f994a46d1229faf43ffff26a058e7378ff0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000a12384c5e52fd646e7bc7f6b3b33a605651f566e000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000170000000000000000000000000000000000000000000000000000000000000000000000000000000000000000833589fcd6edb6e08f4c7c32d4f71b54bda02913000000000000000000000000000000000000000000000000000000000000060f000000000000000000000000000000000000000000000000000000000000060f0000000000000000000000000000000000000000000000000000000000036cd000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000320000000000000000000000000000000000000000000000000000000000000060f000000000000000000000000000000000000000000000000000000000000060f000000000000000000000000327a25ad5cfe5c4d4339c1a4267d4a83e8c93312000000000000000000000000000000000000000000000000000000000000034000000000000000000000000000000000000000000000000000000000000005a00000000000000000000000000b55b053230e4effb6609de652fca73fd1c2980400000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000221000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000006cdd519280ec730727f07aa36550bde31a1d5f3097818f3425c2f083ed33a91f080fa2afac0071f6e1af9a0e9c09b851bf01e68bc8a1c1f89f686c48205762f92500000000000000000000000000000000000000000000000000000000000000244242424242424242424242424242424242424242424242424242424242424242010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000827b226368616c6c656e6765223a224b51704d51446e7841757a726f68522d483878472d5a536b625249702d76515f5f5f4a714259357a655038222c2263726f73734f726967696e223a66616c73652c226f726967696e223a2268747470732f2f6974686163612e78797a222c2274797065223a22776562617574686e2e676574227d0000000000000000000000000000000000000000000000000000000000001bde17b8de18819c9eb86cefc3920ddb5d3d4254de276e3d6e18dd2b399f732b00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000) + │ │ ├─ [4148] 0xA12384c5E52fD646E7BC7F6B3b33A605651F566E::execute(0x0100000000007821000100000000000000000000000000000000000000000000, 0x0000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000201bde17b8de18819c9eb86cefc3920ddb5d3d4254de276e3d6e18dd2b399f732b) + │ │ │ ├─ [3693] 0x0B55b053230E4EFFb6609de652fCa73Fd1C29804::execute(0x0100000000007821000100000000000000000000000000000000000000000000, 0x0000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000201bde17b8de18819c9eb86cefc3920ddb5d3d4254de276e3d6e18dd2b399f732b) [delegatecall] + │ │ │ │ ├─ [435] 0xA12384c5E52fD646E7BC7F6B3b33A605651F566E::fallback() + │ │ │ │ │ ├─ [55] 0x0B55b053230E4EFFb6609de652fCa73Fd1C29804::fallback() [delegatecall] + │ │ │ │ │ │ └─ ← [Stop] + │ │ │ │ │ └─ ← [Return] + │ │ │ │ └─ ← [Stop] + │ │ │ └─ ← [Return] + │ │ └─ ← [Return] 0x0000000000000000000000000000000000000000000000000000000000000000 + │ └─ ← [Stop] + ├─ emit topic 0: 0x31e2fdd22f7eeca688d70008a7bee8e41aa5640885c2bc592419ae8d09d889f1 + │ topic 1: 0x000000000000000000000000a12384c5e52fd646e7bc7f6b3b33a605651f566e + │ topic 2: 0x0000000000000000000000000000000000000000000000000000000000000017 + │ data: 0x00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000 + └─ ← [Return] 0x0000000000000000000000000000000000000000000000000000000000000000 + + +Transaction successfully executed. +[GAS] + +"#]]); +}); + // tests cast send gas estimate execution failure message contains decoded custom error // forgetest_async!(cast_send_estimate_gas_error, |prj, cmd| { @@ -2527,3 +2733,38 @@ Error: Failed to estimate gas: server returned an error response: error code 3: "#]]); }); + +// +casttest!(estimate_base_da, |_prj, cmd| { + cmd.args(["da-estimate", "30558838", "-r", "https://mainnet.base.org/"]) + .assert_success() + .stdout_eq(str![[r#" +Estimated data availability size for block 30558838 with 225 transactions: 52916546100 + +"#]]); +}); + +// +casttest!(cast_call_return_array_of_tuples, |_prj, cmd| { + cmd.args(["call", "0x198FC70Dfe05E755C81e54bd67Bff3F729344B9b", "facets() returns ((address,bytes4[])[])", "--rpc-url", "https://rpc.viction.xyz"]) + .assert_success() + .stdout_eq(str![[r#" +[(0x9640977264aec6d4e9af381F548eee11b9e27aAe, [0x1f931c1c]), (0x395db83A04cC3d3F6C5eFc73896929Cf10D0F301, [0xcdffacc6, 0x52ef6b2c, 0xadfca15e, 0x7a0ed627, 0x01ffc9a7]), (0xC6F7b47F870024B0E2DF6DFd551E10c4A37A1cEa, [0x23452b9c, 0x7200b829, 0x8da5cb5b, 0xf2fde38b]), (0xc1f27c1f6c87e73e089Ffac23C236Fc4A9E3fccc, [0x1458d7ad, 0xd9caed12]), (0x70e272A93bc8344277a1f4390Ea6153A1D5fe450, [0x536db266, 0xfbb2d381, 0xfcd8e49e, 0x9afc19c7, 0x44e2b18c, 0x2d2506a9, 0x124f1ead, 0xc3a6a96b]), (0x662BCADB7A2CBb22367b2471d8A91E5b13FCe96B, [0x612ad9cb, 0xa4c3366e]), (0x190e03D49Ce76DDabC634a98629EDa6246aB5196, [0xa516f0f3, 0x5c2ed36a]), (0xAF69C0E3BBBf6AdE78f1466f86DfF86d64C8dA2A, [0x4630a0d8]), (0xD1317DA862AC5C145519E60D24372dc186EF7426, [0xd5bcb610, 0x5fd9ae2e, 0x2c57e884, 0x736eac0b, 0x4666fc80, 0x733214a3, 0xaf7060fd]), (0x176f558949e2a7C5217dD9C27Bf7A43c6783ee28, [0x7f99d7af, 0x103c5200, 0xc318eeda, 0xee0aa320, 0xdf1c3a5b, 0x070e81f1, 0xd53482cf, 0xf58ae2ce]), (0x531d69A3fAb6CB56A77B8402E6c217cB9cC902A9, [0xf86368ae, 0x5ad317a4, 0x0340e905, 0x2fc487ae])] + +"#]]); +}); + +// +casttest!(tx_raw_opstack_deposit, |_prj, cmd| { + cmd.args([ + "tx", + "0xf403cba612d1c01c027455c0d97427ccd5f7f99aac30017e065f81d1e30244ea", + "--raw", + "--rpc-url", + "https://sepolia.base.org", + ]).assert_success() + .stdout_eq(str![[r#" +0x7ef90207a0cbde10ec697aff886f95d2514bab434e455620627b9bb8ba33baaaa4d537d62794d45955f4de64f1840e5686e64278da901e263031944200000000000000000000000000000000000007872386f26fc10000872386f26fc1000083096c4980b901a4d764ad0b0001000000000000000000000000000000000000000000000000000000065132000000000000000000000000fd0bf71f60660e2f608ed56e1659c450eb1131200000000000000000000000004200000000000000000000000000000000000010000000000000000000000000000000000000000000000000002386f26fc1000000000000000000000000000000000000000000000000000000000000000493e000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000a41635f5fd000000000000000000000000ca11bde05977b3631167028862be2a173976ca110000000000000000000000005703b26fe5a7be820db1bf34c901a79da1a46ba4000000000000000000000000000000000000000000000000002386f26fc100000000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 + +"#]]); +}); diff --git a/crates/cheatcodes/Cargo.toml b/crates/cheatcodes/Cargo.toml index 49611dcca071c..f62bde4323e71 100644 --- a/crates/cheatcodes/Cargo.toml +++ b/crates/cheatcodes/Cargo.toml @@ -25,6 +25,7 @@ foundry-wallets.workspace = true forge-script-sequence.workspace = true alloy-dyn-abi.workspace = true +alloy-evm.workspace = true alloy-json-abi.workspace = true alloy-primitives.workspace = true alloy-genesis.workspace = true @@ -41,6 +42,7 @@ alloy-consensus = { workspace = true, features = ["k256", "kzg"] } alloy-network.workspace = true alloy-rlp.workspace = true alloy-chains.workspace = true +alloy-ens.workspace = true base64.workspace = true dialoguer = "0.11" diff --git a/crates/cheatcodes/assets/cheatcodes.json b/crates/cheatcodes/assets/cheatcodes.json index 5d46978a4fdb9..93cdf40cd4393 100644 --- a/crates/cheatcodes/assets/cheatcodes.json +++ b/crates/cheatcodes/assets/cheatcodes.json @@ -3252,7 +3252,7 @@ }, { "func": { - "id": "attachDelegation", + "id": "attachDelegation_0", "description": "Designate the next call as an EIP-7702 transaction", "declaration": "function attachDelegation(SignedDelegation calldata signedDelegation) external;", "visibility": "external", @@ -3270,6 +3270,26 @@ "status": "stable", "safety": "safe" }, + { + "func": { + "id": "attachDelegation_1", + "description": "Designate the next call as an EIP-7702 transaction, with optional cross-chain validity.", + "declaration": "function attachDelegation(SignedDelegation calldata signedDelegation, bool crossChain) external;", + "visibility": "external", + "mutability": "", + "signature": "attachDelegation((uint8,bytes32,bytes32,uint64,address),bool)", + "selector": "0xf4460d34", + "selectorBytes": [ + 244, + 70, + 13, + 52 + ] + }, + "group": "scripting", + "status": "stable", + "safety": "safe" + }, { "func": { "id": "blobBaseFee", @@ -4274,6 +4294,106 @@ "status": "stable", "safety": "unsafe" }, + { + "func": { + "id": "eip712HashStruct_0", + "description": "Generates the struct hash of the canonical EIP-712 type representation and its abi-encoded data.\nSupports 2 different inputs:\n 1. Name of the type (i.e. \"PermitSingle\"):\n * requires previous binding generation with `forge bind-json`.\n * bindings will be retrieved from the path configured in `foundry.toml`.\n 2. String representation of the type (i.e. \"Foo(Bar bar) Bar(uint256 baz)\").\n * Note: the cheatcode will use the canonical type even if the input is malformated\n with the wrong order of elements or with extra whitespaces.", + "declaration": "function eip712HashStruct(string calldata typeNameOrDefinition, bytes calldata abiEncodedData) external pure returns (bytes32 typeHash);", + "visibility": "external", + "mutability": "pure", + "signature": "eip712HashStruct(string,bytes)", + "selector": "0xaedeaebc", + "selectorBytes": [ + 174, + 222, + 174, + 188 + ] + }, + "group": "utilities", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "eip712HashStruct_1", + "description": "Generates the struct hash of the canonical EIP-712 type representation and its abi-encoded data.\nRequires previous binding generation with `forge bind-json`.\nParams:\n * `bindingsPath`: path where the output of `forge bind-json` is stored.\n * `typeName`: Name of the type (i.e. \"PermitSingle\").\n * `abiEncodedData`: ABI-encoded data for the struct that is being hashed.", + "declaration": "function eip712HashStruct(string calldata bindingsPath, string calldata typeName, bytes calldata abiEncodedData) external pure returns (bytes32 typeHash);", + "visibility": "external", + "mutability": "pure", + "signature": "eip712HashStruct(string,string,bytes)", + "selector": "0x6d06c57c", + "selectorBytes": [ + 109, + 6, + 197, + 124 + ] + }, + "group": "utilities", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "eip712HashType_0", + "description": "Generates the hash of the canonical EIP-712 type representation.\nSupports 2 different inputs:\n 1. Name of the type (i.e. \"Transaction\"):\n * requires previous binding generation with `forge bind-json`.\n * bindings will be retrieved from the path configured in `foundry.toml`.\n 2. String representation of the type (i.e. \"Foo(Bar bar) Bar(uint256 baz)\").\n * Note: the cheatcode will output the canonical type even if the input is malformated\n with the wrong order of elements or with extra whitespaces.", + "declaration": "function eip712HashType(string calldata typeNameOrDefinition) external pure returns (bytes32 typeHash);", + "visibility": "external", + "mutability": "pure", + "signature": "eip712HashType(string)", + "selector": "0x6792e9e2", + "selectorBytes": [ + 103, + 146, + 233, + 226 + ] + }, + "group": "utilities", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "eip712HashType_1", + "description": "Generates the hash of the canonical EIP-712 type representation.\nRequires previous binding generation with `forge bind-json`.\nParams:\n * `bindingsPath`: path where the output of `forge bind-json` is stored.\n * `typeName`: Name of the type (i.e. \"Transaction\").", + "declaration": "function eip712HashType(string calldata bindingsPath, string calldata typeName) external pure returns (bytes32 typeHash);", + "visibility": "external", + "mutability": "pure", + "signature": "eip712HashType(string,string)", + "selector": "0x18fb6406", + "selectorBytes": [ + 24, + 251, + 100, + 6 + ] + }, + "group": "utilities", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "eip712HashTypedData", + "description": "Generates a ready-to-sign digest of human-readable typed data following the EIP-712 standard.", + "declaration": "function eip712HashTypedData(string calldata jsonData) external pure returns (bytes32 digest);", + "visibility": "external", + "mutability": "pure", + "signature": "eip712HashTypedData(string)", + "selector": "0xea25e615", + "selectorBytes": [ + 234, + 37, + 230, + 21 + ] + }, + "group": "utilities", + "status": "stable", + "safety": "safe" + }, { "func": { "id": "ensNamehash", @@ -9560,6 +9680,26 @@ "status": "stable", "safety": "unsafe" }, + { + "func": { + "id": "setSeed", + "description": "Set RNG seed.", + "declaration": "function setSeed(uint256 seed) external;", + "visibility": "external", + "mutability": "", + "signature": "setSeed(uint256)", + "selector": "0xc32a50f9", + "selectorBytes": [ + 195, + 42, + 80, + 249 + ] + }, + "group": "utilities", + "status": "stable", + "safety": "safe" + }, { "func": { "id": "shuffle", @@ -9620,6 +9760,26 @@ "status": "stable", "safety": "safe" }, + { + "func": { + "id": "signAndAttachDelegation_2", + "description": "Sign an EIP-7702 authorization and designate the next call as an EIP-7702 transaction, with optional cross-chain validity.", + "declaration": "function signAndAttachDelegation(address implementation, uint256 privateKey, bool crossChain) external returns (SignedDelegation memory signedDelegation);", + "visibility": "external", + "mutability": "", + "signature": "signAndAttachDelegation(address,uint256,bool)", + "selector": "0xd936e146", + "selectorBytes": [ + 217, + 54, + 225, + 70 + ] + }, + "group": "scripting", + "status": "stable", + "safety": "safe" + }, { "func": { "id": "signCompact_0", @@ -9740,6 +9900,26 @@ "status": "stable", "safety": "safe" }, + { + "func": { + "id": "signDelegation_2", + "description": "Sign an EIP-7702 authorization for delegation, with optional cross-chain validity.", + "declaration": "function signDelegation(address implementation, uint256 privateKey, bool crossChain) external returns (SignedDelegation memory signedDelegation);", + "visibility": "external", + "mutability": "", + "signature": "signDelegation(address,uint256,bool)", + "selector": "0xcdd7563d", + "selectorBytes": [ + 205, + 215, + 86, + 61 + ] + }, + "group": "scripting", + "status": "stable", + "safety": "safe" + }, { "func": { "id": "signP256", diff --git a/crates/cheatcodes/spec/src/vm.rs b/crates/cheatcodes/spec/src/vm.rs index afb687894f863..93eeaa21cd0bd 100644 --- a/crates/cheatcodes/spec/src/vm.rs +++ b/crates/cheatcodes/spec/src/vm.rs @@ -6,7 +6,6 @@ use super::*; use crate::Vm::ForgeContext; use alloy_sol_types::sol; use foundry_macros::Cheatcode; -use std::fmt; sol! { // Cheatcodes are marked as view/pure/none using the following rules: @@ -2229,10 +2228,18 @@ interface Vm { #[cheatcode(group = Scripting)] function signDelegation(address implementation, uint256 privateKey, uint64 nonce) external returns (SignedDelegation memory signedDelegation); + /// Sign an EIP-7702 authorization for delegation, with optional cross-chain validity. + #[cheatcode(group = Scripting)] + function signDelegation(address implementation, uint256 privateKey, bool crossChain) external returns (SignedDelegation memory signedDelegation); + /// Designate the next call as an EIP-7702 transaction #[cheatcode(group = Scripting)] function attachDelegation(SignedDelegation calldata signedDelegation) external; + /// Designate the next call as an EIP-7702 transaction, with optional cross-chain validity. + #[cheatcode(group = Scripting)] + function attachDelegation(SignedDelegation calldata signedDelegation, bool crossChain) external; + /// Sign an EIP-7702 authorization and designate the next call as an EIP-7702 transaction #[cheatcode(group = Scripting)] function signAndAttachDelegation(address implementation, uint256 privateKey) external returns (SignedDelegation memory signedDelegation); @@ -2241,6 +2248,10 @@ interface Vm { #[cheatcode(group = Scripting)] function signAndAttachDelegation(address implementation, uint256 privateKey, uint64 nonce) external returns (SignedDelegation memory signedDelegation); + /// Sign an EIP-7702 authorization and designate the next call as an EIP-7702 transaction, with optional cross-chain validity. + #[cheatcode(group = Scripting)] + function signAndAttachDelegation(address implementation, uint256 privateKey, bool crossChain) external returns (SignedDelegation memory signedDelegation); + /// Attach an EIP-4844 blob to the next call #[cheatcode(group = Scripting)] function attachBlob(bytes calldata blob) external; @@ -2868,6 +2879,10 @@ interface Vm { #[cheatcode(group = Utilities)] function shuffle(uint256[] calldata array) external returns (uint256[] memory); + /// Set RNG seed. + #[cheatcode(group = Utilities)] + function setSeed(uint256 seed) external; + /// Causes the next contract creation (via new) to fail and return its initcode in the returndata buffer. /// This allows type-safe access to the initcode payload that would be used for contract creation. /// Example usage: @@ -2877,6 +2892,55 @@ interface Vm { /// catch (bytes memory interceptedInitcode) { initcode = interceptedInitcode; } #[cheatcode(group = Utilities, safety = Unsafe)] function interceptInitcode() external; + + /// Generates the hash of the canonical EIP-712 type representation. + /// + /// Supports 2 different inputs: + /// 1. Name of the type (i.e. "Transaction"): + /// * requires previous binding generation with `forge bind-json`. + /// * bindings will be retrieved from the path configured in `foundry.toml`. + /// + /// 2. String representation of the type (i.e. "Foo(Bar bar) Bar(uint256 baz)"). + /// * Note: the cheatcode will output the canonical type even if the input is malformated + /// with the wrong order of elements or with extra whitespaces. + #[cheatcode(group = Utilities)] + function eip712HashType(string calldata typeNameOrDefinition) external pure returns (bytes32 typeHash); + + /// Generates the hash of the canonical EIP-712 type representation. + /// Requires previous binding generation with `forge bind-json`. + /// + /// Params: + /// * `bindingsPath`: path where the output of `forge bind-json` is stored. + /// * `typeName`: Name of the type (i.e. "Transaction"). + #[cheatcode(group = Utilities)] + function eip712HashType(string calldata bindingsPath, string calldata typeName) external pure returns (bytes32 typeHash); + + /// Generates the struct hash of the canonical EIP-712 type representation and its abi-encoded data. + /// + /// Supports 2 different inputs: + /// 1. Name of the type (i.e. "PermitSingle"): + /// * requires previous binding generation with `forge bind-json`. + /// * bindings will be retrieved from the path configured in `foundry.toml`. + /// + /// 2. String representation of the type (i.e. "Foo(Bar bar) Bar(uint256 baz)"). + /// * Note: the cheatcode will use the canonical type even if the input is malformated + /// with the wrong order of elements or with extra whitespaces. + #[cheatcode(group = Utilities)] + function eip712HashStruct(string calldata typeNameOrDefinition, bytes calldata abiEncodedData) external pure returns (bytes32 typeHash); + + /// Generates the struct hash of the canonical EIP-712 type representation and its abi-encoded data. + /// Requires previous binding generation with `forge bind-json`. + /// + /// Params: + /// * `bindingsPath`: path where the output of `forge bind-json` is stored. + /// * `typeName`: Name of the type (i.e. "PermitSingle"). + /// * `abiEncodedData`: ABI-encoded data for the struct that is being hashed. + #[cheatcode(group = Utilities)] + function eip712HashStruct(string calldata bindingsPath, string calldata typeName, bytes calldata abiEncodedData) external pure returns (bytes32 typeHash); + + /// Generates a ready-to-sign digest of human-readable typed data following the EIP-712 standard. + #[cheatcode(group = Utilities)] + function eip712HashTypedData(string calldata jsonData) external pure returns (bytes32 digest); } } diff --git a/crates/cheatcodes/src/config.rs b/crates/cheatcodes/src/config.rs index 210c76553cba7..1ad98cd93e92b 100644 --- a/crates/cheatcodes/src/config.rs +++ b/crates/cheatcodes/src/config.rs @@ -33,6 +33,8 @@ pub struct CheatsConfig { pub rpc_endpoints: ResolvedRpcEndpoints, /// Project's paths as configured pub paths: ProjectPathsConfig, + /// Path to the directory that contains the bindings generated by `forge bind-json`. + pub bind_json_path: PathBuf, /// Filesystem permissions for cheatcodes like `writeFile`, `readFile` pub fs_permissions: FsPermissions, /// Project root @@ -98,6 +100,7 @@ impl CheatsConfig { no_storage_caching: config.no_storage_caching, rpc_endpoints, paths: config.project_paths(), + bind_json_path: config.bind_json.out.clone(), fs_permissions: config.fs_permissions.clone().joined(config.root.as_ref()), root: config.root.clone(), broadcast: config.root.clone().join(&config.broadcast), @@ -303,6 +306,7 @@ impl Default for CheatsConfig { paths: ProjectPathsConfig::builder().build_with_root("./"), fs_permissions: Default::default(), root: Default::default(), + bind_json_path: PathBuf::default().join("utils").join("jsonBindings.sol"), broadcast: Default::default(), allowed_paths: vec![], evm_opts: Default::default(), diff --git a/crates/cheatcodes/src/crypto.rs b/crates/cheatcodes/src/crypto.rs index 8ef7e96aeff3b..be6829ee3110b 100644 --- a/crates/cheatcodes/src/crypto.rs +++ b/crates/cheatcodes/src/crypto.rs @@ -217,7 +217,7 @@ fn create_wallet(private_key: &U256, label: Option<&str>, state: &mut Cheatcodes .abi_encode()) } -fn encode_full_sig(sig: alloy_primitives::PrimitiveSignature) -> Vec { +fn encode_full_sig(sig: alloy_primitives::Signature) -> Vec { // Retrieve v, r and s from signature. let v = U256::from(sig.v() as u64 + 27); let r = B256::from(sig.r()); @@ -225,7 +225,7 @@ fn encode_full_sig(sig: alloy_primitives::PrimitiveSignature) -> Vec { (v, r, s).abi_encode() } -fn encode_compact_sig(sig: alloy_primitives::PrimitiveSignature) -> Vec { +fn encode_compact_sig(sig: alloy_primitives::Signature) -> Vec { // Implement EIP-2098 compact signature. let r = B256::from(sig.r()); let mut vs = sig.s(); @@ -233,7 +233,7 @@ fn encode_compact_sig(sig: alloy_primitives::PrimitiveSignature) -> Vec { (r, vs).abi_encode() } -fn sign(private_key: &U256, digest: &B256) -> Result { +fn sign(private_key: &U256, digest: &B256) -> Result { // The `ecrecover` precompile does not use EIP-155. No chain ID is needed. let wallet = parse_wallet(private_key)?; let sig = wallet.sign_hash_sync(digest)?; @@ -245,7 +245,7 @@ fn sign_with_wallet( state: &mut Cheatcodes, signer: Option
, digest: &B256, -) -> Result { +) -> Result { if state.wallets().is_empty() { bail!("no wallets available"); } diff --git a/crates/cheatcodes/src/error.rs b/crates/cheatcodes/src/error.rs index b77c889d59ab7..414c7dbed8f90 100644 --- a/crates/cheatcodes/src/error.rs +++ b/crates/cheatcodes/src/error.rs @@ -8,7 +8,7 @@ use foundry_config::UnresolvedEnvVarError; use foundry_evm_core::backend::{BackendError, DatabaseError}; use foundry_wallets::error::WalletSignerError; use k256::ecdsa::signature::Error as SignatureError; -use revm::primitives::EVMError; +use revm::context_interface::result::EVMError; use std::{borrow::Cow, fmt}; /// Cheatcode result type. @@ -283,6 +283,7 @@ impl_from!( alloy_sol_types::Error, alloy_dyn_abi::Error, alloy_primitives::SignatureError, + alloy_consensus::crypto::RecoveryError, eyre::Report, FsPathError, hex::FromHexError, diff --git a/crates/cheatcodes/src/evm.rs b/crates/cheatcodes/src/evm.rs index 8b7d714d3dd8f..573ba31f939e7 100644 --- a/crates/cheatcodes/src/evm.rs +++ b/crates/cheatcodes/src/evm.rs @@ -1,7 +1,7 @@ //! Implementations of [`Evm`](spec::Group::Evm) cheatcodes. use crate::{ - inspector::{InnerEcx, RecordDebugStepInfo}, + inspector::{Ecx, RecordDebugStepInfo}, BroadcastableTransaction, Cheatcode, Cheatcodes, CheatcodesExecutor, CheatsCtxt, Error, Result, Vm::*, }; @@ -14,11 +14,17 @@ use foundry_common::fs::{read_json_file, write_json_file}; use foundry_evm_core::{ backend::{DatabaseExt, RevertStateSnapshotAction}, constants::{CALLER, CHEATCODE_ADDRESS, HARDHAT_CONSOLE_ADDRESS, TEST_CONTRACT_ADDRESS}, + ContextExt, }; use foundry_evm_traces::StackSnapshotType; use itertools::Itertools; use rand::Rng; -use revm::primitives::{Account, Bytecode, SpecId, KECCAK_EMPTY}; +use revm::{ + bytecode::Bytecode, + context::{Block, JournalTr}, + primitives::{hardfork::SpecId, KECCAK_EMPTY}, + state::Account, +}; use std::{ collections::{btree_map::Entry, BTreeMap}, fmt::Display, @@ -74,7 +80,7 @@ pub struct GasRecord { /// The total gas used in the gas snapshot. pub gas_used: u64, /// Depth at which the gas snapshot was taken. - pub depth: u64, + pub depth: usize, } /// Records `deal` cheatcodes @@ -178,14 +184,14 @@ impl Cheatcode for loadCall { fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { target, slot } = *self; ensure_not_precompile!(&target, ccx); - ccx.ecx.load_account(target)?; - let mut val = ccx.ecx.sload(target, slot.into())?; + ccx.ecx.journaled_state.load_account(target)?; + let mut val = ccx.ecx.journaled_state.sload(target, slot.into())?; if val.is_cold && val.data.is_zero() { if ccx.state.has_arbitrary_storage(&target) { // If storage slot is untouched and load from a target with arbitrary storage, // then set random value for current slot. - let rand_value = ccx.state.rng().gen(); + let rand_value = ccx.state.rng().random(); ccx.state.arbitrary_storage.as_mut().unwrap().save( ccx.ecx, target, @@ -197,7 +203,7 @@ impl Cheatcode for loadCall { // If storage slot is untouched and load from a target that copies storage from // a source address with arbitrary storage, then copy existing arbitrary value. // If no arbitrary value generated yet, then the random one is saved and set. - let rand_value = ccx.state.rng().gen(); + let rand_value = ccx.state.rng().random(); val.data = ccx.state.arbitrary_storage.as_mut().unwrap().copy( ccx.ecx, target, @@ -229,9 +235,8 @@ impl Cheatcode for loadAllocsCall { }; // Then, load the allocs into the database. - ccx.ecx - .db - .load_allocs(&allocs, &mut ccx.ecx.journaled_state) + let (db, journal, _) = ccx.ecx.as_db_env_and_journal(); + db.load_allocs(&allocs, journal) .map(|()| Vec::default()) .map_err(|e| fmt_err!("failed to load allocs: {e}")) } @@ -241,14 +246,12 @@ impl Cheatcode for cloneAccountCall { fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { source, target } = self; - let account = ccx.ecx.journaled_state.load_account(*source, &mut ccx.ecx.db)?; - ccx.ecx.db.clone_account( - &genesis_account(account.data), - target, - &mut ccx.ecx.journaled_state, - )?; + let (db, journal, _) = ccx.ecx.as_db_env_and_journal(); + let account = journal.load_account(db, *source)?; + let genesis = &genesis_account(account.data); + db.clone_account(genesis, target, journal)?; // Cloned account should persist in forked envs. - ccx.ecx.db.add_persistent_account(*target); + ccx.ecx.journaled_state.database.add_persistent_account(*target); Ok(Default::default()) } } @@ -363,7 +366,7 @@ impl Cheatcode for chainIdCall { fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { newChainId } = self; ensure!(*newChainId <= U256::from(u64::MAX), "chain ID must be less than 2^64 - 1"); - ccx.ecx.env.cfg.chain_id = newChainId.to(); + ccx.ecx.cfg.chain_id = newChainId.to(); Ok(Default::default()) } } @@ -371,7 +374,7 @@ impl Cheatcode for chainIdCall { impl Cheatcode for coinbaseCall { fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { newCoinbase } = self; - ccx.ecx.env.block.coinbase = *newCoinbase; + ccx.ecx.block.beneficiary = *newCoinbase; Ok(Default::default()) } } @@ -380,11 +383,11 @@ impl Cheatcode for difficultyCall { fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { newDifficulty } = self; ensure!( - ccx.ecx.spec_id() < SpecId::MERGE, + ccx.ecx.cfg.spec < SpecId::MERGE, "`difficulty` is not supported after the Paris hard fork, use `prevrandao` instead; \ see EIP-4399: https://eips.ethereum.org/EIPS/eip-4399" ); - ccx.ecx.env.block.difficulty = *newDifficulty; + ccx.ecx.block.difficulty = *newDifficulty; Ok(Default::default()) } } @@ -392,7 +395,8 @@ impl Cheatcode for difficultyCall { impl Cheatcode for feeCall { fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { newBasefee } = self; - ccx.ecx.env.block.basefee = *newBasefee; + ensure!(*newBasefee <= U256::from(u64::MAX), "base fee must be less than 2^64 - 1"); + ccx.ecx.block.basefee = newBasefee.saturating_to(); Ok(Default::default()) } } @@ -401,11 +405,11 @@ impl Cheatcode for prevrandao_0Call { fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { newPrevrandao } = self; ensure!( - ccx.ecx.spec_id() >= SpecId::MERGE, + ccx.ecx.cfg.spec >= SpecId::MERGE, "`prevrandao` is not supported before the Paris hard fork, use `difficulty` instead; \ see EIP-4399: https://eips.ethereum.org/EIPS/eip-4399" ); - ccx.ecx.env.block.prevrandao = Some(*newPrevrandao); + ccx.ecx.block.prevrandao = Some(*newPrevrandao); Ok(Default::default()) } } @@ -414,11 +418,11 @@ impl Cheatcode for prevrandao_1Call { fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { newPrevrandao } = self; ensure!( - ccx.ecx.spec_id() >= SpecId::MERGE, + ccx.ecx.cfg.spec >= SpecId::MERGE, "`prevrandao` is not supported before the Paris hard fork, use `difficulty` instead; \ see EIP-4399: https://eips.ethereum.org/EIPS/eip-4399" ); - ccx.ecx.env.block.prevrandao = Some((*newPrevrandao).into()); + ccx.ecx.block.prevrandao = Some((*newPrevrandao).into()); Ok(Default::default()) } } @@ -427,11 +431,11 @@ impl Cheatcode for blobhashesCall { fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { hashes } = self; ensure!( - ccx.ecx.spec_id() >= SpecId::CANCUN, + ccx.ecx.cfg.spec >= SpecId::CANCUN, "`blobhashes` is not supported before the Cancun hard fork; \ see EIP-4844: https://eips.ethereum.org/EIPS/eip-4844" ); - ccx.ecx.env.tx.blob_hashes.clone_from(hashes); + ccx.ecx.tx.blob_hashes.clone_from(hashes); Ok(Default::default()) } } @@ -440,18 +444,19 @@ impl Cheatcode for getBlobhashesCall { fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self {} = self; ensure!( - ccx.ecx.spec_id() >= SpecId::CANCUN, + ccx.ecx.cfg.spec >= SpecId::CANCUN, "`getBlobhashes` is not supported before the Cancun hard fork; \ see EIP-4844: https://eips.ethereum.org/EIPS/eip-4844" ); - Ok(ccx.ecx.env.tx.blob_hashes.clone().abi_encode()) + Ok(ccx.ecx.tx.blob_hashes.clone().abi_encode()) } } impl Cheatcode for rollCall { fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { newHeight } = self; - ccx.ecx.env.block.number = *newHeight; + ensure!(*newHeight <= U256::from(u64::MAX), "block height must be less than 2^64 - 1"); + ccx.ecx.block.number = newHeight.saturating_to(); Ok(Default::default()) } } @@ -459,14 +464,15 @@ impl Cheatcode for rollCall { impl Cheatcode for getBlockNumberCall { fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self {} = self; - Ok(ccx.ecx.env.block.number.abi_encode()) + Ok(ccx.ecx.block.number.abi_encode()) } } impl Cheatcode for txGasPriceCall { fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { newGasPrice } = self; - ccx.ecx.env.tx.gas_price = *newGasPrice; + ensure!(*newGasPrice <= U256::from(u64::MAX), "gas price must be less than 2^64 - 1"); + ccx.ecx.tx.gas_price = newGasPrice.saturating_to(); Ok(Default::default()) } } @@ -474,7 +480,8 @@ impl Cheatcode for txGasPriceCall { impl Cheatcode for warpCall { fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { newTimestamp } = self; - ccx.ecx.env.block.timestamp = *newTimestamp; + ensure!(*newTimestamp <= U256::from(u64::MAX), "timestamp must be less than 2^64 - 1"); + ccx.ecx.block.timestamp = newTimestamp.saturating_to(); Ok(Default::default()) } } @@ -482,7 +489,7 @@ impl Cheatcode for warpCall { impl Cheatcode for getBlockTimestampCall { fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self {} = self; - Ok(ccx.ecx.env.block.timestamp.abi_encode()) + Ok(ccx.ecx.block.timestamp.abi_encode()) } } @@ -490,14 +497,12 @@ impl Cheatcode for blobBaseFeeCall { fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { newBlobBaseFee } = self; ensure!( - ccx.ecx.spec_id() >= SpecId::CANCUN, + ccx.ecx.cfg.spec >= SpecId::CANCUN, "`blobBaseFee` is not supported before the Cancun hard fork; \ see EIP-4844: https://eips.ethereum.org/EIPS/eip-4844" ); - ccx.ecx.env.block.set_blob_excess_gas_and_price( - (*newBlobBaseFee).to(), - ccx.ecx.spec_id() >= SpecId::PRAGUE, - ); + let is_prague = ccx.ecx.cfg.spec >= SpecId::PRAGUE; + ccx.ecx.block.set_blob_excess_gas_and_price((*newBlobBaseFee).to(), is_prague); Ok(Default::default()) } } @@ -505,7 +510,7 @@ impl Cheatcode for blobBaseFeeCall { impl Cheatcode for getBlobBaseFeeCall { fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self {} = self; - Ok(ccx.ecx.env.block.get_blob_excess_gas().unwrap_or(0).abi_encode()) + Ok(ccx.ecx.block.blob_excess_gas().unwrap_or(0).abi_encode()) } } @@ -524,7 +529,7 @@ impl Cheatcode for etchCall { fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { target, newRuntimeBytecode } = self; ensure_not_precompile!(target, ccx); - ccx.ecx.load_account(*target)?; + ccx.ecx.journaled_state.load_account(*target)?; let bytecode = Bytecode::new_raw_checked(Bytes::copy_from_slice(newRuntimeBytecode)) .map_err(|e| fmt_err!("failed to create bytecode: {e}"))?; ccx.ecx.journaled_state.set_code(*target, bytecode); @@ -578,7 +583,7 @@ impl Cheatcode for storeCall { ensure_not_precompile!(&target, ccx); // ensure the account is touched let _ = journaled_account(ccx.ecx, target)?; - ccx.ecx.sstore(target, slot.into(), value.into())?; + ccx.ecx.journaled_state.sstore(target, slot.into(), value.into())?; Ok(Default::default()) } } @@ -588,7 +593,7 @@ impl Cheatcode for coolCall { let Self { target } = self; if let Some(account) = ccx.ecx.journaled_state.state.get_mut(target) { account.unmark_touch(); - account.storage.clear(); + account.storage.values_mut().for_each(|slot| slot.mark_cold()); } Ok(Default::default()) } @@ -639,7 +644,7 @@ impl Cheatcode for coolSlotCall { impl Cheatcode for readCallersCall { fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self {} = self; - read_callers(ccx.state, &ccx.ecx.env.tx.caller, ccx.ecx.journaled_state.depth()) + read_callers(ccx.state, &ccx.ecx.tx.caller, ccx.ecx.journaled_state.depth()) } } @@ -831,16 +836,17 @@ impl Cheatcode for broadcastRawTransactionCall { let tx = TxEnvelope::decode(&mut self.data.as_ref()) .map_err(|err| fmt_err!("failed to decode RLP-encoded transaction: {err}"))?; - ccx.ecx.db.transact_from_tx( + let (db, journal, env) = ccx.ecx.as_db_env_and_journal(); + db.transact_from_tx( &tx.clone().into(), - (*ccx.ecx.env).clone(), - &mut ccx.ecx.journaled_state, + env.to_owned(), + journal, &mut *executor.get_inspector(ccx.state), )?; if ccx.state.broadcast.is_some() { ccx.state.broadcastable_transactions.push_back(BroadcastableTransaction { - rpc: ccx.db.active_fork_url(), + rpc: ccx.ecx.journaled_state.database.active_fork_url(), transaction: tx.try_into()?, }); } @@ -852,12 +858,13 @@ impl Cheatcode for broadcastRawTransactionCall { impl Cheatcode for setBlockhashCall { fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { blockNumber, blockHash } = *self; + ensure!(blockNumber <= U256::from(u64::MAX), "blockNumber must be less than 2^64 - 1"); ensure!( - blockNumber <= ccx.ecx.env.block.number, + blockNumber <= U256::from(ccx.ecx.block.number), "block number must be less than or equal to the current block number" ); - ccx.ecx.db.set_blockhash(blockNumber, blockHash); + ccx.ecx.journaled_state.database.set_blockhash(blockNumber, blockHash); Ok(Default::default()) } @@ -930,23 +937,22 @@ impl Cheatcode for stopAndReturnDebugTraceRecordingCall { } pub(super) fn get_nonce(ccx: &mut CheatsCtxt, address: &Address) -> Result { - let account = ccx.ecx.journaled_state.load_account(*address, &mut ccx.ecx.db)?; + let account = ccx.ecx.journaled_state.load_account(*address)?; Ok(account.info.nonce.abi_encode()) } fn inner_snapshot_state(ccx: &mut CheatsCtxt) -> Result { - Ok(ccx.ecx.db.snapshot_state(&ccx.ecx.journaled_state, &ccx.ecx.env).abi_encode()) + let (db, journal, mut env) = ccx.ecx.as_db_env_and_journal(); + Ok(db.snapshot_state(journal, &mut env).abi_encode()) } fn inner_revert_to_state(ccx: &mut CheatsCtxt, snapshot_id: U256) -> Result { - let result = if let Some(journaled_state) = ccx.ecx.db.revert_state( - snapshot_id, - &ccx.ecx.journaled_state, - &mut ccx.ecx.env, - RevertStateSnapshotAction::RevertKeep, - ) { + let (db, journal, mut env) = ccx.ecx.as_db_env_and_journal(); + let result = if let Some(journaled_state) = + db.revert_state(snapshot_id, &*journal, &mut env, RevertStateSnapshotAction::RevertKeep) + { // we reset the evm's journaled_state to the state of the snapshot previous state - ccx.ecx.journaled_state = journaled_state; + ccx.ecx.journaled_state.inner = journaled_state; true } else { false @@ -955,14 +961,13 @@ fn inner_revert_to_state(ccx: &mut CheatsCtxt, snapshot_id: U256) -> Result { } fn inner_revert_to_state_and_delete(ccx: &mut CheatsCtxt, snapshot_id: U256) -> Result { - let result = if let Some(journaled_state) = ccx.ecx.db.revert_state( - snapshot_id, - &ccx.ecx.journaled_state, - &mut ccx.ecx.env, - RevertStateSnapshotAction::RevertRemove, - ) { + let (db, journal, mut env) = ccx.ecx.as_db_env_and_journal(); + + let result = if let Some(journaled_state) = + db.revert_state(snapshot_id, &*journal, &mut env, RevertStateSnapshotAction::RevertRemove) + { // we reset the evm's journaled_state to the state of the snapshot previous state - ccx.ecx.journaled_state = journaled_state; + ccx.ecx.journaled_state.inner = journaled_state; true } else { false @@ -971,12 +976,12 @@ fn inner_revert_to_state_and_delete(ccx: &mut CheatsCtxt, snapshot_id: U256) -> } fn inner_delete_state_snapshot(ccx: &mut CheatsCtxt, snapshot_id: U256) -> Result { - let result = ccx.ecx.db.delete_state_snapshot(snapshot_id); + let result = ccx.ecx.journaled_state.database.delete_state_snapshot(snapshot_id); Ok(result.abi_encode()) } fn inner_delete_state_snapshots(ccx: &mut CheatsCtxt) -> Result { - ccx.ecx.db.delete_state_snapshots(); + ccx.ecx.journaled_state.database.delete_state_snapshots(); Ok(Default::default()) } @@ -1119,7 +1124,7 @@ fn derive_snapshot_name( /// - If no caller modification is active: /// - caller_mode will be equal to [CallerMode::None], /// - `msg.sender` and `tx.origin` will be equal to the default sender address. -fn read_callers(state: &Cheatcodes, default_sender: &Address, call_depth: u64) -> Result { +fn read_callers(state: &Cheatcodes, default_sender: &Address, call_depth: usize) -> Result { let mut mode = CallerMode::None; let mut new_caller = default_sender; let mut new_origin = default_sender; @@ -1144,11 +1149,11 @@ fn read_callers(state: &Cheatcodes, default_sender: &Address, call_depth: u64) - /// Ensures the `Account` is loaded and touched. pub(super) fn journaled_account<'a>( - ecx: InnerEcx<'a, '_, '_>, + ecx: Ecx<'a, '_, '_>, addr: Address, ) -> Result<&'a mut Account> { - ecx.load_account(addr)?; - ecx.journaled_state.touch(&addr); + ecx.journaled_state.load_account(addr)?; + ecx.journaled_state.touch(addr); Ok(ecx.journaled_state.state.get_mut(&addr).expect("account is loaded")) } diff --git a/crates/cheatcodes/src/evm/fork.rs b/crates/cheatcodes/src/evm/fork.rs index e78aa3be9f697..65a7a04d401cc 100644 --- a/crates/cheatcodes/src/evm/fork.rs +++ b/crates/cheatcodes/src/evm/fork.rs @@ -8,13 +8,14 @@ use alloy_provider::Provider; use alloy_rpc_types::Filter; use alloy_sol_types::SolValue; use foundry_common::provider::ProviderBuilder; -use foundry_evm_core::fork::CreateFork; +use foundry_evm_core::{fork::CreateFork, AsEnvMut, ContextExt}; impl Cheatcode for activeForkCall { fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self {} = self; ccx.ecx - .db + .journaled_state + .database .active_fork_id() .map(|id| id.abi_encode()) .ok_or_else(|| fmt_err!("no active fork")) @@ -67,12 +68,8 @@ impl Cheatcode for rollFork_0Call { fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { blockNumber } = self; persist_caller(ccx); - ccx.ecx.db.roll_fork( - None, - (*blockNumber).to(), - &mut ccx.ecx.env, - &mut ccx.ecx.journaled_state, - )?; + let (db, journal, mut env) = ccx.ecx.as_db_env_and_journal(); + db.roll_fork(None, (*blockNumber).to(), &mut env, journal)?; Ok(Default::default()) } } @@ -81,12 +78,8 @@ impl Cheatcode for rollFork_1Call { fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { txHash } = self; persist_caller(ccx); - ccx.ecx.db.roll_fork_to_transaction( - None, - *txHash, - &mut ccx.ecx.env, - &mut ccx.ecx.journaled_state, - )?; + let (db, journal, mut env) = ccx.ecx.as_db_env_and_journal(); + db.roll_fork_to_transaction(None, *txHash, &mut env, journal)?; Ok(Default::default()) } } @@ -95,12 +88,8 @@ impl Cheatcode for rollFork_2Call { fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { forkId, blockNumber } = self; persist_caller(ccx); - ccx.ecx.db.roll_fork( - Some(*forkId), - (*blockNumber).to(), - &mut ccx.ecx.env, - &mut ccx.ecx.journaled_state, - )?; + let (db, journal, mut env) = ccx.ecx.as_db_env_and_journal(); + db.roll_fork(Some(*forkId), (*blockNumber).to(), &mut env, journal)?; Ok(Default::default()) } } @@ -109,12 +98,8 @@ impl Cheatcode for rollFork_3Call { fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { forkId, txHash } = self; persist_caller(ccx); - ccx.ecx.db.roll_fork_to_transaction( - Some(*forkId), - *txHash, - &mut ccx.ecx.env, - &mut ccx.ecx.journaled_state, - )?; + let (db, journal, mut env) = ccx.ecx.as_db_env_and_journal(); + db.roll_fork_to_transaction(Some(*forkId), *txHash, &mut env, journal)?; Ok(Default::default()) } } @@ -124,8 +109,8 @@ impl Cheatcode for selectForkCall { let Self { forkId } = self; persist_caller(ccx); check_broadcast(ccx.state)?; - - ccx.ecx.db.select_fork(*forkId, &mut ccx.ecx.env, &mut ccx.ecx.journaled_state)?; + let (db, journal, mut env) = ccx.ecx.as_db_env_and_journal(); + db.select_fork(*forkId, &mut env, journal)?; Ok(Default::default()) } } @@ -147,7 +132,7 @@ impl Cheatcode for transact_1Call { impl Cheatcode for allowCheatcodesCall { fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { account } = self; - ccx.ecx.db.allow_cheatcode_access(*account); + ccx.ecx.journaled_state.database.allow_cheatcode_access(*account); Ok(Default::default()) } } @@ -155,7 +140,7 @@ impl Cheatcode for allowCheatcodesCall { impl Cheatcode for makePersistent_0Call { fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { account } = self; - ccx.ecx.db.add_persistent_account(*account); + ccx.ecx.journaled_state.database.add_persistent_account(*account); Ok(Default::default()) } } @@ -163,8 +148,8 @@ impl Cheatcode for makePersistent_0Call { impl Cheatcode for makePersistent_1Call { fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { account0, account1 } = self; - ccx.ecx.db.add_persistent_account(*account0); - ccx.ecx.db.add_persistent_account(*account1); + ccx.ecx.journaled_state.database.add_persistent_account(*account0); + ccx.ecx.journaled_state.database.add_persistent_account(*account1); Ok(Default::default()) } } @@ -172,9 +157,9 @@ impl Cheatcode for makePersistent_1Call { impl Cheatcode for makePersistent_2Call { fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { account0, account1, account2 } = self; - ccx.ecx.db.add_persistent_account(*account0); - ccx.ecx.db.add_persistent_account(*account1); - ccx.ecx.db.add_persistent_account(*account2); + ccx.ecx.journaled_state.database.add_persistent_account(*account0); + ccx.ecx.journaled_state.database.add_persistent_account(*account1); + ccx.ecx.journaled_state.database.add_persistent_account(*account2); Ok(Default::default()) } } @@ -183,7 +168,7 @@ impl Cheatcode for makePersistent_3Call { fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { accounts } = self; for account in accounts { - ccx.ecx.db.add_persistent_account(*account); + ccx.ecx.journaled_state.database.add_persistent_account(*account); } Ok(Default::default()) } @@ -192,7 +177,7 @@ impl Cheatcode for makePersistent_3Call { impl Cheatcode for revokePersistent_0Call { fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { account } = self; - ccx.ecx.db.remove_persistent_account(account); + ccx.ecx.journaled_state.database.remove_persistent_account(account); Ok(Default::default()) } } @@ -201,7 +186,7 @@ impl Cheatcode for revokePersistent_1Call { fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { accounts } = self; for account in accounts { - ccx.ecx.db.remove_persistent_account(account); + ccx.ecx.journaled_state.database.remove_persistent_account(account); } Ok(Default::default()) } @@ -210,15 +195,19 @@ impl Cheatcode for revokePersistent_1Call { impl Cheatcode for isPersistentCall { fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { account } = self; - Ok(ccx.ecx.db.is_persistent(account).abi_encode()) + Ok(ccx.ecx.journaled_state.database.is_persistent(account).abi_encode()) } } impl Cheatcode for rpc_0Call { fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { method, params } = self; - let url = - ccx.ecx.db.active_fork_url().ok_or_else(|| fmt_err!("no active fork URL found"))?; + let url = ccx + .ecx + .journaled_state + .database + .active_fork_url() + .ok_or_else(|| fmt_err!("no active fork URL found"))?; rpc_call(&url, method, params) } } @@ -243,8 +232,12 @@ impl Cheatcode for eth_getLogsCall { bail!("topics array must contain at most 4 elements") } - let url = - ccx.ecx.db.active_fork_url().ok_or_else(|| fmt_err!("no active fork URL found"))?; + let url = ccx + .ecx + .journaled_state + .database + .active_fork_url() + .ok_or_else(|| fmt_err!("no active fork URL found"))?; let provider = ProviderBuilder::new(&url).build()?; let mut filter = Filter::new().address(*target).from_block(from_block).to_block(to_block); for (i, &topic) in topics.iter().enumerate() { @@ -278,14 +271,15 @@ fn create_select_fork(ccx: &mut CheatsCtxt, url_or_alias: &str, block: Option) -> Result { let fork = create_fork_request(ccx, url_or_alias, block)?; - let id = ccx.ecx.db.create_fork(fork)?; + let id = ccx.ecx.journaled_state.database.create_fork(fork)?; Ok(id.abi_encode()) } @@ -298,12 +292,8 @@ fn create_select_fork_at_transaction( check_broadcast(ccx.state)?; let fork = create_fork_request(ccx, url_or_alias, None)?; - let id = ccx.ecx.db.create_select_fork_at_transaction( - fork, - &mut ccx.ecx.env, - &mut ccx.ecx.journaled_state, - *transaction, - )?; + let (db, journal, mut env) = ccx.ecx.as_db_env_and_journal(); + let id = db.create_select_fork_at_transaction(fork, &mut env, journal, *transaction)?; Ok(id.abi_encode()) } @@ -314,7 +304,7 @@ fn create_fork_at_transaction( transaction: &B256, ) -> Result { let fork = create_fork_request(ccx, url_or_alias, None)?; - let id = ccx.ecx.db.create_fork_at_transaction(fork, *transaction)?; + let id = ccx.ecx.journaled_state.database.create_fork_at_transaction(fork, *transaction)?; Ok(id.abi_encode()) } @@ -339,7 +329,7 @@ fn create_fork_request( enable_caching: !ccx.state.config.no_storage_caching && ccx.state.config.rpc_storage_caching.enable_for_endpoint(&url), url, - env: (*ccx.ecx.env).clone(), + env: ccx.ecx.as_env_mut().to_owned(), evm_opts, }; Ok(fork) @@ -359,11 +349,12 @@ fn transact( transaction: B256, fork_id: Option, ) -> Result { - ccx.ecx.db.transact( + let (db, journal, env) = ccx.ecx.as_db_env_and_journal(); + db.transact( fork_id, transaction, - (*ccx.ecx.env).clone(), - &mut ccx.ecx.journaled_state, + env.to_owned(), + journal, &mut *executor.get_inspector(ccx.state), )?; Ok(Default::default()) @@ -374,7 +365,7 @@ fn transact( // Applies to create, select and roll forks actions. // https://github.com/foundry-rs/foundry/issues/8004 fn persist_caller(ccx: &mut CheatsCtxt) { - ccx.ecx.db.add_persistent_account(ccx.caller); + ccx.ecx.journaled_state.database.add_persistent_account(ccx.caller); } /// Performs an Ethereum JSON-RPC request to the given endpoint. diff --git a/crates/cheatcodes/src/evm/mapping.rs b/crates/cheatcodes/src/evm/mapping.rs index e8525908e847a..29c12d6d3113c 100644 --- a/crates/cheatcodes/src/evm/mapping.rs +++ b/crates/cheatcodes/src/evm/mapping.rs @@ -5,7 +5,10 @@ use alloy_primitives::{ Address, B256, U256, }; use alloy_sol_types::SolValue; -use revm::interpreter::{opcode, Interpreter}; +use revm::{ + bytecode::opcode, + interpreter::{interpreter_types::Jumps, Interpreter}, +}; /// Recorded mapping slots. #[derive(Clone, Debug, Default)] @@ -117,22 +120,21 @@ fn slot_child<'a>( #[cold] pub(crate) fn step(mapping_slots: &mut AddressHashMap, interpreter: &Interpreter) { - match interpreter.current_opcode() { + match interpreter.bytecode.opcode() { opcode::KECCAK256 => { if interpreter.stack.peek(1) == Ok(U256::from(0x40)) { - let address = interpreter.contract.target_address; + let address = interpreter.input.target_address; let offset = interpreter.stack.peek(0).expect("stack size > 1").saturating_to(); - let data = interpreter.shared_memory.slice(offset, 0x40); + let data = interpreter.memory.slice_len(offset, 0x40); let low = B256::from_slice(&data[..0x20]); let high = B256::from_slice(&data[0x20..]); - let result = keccak256(data); + let result = keccak256(&*data); mapping_slots.entry(address).or_default().seen_sha3.insert(result, (low, high)); } } opcode::SSTORE => { - if let Some(mapping_slots) = mapping_slots.get_mut(&interpreter.contract.target_address) - { + if let Some(mapping_slots) = mapping_slots.get_mut(&interpreter.input.target_address) { if let Ok(slot) = interpreter.stack.peek(0) { mapping_slots.insert(slot.into()); } diff --git a/crates/cheatcodes/src/evm/mock.rs b/crates/cheatcodes/src/evm/mock.rs index fcfea7a9ce87c..dc2c2137ba524 100644 --- a/crates/cheatcodes/src/evm/mock.rs +++ b/crates/cheatcodes/src/evm/mock.rs @@ -1,6 +1,6 @@ -use crate::{inspector::InnerEcx, Cheatcode, Cheatcodes, CheatsCtxt, Result, Vm::*}; +use crate::{Cheatcode, Cheatcodes, CheatsCtxt, Result, Vm::*}; use alloy_primitives::{Address, Bytes, U256}; -use revm::{interpreter::InstructionResult, primitives::Bytecode}; +use revm::{bytecode::Bytecode, context::JournalTr, interpreter::InstructionResult}; use std::{cmp::Ordering, collections::VecDeque}; /// Mocked call data. @@ -49,7 +49,7 @@ impl Cheatcode for clearMockedCallsCall { impl Cheatcode for mockCall_0Call { fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { callee, data, returnData } = self; - let _ = make_acc_non_empty(callee, ccx.ecx)?; + let _ = make_acc_non_empty(callee, ccx)?; mock_call(ccx.state, callee, data, None, returnData, InstructionResult::Return); Ok(Default::default()) @@ -59,7 +59,7 @@ impl Cheatcode for mockCall_0Call { impl Cheatcode for mockCall_1Call { fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { callee, msgValue, data, returnData } = self; - ccx.ecx.load_account(*callee)?; + ccx.ecx.journaled_state.load_account(*callee)?; mock_call(ccx.state, callee, data, Some(msgValue), returnData, InstructionResult::Return); Ok(Default::default()) } @@ -68,7 +68,7 @@ impl Cheatcode for mockCall_1Call { impl Cheatcode for mockCall_2Call { fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { callee, data, returnData } = self; - let _ = make_acc_non_empty(callee, ccx.ecx)?; + let _ = make_acc_non_empty(callee, ccx)?; mock_call( ccx.state, @@ -85,7 +85,7 @@ impl Cheatcode for mockCall_2Call { impl Cheatcode for mockCall_3Call { fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { callee, msgValue, data, returnData } = self; - ccx.ecx.load_account(*callee)?; + ccx.ecx.journaled_state.load_account(*callee)?; mock_call( ccx.state, callee, @@ -101,7 +101,7 @@ impl Cheatcode for mockCall_3Call { impl Cheatcode for mockCalls_0Call { fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { callee, data, returnData } = self; - let _ = make_acc_non_empty(callee, ccx.ecx)?; + let _ = make_acc_non_empty(callee, ccx)?; mock_calls(ccx.state, callee, data, None, returnData, InstructionResult::Return); Ok(Default::default()) @@ -111,7 +111,7 @@ impl Cheatcode for mockCalls_0Call { impl Cheatcode for mockCalls_1Call { fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { callee, msgValue, data, returnData } = self; - ccx.ecx.load_account(*callee)?; + ccx.ecx.journaled_state.load_account(*callee)?; mock_calls(ccx.state, callee, data, Some(msgValue), returnData, InstructionResult::Return); Ok(Default::default()) } @@ -120,7 +120,7 @@ impl Cheatcode for mockCalls_1Call { impl Cheatcode for mockCallRevert_0Call { fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { callee, data, revertData } = self; - let _ = make_acc_non_empty(callee, ccx.ecx)?; + let _ = make_acc_non_empty(callee, ccx)?; mock_call(ccx.state, callee, data, None, revertData, InstructionResult::Revert); Ok(Default::default()) @@ -130,7 +130,7 @@ impl Cheatcode for mockCallRevert_0Call { impl Cheatcode for mockCallRevert_1Call { fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { callee, msgValue, data, revertData } = self; - let _ = make_acc_non_empty(callee, ccx.ecx)?; + let _ = make_acc_non_empty(callee, ccx)?; mock_call(ccx.state, callee, data, Some(msgValue), revertData, InstructionResult::Revert); Ok(Default::default()) @@ -140,7 +140,7 @@ impl Cheatcode for mockCallRevert_1Call { impl Cheatcode for mockCallRevert_2Call { fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { callee, data, revertData } = self; - let _ = make_acc_non_empty(callee, ccx.ecx)?; + let _ = make_acc_non_empty(callee, ccx)?; mock_call( ccx.state, @@ -157,7 +157,7 @@ impl Cheatcode for mockCallRevert_2Call { impl Cheatcode for mockCallRevert_3Call { fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { callee, msgValue, data, revertData } = self; - let _ = make_acc_non_empty(callee, ccx.ecx)?; + let _ = make_acc_non_empty(callee, ccx)?; mock_call( ccx.state, @@ -210,8 +210,8 @@ fn mock_calls( // Etches a single byte onto the account if it is empty to circumvent the `extcodesize` // check Solidity might perform. -fn make_acc_non_empty(callee: &Address, ecx: InnerEcx) -> Result { - let acc = ecx.load_account(*callee)?; +fn make_acc_non_empty(callee: &Address, ecx: &mut CheatsCtxt) -> Result { + let acc = ecx.journaled_state.load_account(*callee)?; let empty_bytecode = acc.info.code.as_ref().is_none_or(Bytecode::is_empty); if empty_bytecode { diff --git a/crates/cheatcodes/src/evm/prank.rs b/crates/cheatcodes/src/evm/prank.rs index 195760e702f46..96ab6cb9df925 100644 --- a/crates/cheatcodes/src/evm/prank.rs +++ b/crates/cheatcodes/src/evm/prank.rs @@ -1,5 +1,6 @@ use crate::{Cheatcode, CheatsCtxt, Result, Vm::*}; use alloy_primitives::Address; +use revm::{context::JournalTr, interpreter::Host}; /// Prank information. #[derive(Clone, Copy, Debug, Default)] @@ -13,7 +14,7 @@ pub struct Prank { /// The address to assign to `tx.origin` pub new_origin: Option
, /// The depth at which the prank was called - pub depth: u64, + pub depth: usize, /// Whether the prank stops by itself after the next call pub single_call: bool, /// Whether the prank should be applied to delegate call @@ -29,7 +30,7 @@ impl Prank { prank_origin: Address, new_caller: Address, new_origin: Option
, - depth: u64, + depth: usize, single_call: bool, delegate_call: bool, ) -> Self { @@ -129,8 +130,11 @@ fn prank( ) -> Result { // Ensure that code exists at `msg.sender` if delegate calling. if delegate_call { - let code = ccx.code(*new_caller)?; - ensure!(!code.is_empty(), "cannot `prank` delegate call from an EOA"); + let code = ccx + .load_account_code(*new_caller) + .ok_or_else(|| eyre::eyre!("cannot `prank` delegate call from an EOA"))?; + + ensure!(!code.data.is_empty(), "cannot `prank` delegate call from an EOA"); } let depth = ccx.ecx.journaled_state.depth(); @@ -147,7 +151,7 @@ fn prank( let prank = Prank::new( ccx.caller, - ccx.ecx.env.tx.caller, + ccx.ecx.tx.caller, *new_caller, new_origin.copied(), depth, diff --git a/crates/cheatcodes/src/evm/record_debug_step.rs b/crates/cheatcodes/src/evm/record_debug_step.rs index 1b1756936652e..bc78c2425c414 100644 --- a/crates/cheatcodes/src/evm/record_debug_step.rs +++ b/crates/cheatcodes/src/evm/record_debug_step.rs @@ -1,7 +1,7 @@ use alloy_primitives::{Bytes, U256}; use foundry_evm_traces::CallTraceArena; -use revm::interpreter::{InstructionResult, OpCode}; +use revm::{bytecode::opcode::OpCode, interpreter::InstructionResult}; use foundry_evm_core::buffer::{get_buffer_accesses, BufferKind}; use revm_inspectors::tracing::types::{CallTraceStep, RecordedMemory, TraceMemberOrder}; diff --git a/crates/cheatcodes/src/fs.rs b/crates/cheatcodes/src/fs.rs index 940d5f6d8ef3c..f1985eb32bbc1 100644 --- a/crates/cheatcodes/src/fs.rs +++ b/crates/cheatcodes/src/fs.rs @@ -12,7 +12,7 @@ use dialoguer::{Input, Password}; use forge_script_sequence::{BroadcastReader, TransactionWithMetadata}; use foundry_common::fs; use foundry_config::fs_permissions::FsAccessKind; -use revm::interpreter::CreateInputs; +use revm::{context::CreateScheme, interpreter::CreateInputs}; use revm_inspectors::tracing::types::CallKind; use semver::Version; use std::{ @@ -97,7 +97,7 @@ impl Cheatcode for closeFileCall { let Self { path } = self; let path = state.config.ensure_path_allowed(path, FsAccessKind::Read)?; - state.context.opened_read_files.remove(&path); + state.test_context.opened_read_files.remove(&path); Ok(Default::default()) } @@ -167,7 +167,7 @@ impl Cheatcode for readLineCall { let path = state.config.ensure_path_allowed(path, FsAccessKind::Read)?; // Get reader for previously opened file to continue reading OR initialize new reader - let reader = match state.context.opened_read_files.entry(path.clone()) { + let reader = match state.test_context.opened_read_files.entry(path.clone()) { Entry::Occupied(entry) => entry.into_mut(), Entry::Vacant(entry) => entry.insert(BufReader::new(fs::open(path)?)), }; @@ -212,7 +212,7 @@ impl Cheatcode for removeFileCall { state.config.ensure_not_foundry_toml(&path)?; // also remove from the set if opened previously - state.context.opened_read_files.remove(&path); + state.test_context.opened_read_files.remove(&path); if state.fs_commit { fs::remove_file(&path)?; @@ -365,11 +365,8 @@ fn deploy_code( bytecode.extend_from_slice(args); } - let scheme = if let Some(salt) = salt { - revm::primitives::CreateScheme::Create2 { salt } - } else { - revm::primitives::CreateScheme::Create - }; + let scheme = + if let Some(salt) = salt { CreateScheme::Create2 { salt } } else { CreateScheme::Create }; let outcome = executor.exec_create( CreateInputs { @@ -753,7 +750,7 @@ impl Cheatcode for getBroadcasts_1Call { impl Cheatcode for getDeployment_0Call { fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { contractName } = self; - let chain_id = ccx.ecx.env.cfg.chain_id; + let chain_id = ccx.ecx.cfg.chain_id; let latest_broadcast = latest_broadcast( contractName, diff --git a/crates/cheatcodes/src/inspector.rs b/crates/cheatcodes/src/inspector.rs index 517b1ffbe72d7..e2eb84ec522bf 100644 --- a/crates/cheatcodes/src/inspector.rs +++ b/crates/cheatcodes/src/inspector.rs @@ -22,6 +22,7 @@ use crate::{ Vm::{self, AccountAccess}, }; use alloy_consensus::BlobTransactionSidecar; +use alloy_evm::eth::EthEvmContext; use alloy_network::TransactionBuilder4844; use alloy_primitives::{ hex, @@ -38,7 +39,7 @@ use foundry_evm_core::{ abi::Vm::stopExpectSafeMemoryCall, backend::{DatabaseError, DatabaseExt, RevertDiagnostic}, constants::{CHEATCODE_ADDRESS, HARDHAT_CONSOLE_ADDRESS, MAGIC_ASSUME}, - utils::new_evm_with_existing_context, + evm::{new_evm_with_existing_context, FoundryEvm}, InspectorExt, }; use foundry_evm_traces::{TracingInspector, TracingInspectorConfig}; @@ -47,16 +48,17 @@ use itertools::Itertools; use proptest::test_runner::{RngAlgorithm, TestRng, TestRunner}; use rand::Rng; use revm::{ + bytecode::opcode as op, + context::{result::EVMError, BlockEnv, JournalTr, LocalContext, TransactionType}, + context_interface::{transaction::SignedAuthorization, CreateScheme}, + handler::FrameResult, interpreter::{ - opcode as op, CallInputs, CallOutcome, CallScheme, CreateInputs, CreateOutcome, - EOFCreateInputs, EOFCreateKind, Gas, InstructionResult, Interpreter, InterpreterAction, - InterpreterResult, + interpreter_types::{Jumps, MemoryTr}, + CallInputs, CallOutcome, CallScheme, CreateInputs, CreateOutcome, FrameInput, Gas, Host, + InstructionResult, Interpreter, InterpreterAction, InterpreterResult, }, - primitives::{ - BlockEnv, CreateScheme, EVMError, EvmStorageSlot, SignedAuthorization, SpecId, - EOF_MAGIC_BYTES, - }, - EvmContext, InnerEvmContext, Inspector, + state::EvmStorageSlot, + Inspector, Journal, }; use serde_json::Value; use std::{ @@ -71,8 +73,7 @@ use std::{ mod utils; -pub type Ecx<'a, 'b, 'c> = &'a mut EvmContext<&'b mut (dyn DatabaseExt + 'c)>; -pub type InnerEcx<'a, 'b, 'c> = &'a mut InnerEvmContext<&'b mut (dyn DatabaseExt + 'c)>; +pub type Ecx<'a, 'b, 'c> = &'a mut EthEvmContext<&'b mut (dyn DatabaseExt + 'c)>; /// Helper trait for obtaining complete [revm::Inspector] instance from mutable reference to /// [Cheatcodes]. @@ -84,46 +85,23 @@ pub trait CheatcodesExecutor { /// [revm::Inspector]. fn get_inspector<'a>(&'a mut self, cheats: &'a mut Cheatcodes) -> Box; - /// Obtains [revm::Evm] instance and executes the given CREATE frame. + /// Obtains [FoundryEvm] instance and executes the given CREATE frame. fn exec_create( &mut self, inputs: CreateInputs, ccx: &mut CheatsCtxt, ) -> Result> { with_evm(self, ccx, |evm| { - evm.context.evm.inner.journaled_state.depth += 1; - - // Handle EOF bytecode - let first_frame_or_result = if evm.handler.cfg.spec_id.is_enabled_in(SpecId::OSAKA) && - inputs.scheme == CreateScheme::Create && - inputs.init_code.starts_with(&EOF_MAGIC_BYTES) - { - evm.handler.execution().eofcreate( - &mut evm.context, - Box::new(EOFCreateInputs::new( - inputs.caller, - inputs.value, - inputs.gas_limit, - EOFCreateKind::Tx { initdata: inputs.init_code }, - )), - )? - } else { - evm.handler.execution().create(&mut evm.context, Box::new(inputs))? - }; - - let mut result = match first_frame_or_result { - revm::FrameOrResult::Frame(first_frame) => evm.run_the_loop(first_frame)?, - revm::FrameOrResult::Result(result) => result, - }; + evm.inner.ctx.journaled_state.depth += 1; - evm.handler.execution().last_frame_return(&mut evm.context, &mut result)?; + let frame = FrameInput::Create(Box::new(inputs)); - let outcome = match result { - revm::FrameResult::Call(_) => unreachable!(), - revm::FrameResult::Create(create) | revm::FrameResult::EOFCreate(create) => create, + let outcome = match evm.run_execution(frame)? { + FrameResult::Call(_) | FrameResult::EOFCreate(_) => unreachable!(), + FrameResult::Create(create) => create, }; - evm.context.evm.inner.journaled_state.depth -= 1; + evm.inner.ctx.journaled_state.depth -= 1; Ok(outcome) }) @@ -139,7 +117,7 @@ pub trait CheatcodesExecutor { } } -/// Constructs [revm::Evm] and runs a given closure with it. +/// Constructs [FoundryEvm] and runs a given closure with it. fn with_evm( executor: &mut E, ccx: &mut CheatsCtxt, @@ -148,32 +126,34 @@ fn with_evm( where E: CheatcodesExecutor + ?Sized, F: for<'a, 'b> FnOnce( - &mut revm::Evm<'_, &'b mut dyn InspectorExt, &'a mut dyn DatabaseExt>, + &mut FoundryEvm<'a, &'b mut dyn InspectorExt>, ) -> Result>, { let mut inspector = executor.get_inspector(ccx.state); let error = std::mem::replace(&mut ccx.ecx.error, Ok(())); - let l1_block_info = std::mem::take(&mut ccx.ecx.l1_block_info); - - let inner = revm::InnerEvmContext { - env: ccx.ecx.env.clone(), - journaled_state: std::mem::replace( - &mut ccx.ecx.journaled_state, - revm::JournaledState::new(Default::default(), Default::default()), - ), - db: &mut ccx.ecx.db as &mut dyn DatabaseExt, + + let ctx = EthEvmContext { + block: ccx.ecx.block.clone(), + cfg: ccx.ecx.cfg.clone(), + tx: ccx.ecx.tx.clone(), + journaled_state: Journal { + inner: ccx.ecx.journaled_state.inner.clone(), + database: &mut *ccx.ecx.journaled_state.database as &mut dyn DatabaseExt, + }, + local: LocalContext::default(), + chain: (), error, - l1_block_info, }; - let mut evm = new_evm_with_existing_context(inner, &mut *inspector); + let mut evm = new_evm_with_existing_context(ctx, &mut *inspector); let res = f(&mut evm)?; - ccx.ecx.journaled_state = evm.context.evm.inner.journaled_state; - ccx.ecx.env = evm.context.evm.inner.env; - ccx.ecx.l1_block_info = evm.context.evm.inner.l1_block_info; - ccx.ecx.error = evm.context.evm.inner.error; + ccx.ecx.journaled_state.inner = evm.inner.ctx.journaled_state.inner; + ccx.ecx.block = evm.inner.ctx.block; + ccx.ecx.tx = evm.inner.ctx.tx; + ccx.ecx.cfg = evm.inner.ctx.cfg; + ccx.ecx.error = evm.inner.ctx.error; Ok(res) } @@ -200,19 +180,19 @@ macro_rules! try_or_return { /// Contains additional, test specific resources that should be kept for the duration of the test #[derive(Debug, Default)] -pub struct Context { +pub struct TestContext { /// Buffered readers for files opened for reading (path => BufReader mapping) pub opened_read_files: HashMap>, } /// Every time we clone `Context`, we want it to be empty -impl Clone for Context { +impl Clone for TestContext { fn clone(&self) -> Self { Default::default() } } -impl Context { +impl TestContext { /// Clears the context. #[inline] pub fn clear(&mut self) { @@ -328,9 +308,9 @@ impl ArbitraryStorage { /// Saves arbitrary storage value for a given address: /// - store value in changed values cache. /// - update account's storage with given value. - pub fn save(&mut self, ecx: InnerEcx, address: Address, slot: U256, data: U256) { + pub fn save(&mut self, ecx: Ecx, address: Address, slot: U256, data: U256) { self.values.get_mut(&address).expect("missing arbitrary address entry").insert(slot, data); - if let Ok(mut account) = ecx.load_account(address) { + if let Ok(mut account) = ecx.journaled_state.load_account(address) { account.storage.insert(slot, EvmStorageSlot::new(data)); } } @@ -340,7 +320,7 @@ impl ArbitraryStorage { /// existing value. /// - if no value was yet generated for given slot, then save new value in cache and update both /// source and target storages. - pub fn copy(&mut self, ecx: InnerEcx, target: Address, slot: U256, new_value: U256) -> U256 { + pub fn copy(&mut self, ecx: Ecx, target: Address, slot: U256, new_value: U256) -> U256 { let source = self.copies.get(&target).expect("missing arbitrary copy target entry"); let storage_cache = self.values.get_mut(source).expect("missing arbitrary source storage"); let value = match storage_cache.get(&slot) { @@ -348,14 +328,14 @@ impl ArbitraryStorage { None => { storage_cache.insert(slot, new_value); // Update source storage with new value. - if let Ok(mut source_account) = ecx.load_account(*source) { + if let Ok(mut source_account) = ecx.journaled_state.load_account(*source) { source_account.storage.insert(slot, EvmStorageSlot::new(new_value)); } new_value } }; // Update target storage with new value. - if let Ok(mut target_account) = ecx.load_account(target) { + if let Ok(mut target_account) = ecx.journaled_state.load_account(target) { target_account.storage.insert(slot, EvmStorageSlot::new(value)); } value @@ -390,10 +370,10 @@ pub struct Cheatcodes { /// execution block environment. pub block: Option, - /// Currently active EIP-7702 delegation that will be consumed when building the next + /// Currently active EIP-7702 delegations that will be consumed when building the next /// transaction. Set by `vm.attachDelegation()` and consumed via `.take()` during /// transaction construction. - pub active_delegation: Option, + pub active_delegations: Vec, /// The active EIP-4844 blob that will be attached to the next call. pub active_blob_sidecar: Option, @@ -402,13 +382,13 @@ pub struct Cheatcodes { /// /// Used in the cheatcode handler to overwrite the gas price separately from the gas price /// in the execution environment. - pub gas_price: Option, + pub gas_price: Option, /// Address labels pub labels: AddressHashMap, /// Prank information, mapped to the call depth where pranks were added. - pub pranks: BTreeMap, + pub pranks: BTreeMap, /// Expected revert information pub expected_revert: Option, @@ -468,7 +448,7 @@ pub struct Cheatcodes { pub config: Arc, /// Test-scoped context holding data that needs to be reset every test run - pub context: Context, + pub test_context: TestContext, /// Whether to commit FS changes such as file creations, writes and deletes. /// Used to prevent duplicate changes file executing non-committing calls. @@ -533,7 +513,7 @@ impl Cheatcodes { labels: config.labels.clone(), config, block: Default::default(), - active_delegation: Default::default(), + active_delegations: Default::default(), active_blob_sidecar: Default::default(), gas_price: Default::default(), pranks: Default::default(), @@ -554,7 +534,7 @@ impl Cheatcodes { broadcast: Default::default(), broadcastable_transactions: Default::default(), access_list: Default::default(), - context: Default::default(), + test_context: Default::default(), serialized_jsons: Default::default(), eth_deals: Default::default(), gas_metering: Default::default(), @@ -574,7 +554,7 @@ impl Cheatcodes { /// Returns the configured prank at given depth or the first prank configured at a lower depth. /// For example, if pranks configured for depth 1, 3 and 5, the prank for depth 4 is the one /// configured at depth 3. - pub fn get_prank(&self, depth: u64) -> Option<&Prank> { + pub fn get_prank(&self, depth: usize) -> Option<&Prank> { self.pranks.range(..=depth).last().map(|(_, prank)| prank) } @@ -588,6 +568,11 @@ impl Cheatcodes { self.wallets = Some(wallets); } + /// Adds a delegation to the active delegations list. + pub fn add_delegation(&mut self, authorization: SignedAuthorization) { + self.active_delegations.push(authorization); + } + /// Decodes the input data and applies the cheatcode. fn apply_cheatcode( &mut self, @@ -596,7 +581,7 @@ impl Cheatcodes { executor: &mut dyn CheatcodesExecutor, ) -> Result { // decode the cheatcode call - let decoded = Vm::VmCalls::abi_decode(&call.input, false).map_err(|e| { + let decoded = Vm::VmCalls::abi_decode(&call.input.bytes(ecx)).map_err(|e| { if let alloy_sol_types::Error::UnknownSelector { name: _, selector } = e { let msg = format!( "unknown cheatcode with selector {selector}; \ @@ -612,17 +597,11 @@ impl Cheatcodes { // ensure the caller is allowed to execute cheatcodes, // but only if the backend is in forking mode - ecx.db.ensure_cheatcode_access_forking_mode(&caller)?; + ecx.journaled_state.database.ensure_cheatcode_access_forking_mode(&caller)?; apply_dispatch( &decoded, - &mut CheatsCtxt { - state: self, - ecx: &mut ecx.inner, - precompiles: &mut ecx.precompiles, - gas_limit: call.gas_limit, - caller, - }, + &mut CheatsCtxt { state: self, ecx, gas_limit: call.gas_limit, caller }, executor, ) } @@ -632,9 +611,26 @@ impl Cheatcodes { /// /// There may be cheatcodes in the constructor of the new contract, in order to allow them /// automatically we need to determine the new address. - fn allow_cheatcodes_on_create(&self, ecx: InnerEcx, caller: Address, created_address: Address) { - if ecx.journaled_state.depth <= 1 || ecx.db.has_cheatcode_access(&caller) { - ecx.db.allow_cheatcode_access(created_address); + fn allow_cheatcodes_on_create(&self, ecx: Ecx, caller: Address, created_address: Address) { + if ecx.journaled_state.depth <= 1 || + ecx.journaled_state.database.has_cheatcode_access(&caller) + { + ecx.journaled_state.database.allow_cheatcode_access(created_address); + } + } + + /// Apply EIP-2930 access list. + /// + /// If the transaction type is [TransactionType::Legacy] we need to upgrade it to + /// [TransactionType::Eip2930] in order to use access lists. Other transaction types support + /// access lists themselves. + fn apply_accesslist(&mut self, ecx: Ecx) { + if let Some(access_list) = &self.access_list { + ecx.tx.access_list = access_list.clone(); + + if ecx.tx.tx_type == TransactionType::Legacy as u8 { + ecx.tx.tx_type = TransactionType::Eip2930 as u8; + } } } @@ -665,272 +661,6 @@ impl Cheatcodes { } } - // common create functionality for both legacy and EOF. - fn create_common(&mut self, ecx: Ecx, mut input: Input) -> Option - where - Input: CommonCreateInput, - { - // Check if we should intercept this create - if self.intercept_next_create_call { - // Reset the flag - self.intercept_next_create_call = false; - - // Get initcode from the input - let output = input.init_code(); - - // Return a revert with the initcode as error data - return Some(CreateOutcome { - result: InterpreterResult { - result: InstructionResult::Revert, - output, - gas: Gas::new(input.gas_limit()), - }, - address: None, - }); - } - - let ecx = &mut ecx.inner; - let gas = Gas::new(input.gas_limit()); - let curr_depth = ecx.journaled_state.depth(); - - // Apply our prank - if let Some(prank) = &self.get_prank(curr_depth) { - if curr_depth >= prank.depth && input.caller() == prank.prank_caller { - // At the target depth we set `msg.sender` - if ecx.journaled_state.depth() == prank.depth { - input.set_caller(prank.new_caller); - } - - // At the target depth, or deeper, we set `tx.origin` - if let Some(new_origin) = prank.new_origin { - ecx.env.tx.caller = new_origin; - } - } - } - - // Apply EIP-2930 access lists. - if let Some(access_list) = &self.access_list { - ecx.env.tx.access_list = access_list.to_vec(); - } - - // Apply our broadcast - if let Some(broadcast) = &self.broadcast { - if curr_depth >= broadcast.depth && input.caller() == broadcast.original_caller { - if let Err(err) = - ecx.journaled_state.load_account(broadcast.new_origin, &mut ecx.db) - { - return Some(CreateOutcome { - result: InterpreterResult { - result: InstructionResult::Revert, - output: Error::encode(err), - gas, - }, - address: None, - }); - } - - ecx.env.tx.caller = broadcast.new_origin; - - if curr_depth == broadcast.depth { - input.set_caller(broadcast.new_origin); - let is_fixed_gas_limit = check_if_fixed_gas_limit(ecx, input.gas_limit()); - - let account = &ecx.journaled_state.state()[&broadcast.new_origin]; - self.broadcastable_transactions.push_back(BroadcastableTransaction { - rpc: ecx.db.active_fork_url(), - transaction: TransactionRequest { - from: Some(broadcast.new_origin), - to: None, - value: Some(input.value()), - input: TransactionInput::new(input.init_code()), - nonce: Some(account.info.nonce), - gas: if is_fixed_gas_limit { Some(input.gas_limit()) } else { None }, - ..Default::default() - } - .into(), - }); - - input.log_debug(self, &input.scheme().unwrap_or(CreateScheme::Create)); - } - } - } - - // Allow cheatcodes from the address of the new contract - let address = input.allow_cheatcodes(self, ecx); - - // If `recordAccountAccesses` has been called, record the create - if let Some(recorded_account_diffs_stack) = &mut self.recorded_account_diffs_stack { - recorded_account_diffs_stack.push(vec![AccountAccess { - chainInfo: crate::Vm::ChainInfo { - forkId: ecx.db.active_fork_id().unwrap_or_default(), - chainId: U256::from(ecx.env.cfg.chain_id), - }, - accessor: input.caller(), - account: address, - kind: crate::Vm::AccountAccessKind::Create, - initialized: true, - oldBalance: U256::ZERO, // updated on (eof)create_end - newBalance: U256::ZERO, // updated on (eof)create_end - value: input.value(), - data: input.init_code(), - reverted: false, - deployedCode: Bytes::new(), // updated on (eof)create_end - storageAccesses: vec![], // updated on (eof)create_end - depth: curr_depth, - }]); - } - - None - } - - // common create_end functionality for both legacy and EOF. - fn create_end_common( - &mut self, - ecx: Ecx, - call: Option<&CreateInputs>, - mut outcome: CreateOutcome, - ) -> CreateOutcome -where { - let ecx = &mut ecx.inner; - let curr_depth = ecx.journaled_state.depth(); - - // Clean up pranks - if let Some(prank) = &self.get_prank(curr_depth) { - if curr_depth == prank.depth { - ecx.env.tx.caller = prank.prank_origin; - - // Clean single-call prank once we have returned to the original depth - if prank.single_call { - std::mem::take(&mut self.pranks); - } - } - } - - // Clean up broadcasts - if let Some(broadcast) = &self.broadcast { - if curr_depth == broadcast.depth { - ecx.env.tx.caller = broadcast.original_origin; - - // Clean single-call broadcast once we have returned to the original depth - if broadcast.single_call { - std::mem::take(&mut self.broadcast); - } - } - } - - // Handle expected reverts - if let Some(expected_revert) = &self.expected_revert { - if curr_depth <= expected_revert.depth && - matches!(expected_revert.kind, ExpectedRevertKind::Default) - { - let mut expected_revert = std::mem::take(&mut self.expected_revert).unwrap(); - return match revert_handlers::handle_expect_revert( - false, - true, - self.config.internal_expect_revert, - &expected_revert, - outcome.result.result, - outcome.result.output.clone(), - &self.config.available_artifacts, - ) { - Ok((address, retdata)) => { - expected_revert.actual_count += 1; - if expected_revert.actual_count < expected_revert.count { - self.expected_revert = Some(expected_revert.clone()); - } - - outcome.result.result = InstructionResult::Return; - outcome.result.output = retdata; - outcome.address = address; - outcome - } - Err(err) => { - outcome.result.result = InstructionResult::Revert; - outcome.result.output = err.abi_encode().into(); - outcome - } - }; - } - } - - // If `startStateDiffRecording` has been called, update the `reverted` status of the - // previous call depth's recorded accesses, if any - if let Some(recorded_account_diffs_stack) = &mut self.recorded_account_diffs_stack { - // The root call cannot be recorded. - if curr_depth > 0 { - if let Some(last_depth) = &mut recorded_account_diffs_stack.pop() { - // Update the reverted status of all deeper calls if this call reverted, in - // accordance with EVM behavior - if outcome.result.is_revert() { - last_depth.iter_mut().for_each(|element| { - element.reverted = true; - element - .storageAccesses - .iter_mut() - .for_each(|storage_access| storage_access.reverted = true); - }) - } - - if let Some(create_access) = last_depth.first_mut() { - // Assert that we're at the correct depth before recording post-create state - // changes. Depending on what depth the cheat was called at, there - // may not be any pending calls to update if execution has - // percolated up to a higher depth. - if create_access.depth == ecx.journaled_state.depth() { - debug_assert_eq!( - create_access.kind as u8, - crate::Vm::AccountAccessKind::Create as u8 - ); - if let Some(address) = outcome.address { - if let Ok(created_acc) = - ecx.journaled_state.load_account(address, &mut ecx.db) - { - create_access.newBalance = created_acc.info.balance; - create_access.deployedCode = created_acc - .info - .code - .clone() - .unwrap_or_default() - .original_bytes(); - } - } - } - // Merge the last depth's AccountAccesses into the AccountAccesses at the - // current depth, or push them back onto the pending - // vector if higher depths were not recorded. This - // preserves ordering of accesses. - if let Some(last) = recorded_account_diffs_stack.last_mut() { - last.append(last_depth); - } else { - recorded_account_diffs_stack.push(last_depth.clone()); - } - } - } - } - } - - // Match the create against expected_creates - if !self.expected_creates.is_empty() { - if let (Some(address), Some(call)) = (outcome.address, call) { - if let Ok(created_acc) = ecx.journaled_state.load_account(address, &mut ecx.db) { - let bytecode = - created_acc.info.code.clone().unwrap_or_default().original_bytes(); - if let Some((index, _)) = - self.expected_creates.iter().find_position(|expected_create| { - expected_create.deployer == call.caller && - expected_create.create_scheme.eq(call.scheme) && - expected_create.bytecode == bytecode - }) - { - self.expected_creates.swap_remove(index); - } - } - } - } - - outcome - } - pub fn call_with_executor( &mut self, ecx: Ecx, @@ -944,7 +674,7 @@ where { // decreasing sender nonce to ensure that it matches on-chain nonce once we start // broadcasting. if curr_depth == 0 { - let sender = ecx.env.tx.caller; + let sender = ecx.tx.caller; let account = match super::evm::journaled_account(ecx, sender) { Ok(account) => account, Err(err) => { @@ -985,8 +715,6 @@ where { }; } - let ecx = &mut ecx.inner; - if call.target_address == HARDHAT_CONSOLE_ADDRESS { return None; } @@ -1002,7 +730,7 @@ where { // The calldata is at most, as big as this call's input, and if calldata.len() <= call.input.len() && // Both calldata match, taking the length of the assumed smaller one (which will have at least the selector), and - *calldata == call.input[..calldata.len()] && + *calldata == call.input.bytes(ecx)[..calldata.len()] && // The value matches, if provided expected .value.is_none_or(|value| Some(value) == call.transfer_value()) && @@ -1018,19 +746,25 @@ where { // Handle mocked calls if let Some(mocks) = self.mocked_calls.get_mut(&call.bytecode_address) { - let ctx = - MockCallDataContext { calldata: call.input.clone(), value: call.transfer_value() }; - - if let Some(return_data_queue) = match mocks.get_mut(&ctx) { - Some(queue) => Some(queue), - None => mocks - .iter_mut() - .find(|(mock, _)| { - call.input.get(..mock.calldata.len()) == Some(&mock.calldata[..]) && - mock.value.is_none_or(|value| Some(value) == call.transfer_value()) - }) - .map(|(_, v)| v), - } { + let ctx = MockCallDataContext { + calldata: call.input.bytes(ecx), + value: call.transfer_value(), + }; + + if let Some(return_data_queue) = + match mocks.get_mut(&ctx) { + Some(queue) => Some(queue), + None => mocks + .iter_mut() + .find(|(mock, _)| { + call.input.bytes(ecx).get(..mock.calldata.len()) == + Some(&mock.calldata[..]) && + mock.value + .is_none_or(|value| Some(value) == call.transfer_value()) + }) + .map(|(_, v)| v), + } + { if let Some(return_data) = if return_data_queue.len() == 1 { // If the mocked calls stack has a single element in it, don't empty it return_data_queue.front().map(|x| x.to_owned()) @@ -1058,7 +792,7 @@ where { call.target_address = prank.new_caller; call.caller = prank.new_caller; if let Some(new_origin) = prank.new_origin { - ecx.env.tx.caller = new_origin; + ecx.tx.caller = new_origin; } } } @@ -1074,7 +808,7 @@ where { // At the target depth, or deeper, we set `tx.origin` if let Some(new_origin) = prank.new_origin { - ecx.env.tx.caller = new_origin; + ecx.tx.caller = new_origin; prank_applied = true; } @@ -1087,10 +821,8 @@ where { } } - // Apply EIP-2930 access lists. - if let Some(access_list) = &self.access_list { - ecx.env.tx.access_list = access_list.to_vec(); - } + // Apply EIP-2930 access list + self.apply_accesslist(ecx); // Apply our broadcast if let Some(broadcast) = &self.broadcast { @@ -1102,7 +834,7 @@ where { // At the target depth we set `msg.sender` & tx.origin. // We are simulating the caller as being an EOA, so *both* must be set to the // broadcast.origin. - ecx.env.tx.caller = broadcast.new_origin; + ecx.tx.caller = broadcast.new_origin; call.caller = broadcast.new_origin; // Add a `legacy` transaction to the VecDeque. We use a legacy transaction here @@ -1110,7 +842,7 @@ where { // into 1559, in the cli package, relatively easily once we // know the target chain supports EIP-1559. if !call.is_static { - if let Err(err) = ecx.load_account(broadcast.new_origin) { + if let Err(err) = ecx.journaled_state.load_account(broadcast.new_origin) { return Some(CallOutcome { result: InterpreterResult { result: InstructionResult::Revert, @@ -1121,24 +853,29 @@ where { }); } - let is_fixed_gas_limit = check_if_fixed_gas_limit(ecx, call.gas_limit); + let is_fixed_gas_limit = check_if_fixed_gas_limit(&ecx, call.gas_limit); + + let input = TransactionInput::new(call.input.bytes(ecx)); let account = - ecx.journaled_state.state().get_mut(&broadcast.new_origin).unwrap(); + ecx.journaled_state.inner.state().get_mut(&broadcast.new_origin).unwrap(); let mut tx_req = TransactionRequest { from: Some(broadcast.new_origin), to: Some(TxKind::from(Some(call.target_address))), value: call.transfer_value(), - input: TransactionInput::new(call.input.clone()), + input, nonce: Some(account.info.nonce), - chain_id: Some(ecx.env.cfg.chain_id), + chain_id: Some(ecx.cfg.chain_id), gas: if is_fixed_gas_limit { Some(call.gas_limit) } else { None }, ..Default::default() }; - match (self.active_delegation.take(), self.active_blob_sidecar.take()) { - (Some(_), Some(_)) => { + let active_delegations = std::mem::take(&mut self.active_delegations); + // Set active blob sidecar, if any. + if let Some(blob_sidecar) = self.active_blob_sidecar.take() { + // Ensure blob and delegation are not set for the same tx. + if !active_delegations.is_empty() { let msg = "both delegation and blob are active; `attachBlob` and `attachDelegation` are not compatible"; return Some(CallOutcome { result: InterpreterResult { @@ -1149,22 +886,26 @@ where { memory_offset: call.return_memory_offset.clone(), }); } - (Some(auth_list), None) => { - tx_req.authorization_list = Some(vec![auth_list]); - tx_req.sidecar = None; - } - (None, Some(blob_sidecar)) => { - tx_req.set_blob_sidecar(blob_sidecar); - tx_req.authorization_list = None; - } - (None, None) => { - tx_req.sidecar = None; - tx_req.authorization_list = None; + tx_req.set_blob_sidecar(blob_sidecar); + } + + // Apply active EIP-7702 delegations, if any. + if !active_delegations.is_empty() { + for auth in &active_delegations { + let Ok(authority) = auth.recover_authority() else { + continue; + }; + if authority == broadcast.new_origin { + // Increment nonce of broadcasting account to reflect signed + // authorization. + account.info.nonce += 1; + } } + tx_req.authorization_list = Some(active_delegations); } self.broadcastable_transactions.push_back(BroadcastableTransaction { - rpc: ecx.db.active_fork_url(), + rpc: ecx.journaled_state.database.active_fork_url(), transaction: tx_req.into(), }); debug!(target: "cheatcodes", tx=?self.broadcastable_transactions.back().unwrap(), "broadcastable call"); @@ -1196,7 +937,7 @@ where { // nonce, a non-zero KECCAK_EMPTY codehash, or non-empty code let initialized; let old_balance; - if let Ok(acc) = ecx.load_account(call.target_address) { + if let Ok(acc) = ecx.journaled_state.load_account(call.target_address) { initialized = acc.info.exists(); old_balance = acc.info.balance; } else { @@ -1219,8 +960,8 @@ where { // as "warm" if the call from which they were accessed is reverted recorded_account_diffs_stack.push(vec![AccountAccess { chainInfo: crate::Vm::ChainInfo { - forkId: ecx.db.active_fork_id().unwrap_or_default(), - chainId: U256::from(ecx.env.cfg.chain_id), + forkId: ecx.journaled_state.db().active_fork_id().unwrap_or_default(), + chainId: U256::from(ecx.cfg.chain_id), }, accessor: call.caller, account: call.bytecode_address, @@ -1229,11 +970,15 @@ where { oldBalance: old_balance, newBalance: U256::ZERO, // updated on call_end value: call.call_value(), - data: call.input.clone(), + data: call.input.bytes(ecx), reverted: false, deployedCode: Bytes::new(), storageAccesses: vec![], // updated on step - depth: ecx.journaled_state.depth(), + depth: ecx + .journaled_state + .depth() + .try_into() + .expect("journaled state depth exceeds u64"), }]); } @@ -1254,6 +999,13 @@ where { }) } + pub fn set_seed(&mut self, seed: U256) { + self.test_runner = Some(TestRunner::new_with_rng( + proptest::test_runner::Config::default(), + TestRng::from_seed(RngAlgorithm::ChaCha, &seed.to_be_bytes::<32>()), + )); + } + /// Returns existing or set a default `ArbitraryStorage` option. /// Used by `setArbitraryStorage` cheatcode to track addresses with arbitrary storage. pub fn arbitrary_storage(&mut self) -> &mut ArbitraryStorage { @@ -1298,21 +1050,21 @@ where { } } -impl Inspector<&mut dyn DatabaseExt> for Cheatcodes { +impl Inspector> for Cheatcodes { #[inline] fn initialize_interp(&mut self, interpreter: &mut Interpreter, ecx: Ecx) { // When the first interpreter is initialized we've circumvented the balance and gas checks, // so we apply our actual block data with the correct fees and all. if let Some(block) = self.block.take() { - ecx.env.block = block; + ecx.block = block; } if let Some(gas_price) = self.gas_price.take() { - ecx.env.tx.gas_price = gas_price; + ecx.tx.gas_price = gas_price; } // Record gas for current frame. if self.gas_metering.paused { - self.gas_metering.paused_frames.push(interpreter.gas); + self.gas_metering.paused_frames.push(interpreter.control.gas); } // `expectRevert`: track the max call depth during `expectRevert` @@ -1323,7 +1075,7 @@ impl Inspector<&mut dyn DatabaseExt> for Cheatcodes { #[inline] fn step(&mut self, interpreter: &mut Interpreter, ecx: Ecx) { - self.pc = interpreter.program_counter(); + self.pc = interpreter.bytecode.pc(); // `pauseGasMetering`: pause / resume interpreter gas. if self.gas_metering.paused { @@ -1347,7 +1099,10 @@ impl Inspector<&mut dyn DatabaseExt> for Cheatcodes { // `expectSafeMemory`: check if the current opcode is allowed to interact with memory. if !self.allowed_mem_writes.is_empty() { - self.check_mem_opcodes(interpreter, ecx.journaled_state.depth()); + self.check_mem_opcodes( + interpreter, + ecx.journaled_state.depth().try_into().expect("journaled state depth exceeds u64"), + ); } // `startMappingRecording`: record SSTORE and KECCAK256. @@ -1377,9 +1132,9 @@ impl Inspector<&mut dyn DatabaseExt> for Cheatcodes { } } - fn log(&mut self, interpreter: &mut Interpreter, _ecx: Ecx, log: &Log) { + fn log(&mut self, interpreter: &mut Interpreter, _ecx: Ecx, log: Log) { if !self.expected_emits.is_empty() { - expect::handle_expect_emit(self, log, interpreter); + expect::handle_expect_emit(self, &log, interpreter); } // `recordLogs` @@ -1396,8 +1151,7 @@ impl Inspector<&mut dyn DatabaseExt> for Cheatcodes { Self::call_with_executor(self, ecx, inputs, &mut TransparentCheatcodesExecutor) } - fn call_end(&mut self, ecx: Ecx, call: &CallInputs, mut outcome: CallOutcome) -> CallOutcome { - let ecx = &mut ecx.inner; + fn call_end(&mut self, ecx: Ecx, call: &CallInputs, outcome: &mut CallOutcome) { let cheatcode_call = call.target_address == CHEATCODE_ADDRESS || call.target_address == HARDHAT_CONSOLE_ADDRESS; @@ -1409,7 +1163,7 @@ impl Inspector<&mut dyn DatabaseExt> for Cheatcodes { let curr_depth = ecx.journaled_state.depth(); if let Some(prank) = &self.get_prank(curr_depth) { if curr_depth == prank.depth { - ecx.env.tx.caller = prank.prank_origin; + ecx.tx.caller = prank.prank_origin; // Clean single-call prank once we have returned to the original depth if prank.single_call { @@ -1420,8 +1174,8 @@ impl Inspector<&mut dyn DatabaseExt> for Cheatcodes { // Clean up broadcast if let Some(broadcast) = &self.broadcast { - if ecx.journaled_state.depth() == broadcast.depth { - ecx.env.tx.caller = broadcast.original_origin; + if curr_depth == broadcast.depth { + ecx.tx.caller = broadcast.original_origin; // Clean single-call broadcast once we have returned to the original depth if broadcast.single_call { @@ -1438,8 +1192,10 @@ impl Inspector<&mut dyn DatabaseExt> for Cheatcodes { if outcome.result.is_revert() && assume_no_revert.reverted_by.is_none() { assume_no_revert.reverted_by = Some(call.target_address); } + // allow multiple cheatcode calls at the same depth - if ecx.journaled_state.depth() <= assume_no_revert.depth && !cheatcode_call { + let curr_depth = ecx.journaled_state.depth(); + if curr_depth <= assume_no_revert.depth && !cheatcode_call { // Discard run if we're at the same depth as cheatcode, call reverted, and no // specific reason was supplied if outcome.result.is_revert() { @@ -1454,7 +1210,6 @@ impl Inspector<&mut dyn DatabaseExt> for Cheatcodes { // to reject this run Ok(_) => { outcome.result.output = Error::from(MAGIC_ASSUME).abi_encode().into(); - outcome } // if result is Error, it was an unanticipated revert; should revert // normally @@ -1462,13 +1217,11 @@ impl Inspector<&mut dyn DatabaseExt> for Cheatcodes { trace!(expected=?assume_no_revert, ?error, status=?outcome.result.result, "Expected revert mismatch"); outcome.result.result = InstructionResult::Revert; outcome.result.output = error.abi_encode().into(); - outcome } } } else { // Call didn't revert, reset `assume_no_revert` state. self.assume_no_revert = None; - return outcome; } } } @@ -1488,7 +1241,8 @@ impl Inspector<&mut dyn DatabaseExt> for Cheatcodes { } } - if ecx.journaled_state.depth() <= expected_revert.depth { + let curr_depth = ecx.journaled_state.depth(); + if curr_depth <= expected_revert.depth { let needs_processing = match expected_revert.kind { ExpectedRevertKind::Default => !cheatcode_call, // `pending_processing` == true means that we're in the `call_end` hook for @@ -1513,7 +1267,6 @@ impl Inspector<&mut dyn DatabaseExt> for Cheatcodes { trace!(expected=?expected_revert, ?error, status=?outcome.result.result, "Expected revert mismatch"); outcome.result.result = InstructionResult::Revert; outcome.result.output = error.abi_encode().into(); - outcome } Ok((_, retdata)) => { expected_revert.actual_count += 1; @@ -1522,7 +1275,6 @@ impl Inspector<&mut dyn DatabaseExt> for Cheatcodes { } outcome.result.result = InstructionResult::Return; outcome.result.output = retdata; - outcome } }; } @@ -1540,7 +1292,7 @@ impl Inspector<&mut dyn DatabaseExt> for Cheatcodes { // Exit early for calls to cheatcodes as other logic is not relevant for cheatcode // invocations if cheatcode_call { - return outcome; + return; } // Record the gas usage of the call, this allows the `lastCallGas` cheatcode to @@ -1577,8 +1329,9 @@ impl Inspector<&mut dyn DatabaseExt> for Cheatcodes { // changes. Depending on the depth the cheat was // called at, there may not be any pending // calls to update if execution has percolated up to a higher depth. - if call_access.depth == ecx.journaled_state.depth() { - if let Ok(acc) = ecx.load_account(call.target_address) { + let curr_depth = ecx.journaled_state.depth(); + if call_access.depth == curr_depth as u64 { + if let Ok(acc) = ecx.journaled_state.load_account(call.target_address) { debug_assert!(access_is_call(call_access.kind)); call_access.newBalance = acc.info.balance; } @@ -1611,7 +1364,10 @@ impl Inspector<&mut dyn DatabaseExt> for Cheatcodes { let should_check_emits = self .expected_emits .iter() - .any(|(expected, _)| expected.depth == ecx.journaled_state.depth()) && + .any(|(expected, _)| { + let curr_depth = ecx.journaled_state.depth(); + expected.depth == curr_depth + }) && // Ignore staticcalls !call.is_static; if should_check_emits { @@ -1642,11 +1398,12 @@ impl Inspector<&mut dyn DatabaseExt> for Cheatcodes { }) .collect::>(); - // Not all emits were matched. - if self.expected_emits.iter().any(|(expected, _)| !expected.found) { + // Revert if not all emits expected were matched. + if self.expected_emits.iter().any(|(expected, _)| !expected.found && expected.count > 0) + { outcome.result.result = InstructionResult::Revert; outcome.result.output = "log != expected log".abi_encode().into(); - return outcome; + return; } if !expected_counts.is_empty() { @@ -1661,7 +1418,7 @@ impl Inspector<&mut dyn DatabaseExt> for Cheatcodes { outcome.result.result = InstructionResult::Revert; outcome.result.output = Error::encode(msg); - return outcome; + return; } // All emits were found, we're good. @@ -1679,21 +1436,22 @@ impl Inspector<&mut dyn DatabaseExt> for Cheatcodes { if outcome.result.is_revert() { if let Some(err) = diag { outcome.result.output = Error::encode(err.to_error_msg(&self.labels)); - return outcome; + return; } } // try to diagnose reverts in multi-fork mode where a call is made to an address that does // not exist - if let TxKind::Call(test_contract) = ecx.env.tx.transact_to { + if let TxKind::Call(test_contract) = ecx.tx.kind { // if a call to a different contract than the original test contract returned with // `Stop` we check if the contract actually exists on the active fork - if ecx.db.is_forked_mode() && + if ecx.journaled_state.db().is_forked_mode() && outcome.result.result == InstructionResult::Stop && call.target_address != test_contract { + let journaled_state = ecx.journaled_state.clone(); self.fork_revert_diagnostic = - ecx.db.diagnose_revert(call.target_address, &ecx.journaled_state); + ecx.journaled_state.db().diagnose_revert(call.target_address, &journaled_state); } } @@ -1703,7 +1461,7 @@ impl Inspector<&mut dyn DatabaseExt> for Cheatcodes { // earlier error that happened first with unrelated information about // another error when using cheatcodes. if outcome.result.is_revert() { - return outcome; + return; } // If there's not a revert, we can continue on to run the last logic for expect* @@ -1753,7 +1511,7 @@ impl Inspector<&mut dyn DatabaseExt> for Cheatcodes { outcome.result.result = InstructionResult::Revert; outcome.result.output = Error::encode(msg); - return outcome; + return; } } } @@ -1772,7 +1530,7 @@ impl Inspector<&mut dyn DatabaseExt> for Cheatcodes { }; outcome.result.result = InstructionResult::Revert; outcome.result.output = Error::encode(msg); - return outcome; + return; } // Check for leftover expected creates @@ -1785,42 +1543,269 @@ impl Inspector<&mut dyn DatabaseExt> for Cheatcodes { ); outcome.result.result = InstructionResult::Revert; outcome.result.output = Error::encode(msg); - return outcome; } } - - outcome } - fn create(&mut self, ecx: Ecx, call: &mut CreateInputs) -> Option { - self.create_common(ecx, call) - } + fn create(&mut self, ecx: Ecx, mut input: &mut CreateInputs) -> Option { + // Check if we should intercept this create + if self.intercept_next_create_call { + // Reset the flag + self.intercept_next_create_call = false; - fn create_end( - &mut self, - ecx: Ecx, - call: &CreateInputs, - outcome: CreateOutcome, - ) -> CreateOutcome { - self.create_end_common(ecx, Some(call), outcome) - } + // Get initcode from the input + let output = input.init_code(); - fn eofcreate(&mut self, ecx: Ecx, call: &mut EOFCreateInputs) -> Option { - self.create_common(ecx, call) + // Return a revert with the initcode as error data + return Some(CreateOutcome { + result: InterpreterResult { + result: InstructionResult::Revert, + output, + gas: Gas::new(input.gas_limit()), + }, + address: None, + }); + } + + let gas = Gas::new(input.gas_limit()); + let curr_depth = ecx.journaled_state.depth(); + + // Apply our prank + if let Some(prank) = &self.get_prank(curr_depth) { + if curr_depth >= prank.depth && input.caller() == prank.prank_caller { + let mut prank_applied = false; + + // At the target depth we set `msg.sender` + if curr_depth == prank.depth { + input.set_caller(prank.new_caller); + prank_applied = true; + } + + // At the target depth, or deeper, we set `tx.origin` + if let Some(new_origin) = prank.new_origin { + ecx.tx.caller = new_origin; + prank_applied = true; + } + + // If prank applied for first time, then update + if prank_applied { + if let Some(applied_prank) = prank.first_time_applied() { + self.pranks.insert(curr_depth, applied_prank); + } + } + } + } + + // Apply EIP-2930 access list + self.apply_accesslist(ecx); + + // Apply our broadcast + if let Some(broadcast) = &self.broadcast { + if curr_depth >= broadcast.depth && input.caller() == broadcast.original_caller { + if let Err(err) = ecx.journaled_state.load_account(broadcast.new_origin) { + return Some(CreateOutcome { + result: InterpreterResult { + result: InstructionResult::Revert, + output: Error::encode(err), + gas, + }, + address: None, + }); + } + + ecx.tx.caller = broadcast.new_origin; + + if curr_depth == broadcast.depth { + input.set_caller(broadcast.new_origin); + let is_fixed_gas_limit = check_if_fixed_gas_limit(&ecx, input.gas_limit()); + + let account = &ecx.journaled_state.inner.state()[&broadcast.new_origin]; + self.broadcastable_transactions.push_back(BroadcastableTransaction { + rpc: ecx.journaled_state.database.active_fork_url(), + transaction: TransactionRequest { + from: Some(broadcast.new_origin), + to: None, + value: Some(input.value()), + input: TransactionInput::new(input.init_code()), + nonce: Some(account.info.nonce), + gas: if is_fixed_gas_limit { Some(input.gas_limit()) } else { None }, + ..Default::default() + } + .into(), + }); + + input.log_debug(self, &input.scheme().unwrap_or(CreateScheme::Create)); + } + } + } + + // Allow cheatcodes from the address of the new contract + let address = input.allow_cheatcodes(self, ecx); + + // If `recordAccountAccesses` has been called, record the create + if let Some(recorded_account_diffs_stack) = &mut self.recorded_account_diffs_stack { + recorded_account_diffs_stack.push(vec![AccountAccess { + chainInfo: crate::Vm::ChainInfo { + forkId: ecx.journaled_state.db().active_fork_id().unwrap_or_default(), + chainId: U256::from(ecx.cfg.chain_id), + }, + accessor: input.caller(), + account: address, + kind: crate::Vm::AccountAccessKind::Create, + initialized: true, + oldBalance: U256::ZERO, // updated on create_end + newBalance: U256::ZERO, // updated on create_end + value: input.value(), + data: input.init_code(), + reverted: false, + deployedCode: Bytes::new(), // updated on create_end + storageAccesses: vec![], // updated on create_end + depth: curr_depth as u64, + }]); + } + + None } - fn eofcreate_end( - &mut self, - ecx: Ecx, - _call: &EOFCreateInputs, - outcome: CreateOutcome, - ) -> CreateOutcome { - self.create_end_common(ecx, None, outcome) + fn create_end(&mut self, ecx: Ecx, call: &CreateInputs, outcome: &mut CreateOutcome) { + let call = Some(call); + let curr_depth = ecx.journaled_state.depth(); + + // Clean up pranks + if let Some(prank) = &self.get_prank(curr_depth) { + if curr_depth == prank.depth { + ecx.tx.caller = prank.prank_origin; + + // Clean single-call prank once we have returned to the original depth + if prank.single_call { + std::mem::take(&mut self.pranks); + } + } + } + + // Clean up broadcasts + if let Some(broadcast) = &self.broadcast { + if curr_depth == broadcast.depth { + ecx.tx.caller = broadcast.original_origin; + + // Clean single-call broadcast once we have returned to the original depth + if broadcast.single_call { + std::mem::take(&mut self.broadcast); + } + } + } + + // Handle expected reverts + if let Some(expected_revert) = &self.expected_revert { + if curr_depth <= expected_revert.depth && + matches!(expected_revert.kind, ExpectedRevertKind::Default) + { + let mut expected_revert = std::mem::take(&mut self.expected_revert).unwrap(); + return match revert_handlers::handle_expect_revert( + false, + true, + self.config.internal_expect_revert, + &expected_revert, + outcome.result.result, + outcome.result.output.clone(), + &self.config.available_artifacts, + ) { + Ok((address, retdata)) => { + expected_revert.actual_count += 1; + if expected_revert.actual_count < expected_revert.count { + self.expected_revert = Some(expected_revert.clone()); + } + + outcome.result.result = InstructionResult::Return; + outcome.result.output = retdata; + outcome.address = address; + } + Err(err) => { + outcome.result.result = InstructionResult::Revert; + outcome.result.output = err.abi_encode().into(); + } + }; + } + } + + // If `startStateDiffRecording` has been called, update the `reverted` status of the + // previous call depth's recorded accesses, if any + if let Some(recorded_account_diffs_stack) = &mut self.recorded_account_diffs_stack { + // The root call cannot be recorded. + if curr_depth > 0 { + if let Some(last_depth) = &mut recorded_account_diffs_stack.pop() { + // Update the reverted status of all deeper calls if this call reverted, in + // accordance with EVM behavior + if outcome.result.is_revert() { + last_depth.iter_mut().for_each(|element| { + element.reverted = true; + element + .storageAccesses + .iter_mut() + .for_each(|storage_access| storage_access.reverted = true); + }) + } + + if let Some(create_access) = last_depth.first_mut() { + // Assert that we're at the correct depth before recording post-create state + // changes. Depending on what depth the cheat was called at, there + // may not be any pending calls to update if execution has + // percolated up to a higher depth. + let depth = ecx.journaled_state.depth(); + if create_access.depth == depth as u64 { + debug_assert_eq!( + create_access.kind as u8, + crate::Vm::AccountAccessKind::Create as u8 + ); + if let Some(address) = outcome.address { + if let Ok(created_acc) = ecx.journaled_state.load_account(address) { + create_access.newBalance = created_acc.info.balance; + create_access.deployedCode = created_acc + .info + .code + .clone() + .unwrap_or_default() + .original_bytes(); + } + } + } + // Merge the last depth's AccountAccesses into the AccountAccesses at the + // current depth, or push them back onto the pending + // vector if higher depths were not recorded. This + // preserves ordering of accesses. + if let Some(last) = recorded_account_diffs_stack.last_mut() { + last.append(last_depth); + } else { + recorded_account_diffs_stack.push(last_depth.clone()); + } + } + } + } + } + + // Match the create against expected_creates + if !self.expected_creates.is_empty() { + if let (Some(address), Some(call)) = (outcome.address, call) { + if let Ok(created_acc) = ecx.journaled_state.load_account(address) { + let bytecode = + created_acc.info.code.clone().unwrap_or_default().original_bytes(); + if let Some((index, _)) = + self.expected_creates.iter().find_position(|expected_create| { + expected_create.deployer == call.caller && + expected_create.create_scheme.eq(call.scheme.into()) && + expected_create.bytecode == bytecode + }) + { + self.expected_creates.swap_remove(index); + } + } + } + } } } impl InspectorExt for Cheatcodes { - fn should_use_create2_factory(&mut self, ecx: Ecx, inputs: &mut CreateInputs) -> bool { + fn should_use_create2_factory(&mut self, ecx: Ecx, inputs: &CreateInputs) -> bool { if let CreateScheme::Create2 { .. } = inputs.scheme { let depth = ecx.journaled_state.depth(); let target_depth = if let Some(prank) = &self.get_prank(depth) { @@ -1848,29 +1833,37 @@ impl Cheatcodes { fn meter_gas(&mut self, interpreter: &mut Interpreter) { if let Some(paused_gas) = self.gas_metering.paused_frames.last() { // Keep gas constant if paused. - interpreter.gas = *paused_gas; + // Make sure we record the memory changes so that memory expansion is not paused. + let memory = *interpreter.control.gas.memory(); + interpreter.control.gas = *paused_gas; + interpreter.control.gas.memory_mut().words_num = memory.words_num; + interpreter.control.gas.memory_mut().expansion_cost = memory.expansion_cost; } else { // Record frame paused gas. - self.gas_metering.paused_frames.push(interpreter.gas); + self.gas_metering.paused_frames.push(interpreter.control.gas); } } #[cold] fn meter_gas_record(&mut self, interpreter: &mut Interpreter, ecx: Ecx) { - if matches!(interpreter.instruction_result, InstructionResult::Continue) { + if matches!(interpreter.control.instruction_result, InstructionResult::Continue) { self.gas_metering.gas_records.iter_mut().for_each(|record| { - if ecx.journaled_state.depth() == record.depth { + let curr_depth = ecx.journaled_state.depth(); + if curr_depth == record.depth { // Skip the first opcode of the first call frame as it includes the gas cost of // creating the snapshot. if self.gas_metering.last_gas_used != 0 { - let gas_diff = - interpreter.gas.spent().saturating_sub(self.gas_metering.last_gas_used); + let gas_diff = interpreter + .control + .gas + .spent() + .saturating_sub(self.gas_metering.last_gas_used); record.gas_used = record.gas_used.saturating_add(gas_diff); } // Update `last_gas_used` to the current spent gas for the next iteration to // compare against. - self.gas_metering.last_gas_used = interpreter.gas.spent(); + self.gas_metering.last_gas_used = interpreter.control.gas.spent(); } }); } @@ -1879,27 +1872,27 @@ impl Cheatcodes { #[cold] fn meter_gas_end(&mut self, interpreter: &mut Interpreter) { // Remove recorded gas if we exit frame. - if will_exit(interpreter.instruction_result) { + if will_exit(interpreter.control.instruction_result) { self.gas_metering.paused_frames.pop(); } } #[cold] fn meter_gas_reset(&mut self, interpreter: &mut Interpreter) { - interpreter.gas = Gas::new(interpreter.gas().limit()); + interpreter.control.gas = Gas::new(interpreter.control.gas.limit()); self.gas_metering.reset = false; } #[cold] fn meter_gas_check(&mut self, interpreter: &mut Interpreter) { - if will_exit(interpreter.instruction_result) { + if will_exit(interpreter.control.instruction_result) { // Reset gas if spent is less than refunded. // This can happen if gas was paused / resumed or reset. // https://github.com/foundry-rs/foundry/issues/4370 - if interpreter.gas.spent() < - u64::try_from(interpreter.gas.refunded()).unwrap_or_default() + if interpreter.control.gas.spent() < + u64::try_from(interpreter.control.gas.refunded()).unwrap_or_default() { - interpreter.gas = Gas::new(interpreter.gas.limit()); + interpreter.control.gas = Gas::new(interpreter.control.gas.limit()); } } } @@ -1913,13 +1906,13 @@ impl Cheatcodes { /// - generates arbitrary value and saves it in target address storage. #[cold] fn arbitrary_storage_end(&mut self, interpreter: &mut Interpreter, ecx: Ecx) { - let (key, target_address) = if interpreter.current_opcode() == op::SLOAD { - (try_or_return!(interpreter.stack().peek(0)), interpreter.contract().target_address) + let (key, target_address) = if interpreter.bytecode.opcode() == op::SLOAD { + (try_or_return!(interpreter.stack.peek(0)), interpreter.input.target_address) } else { return }; - let Ok(value) = ecx.sload(target_address, key) else { + let Some(value) = ecx.sload(target_address, key) else { return; }; @@ -1927,17 +1920,17 @@ impl Cheatcodes { self.should_overwrite_arbitrary_storage(&target_address, key) { if self.has_arbitrary_storage(&target_address) { - let arbitrary_value = self.rng().gen(); + let arbitrary_value = self.rng().random(); self.arbitrary_storage.as_mut().unwrap().save( - &mut ecx.inner, + ecx, target_address, key, arbitrary_value, ); } else if self.is_arbitrary_storage_copy(&target_address) { - let arbitrary_value = self.rng().gen(); + let arbitrary_value = self.rng().random(); self.arbitrary_storage.as_mut().unwrap().copy( - &mut ecx.inner, + ecx, target_address, key, arbitrary_value, @@ -1950,14 +1943,14 @@ impl Cheatcodes { #[cold] fn record_accesses(&mut self, interpreter: &mut Interpreter) { let access = &mut self.accesses; - match interpreter.current_opcode() { + match interpreter.bytecode.opcode() { op::SLOAD => { - let key = try_or_return!(interpreter.stack().peek(0)); - access.record_read(interpreter.contract().target_address, key); + let key = try_or_return!(interpreter.stack.peek(0)); + access.record_read(interpreter.input.target_address, key); } op::SSTORE => { - let key = try_or_return!(interpreter.stack().peek(0)); - access.record_write(interpreter.contract().target_address, key); + let key = try_or_return!(interpreter.stack.peek(0)); + access.record_write(interpreter.input.target_address, key); } _ => {} } @@ -1966,32 +1959,33 @@ impl Cheatcodes { #[cold] fn record_state_diffs(&mut self, interpreter: &mut Interpreter, ecx: Ecx) { let Some(account_accesses) = &mut self.recorded_account_diffs_stack else { return }; - match interpreter.current_opcode() { + match interpreter.bytecode.opcode() { op::SELFDESTRUCT => { // Ensure that we're not selfdestructing a context recording was initiated on let Some(last) = account_accesses.last_mut() else { return }; // get previous balance and initialized status of the target account - let target = try_or_return!(interpreter.stack().peek(0)); + let target = try_or_return!(interpreter.stack.peek(0)); let target = Address::from_word(B256::from(target)); let (initialized, old_balance) = ecx + .journaled_state .load_account(target) .map(|account| (account.info.exists(), account.info.balance)) .unwrap_or_default(); // load balance of this account let value = ecx - .balance(interpreter.contract().target_address) + .balance(interpreter.input.target_address) .map(|b| b.data) .unwrap_or(U256::ZERO); // register access for the target account last.push(crate::Vm::AccountAccess { chainInfo: crate::Vm::ChainInfo { - forkId: ecx.db.active_fork_id().unwrap_or_default(), - chainId: U256::from(ecx.env.cfg.chain_id), + forkId: ecx.journaled_state.database.active_fork_id().unwrap_or_default(), + chainId: U256::from(ecx.cfg.chain_id), }, - accessor: interpreter.contract().target_address, + accessor: interpreter.input.target_address, account: target, kind: crate::Vm::AccountAccessKind::SelfDestruct, initialized, @@ -2002,46 +1996,55 @@ impl Cheatcodes { reverted: false, deployedCode: Bytes::new(), storageAccesses: vec![], - depth: ecx.journaled_state.depth(), + depth: ecx + .journaled_state + .depth() + .try_into() + .expect("journaled state depth exceeds u64"), }); } op::SLOAD => { let Some(last) = account_accesses.last_mut() else { return }; - let key = try_or_return!(interpreter.stack().peek(0)); - let address = interpreter.contract().target_address; + let key = try_or_return!(interpreter.stack.peek(0)); + let address = interpreter.input.target_address; // Try to include present value for informational purposes, otherwise assume // it's not set (zero value) let mut present_value = U256::ZERO; // Try to load the account and the slot's present value - if ecx.load_account(address).is_ok() { - if let Ok(previous) = ecx.sload(address, key) { + if ecx.journaled_state.load_account(address).is_ok() { + if let Some(previous) = ecx.sload(address, key) { present_value = previous.data; } } let access = crate::Vm::StorageAccess { - account: interpreter.contract().target_address, + account: interpreter.input.target_address, slot: key.into(), isWrite: false, previousValue: present_value.into(), newValue: present_value.into(), reverted: false, }; - append_storage_access(last, access, ecx.journaled_state.depth()); + let curr_depth = ecx + .journaled_state + .depth() + .try_into() + .expect("journaled state depth exceeds u64"); + append_storage_access(last, access, curr_depth); } op::SSTORE => { let Some(last) = account_accesses.last_mut() else { return }; - let key = try_or_return!(interpreter.stack().peek(0)); - let value = try_or_return!(interpreter.stack().peek(1)); - let address = interpreter.contract().target_address; + let key = try_or_return!(interpreter.stack.peek(0)); + let value = try_or_return!(interpreter.stack.peek(1)); + let address = interpreter.input.target_address; // Try to load the account and the slot's previous value, otherwise, assume it's // not set (zero value) let mut previous_value = U256::ZERO; - if ecx.load_account(address).is_ok() { - if let Ok(previous) = ecx.sload(address, key) { + if ecx.journaled_state.load_account(address).is_ok() { + if let Some(previous) = ecx.sload(address, key) { previous_value = previous.data; } } @@ -2054,12 +2057,17 @@ impl Cheatcodes { newValue: value.into(), reverted: false, }; - append_storage_access(last, access, ecx.journaled_state.depth()); + let curr_depth = ecx + .journaled_state + .depth() + .try_into() + .expect("journaled state depth exceeds u64"); + append_storage_access(last, access, curr_depth); } // Record account accesses via the EXT family of opcodes op::EXTCODECOPY | op::EXTCODESIZE | op::EXTCODEHASH | op::BALANCE => { - let kind = match interpreter.current_opcode() { + let kind = match interpreter.bytecode.opcode() { op::EXTCODECOPY => crate::Vm::AccountAccessKind::Extcodecopy, op::EXTCODESIZE => crate::Vm::AccountAccessKind::Extcodesize, op::EXTCODEHASH => crate::Vm::AccountAccessKind::Extcodehash, @@ -2067,22 +2075,27 @@ impl Cheatcodes { _ => unreachable!(), }; let address = - Address::from_word(B256::from(try_or_return!(interpreter.stack().peek(0)))); + Address::from_word(B256::from(try_or_return!(interpreter.stack.peek(0)))); let initialized; let balance; - if let Ok(acc) = ecx.load_account(address) { + if let Ok(acc) = ecx.journaled_state.load_account(address) { initialized = acc.info.exists(); balance = acc.info.balance; } else { initialized = false; balance = U256::ZERO; } + let curr_depth = ecx + .journaled_state + .depth() + .try_into() + .expect("journaled state depth exceeds u64"); let account_access = crate::Vm::AccountAccess { chainInfo: crate::Vm::ChainInfo { - forkId: ecx.db.active_fork_id().unwrap_or_default(), - chainId: U256::from(ecx.env.cfg.chain_id), + forkId: ecx.journaled_state.database.active_fork_id().unwrap_or_default(), + chainId: U256::from(ecx.cfg.chain_id), }, - accessor: interpreter.contract().target_address, + accessor: interpreter.input.target_address, account: address, kind, initialized, @@ -2093,7 +2106,7 @@ impl Cheatcodes { reverted: false, deployedCode: Bytes::new(), storageAccesses: vec![], - depth: ecx.journaled_state.depth(), + depth: curr_depth, }; // Record the EXT* call as an account access at the current depth // (future storage accesses will be recorded in a new "Resume" context) @@ -2127,14 +2140,14 @@ impl Cheatcodes { // size of the memory write is implicit, so these cases are hard-coded. macro_rules! mem_opcode_match { ($(($opcode:ident, $offset_depth:expr, $size_depth:expr, $writes:expr)),* $(,)?) => { - match interpreter.current_opcode() { + match interpreter.bytecode.opcode() { //////////////////////////////////////////////////////////////// // OPERATIONS THAT CAN EXPAND/MUTATE MEMORY BY WRITING // //////////////////////////////////////////////////////////////// op::MSTORE => { // The offset of the mstore operation is at the top of the stack. - let offset = try_or_return!(interpreter.stack().peek(0)).saturating_to::(); + let offset = try_or_return!(interpreter.stack.peek(0)).saturating_to::(); // If none of the allowed ranges contain [offset, offset + 32), memory has been // unexpectedly mutated. @@ -2145,7 +2158,7 @@ impl Cheatcodes { // `stopExpectSafeMemory`, this is allowed. It will do so at the current free memory // pointer, which could have been updated to the exclusive upper bound during // execution. - let value = try_or_return!(interpreter.stack().peek(1)).to_be_bytes::<32>(); + let value = try_or_return!(interpreter.stack.peek(1)).to_be_bytes::<32>(); if value[..SELECTOR_LEN] == stopExpectSafeMemoryCall::SELECTOR { return } @@ -2156,7 +2169,7 @@ impl Cheatcodes { } op::MSTORE8 => { // The offset of the mstore8 operation is at the top of the stack. - let offset = try_or_return!(interpreter.stack().peek(0)).saturating_to::(); + let offset = try_or_return!(interpreter.stack.peek(0)).saturating_to::(); // If none of the allowed ranges contain the offset, memory has been // unexpectedly mutated. @@ -2172,12 +2185,12 @@ impl Cheatcodes { op::MLOAD => { // The offset of the mload operation is at the top of the stack - let offset = try_or_return!(interpreter.stack().peek(0)).saturating_to::(); + let offset = try_or_return!(interpreter.stack.peek(0)).saturating_to::(); // If the offset being loaded is >= than the memory size, the // memory is being expanded. If none of the allowed ranges contain // [offset, offset + 32), memory has been unexpectedly mutated. - if offset >= interpreter.shared_memory.len() as u64 && !ranges.iter().any(|range| { + if offset >= interpreter.memory.size() as u64 && !ranges.iter().any(|range| { range.contains(&offset) && range.contains(&(offset + 31)) }) { disallowed_mem_write(offset, 32, interpreter, ranges); @@ -2191,10 +2204,10 @@ impl Cheatcodes { op::CALL => { // The destination offset of the operation is the fifth element on the stack. - let dest_offset = try_or_return!(interpreter.stack().peek(5)).saturating_to::(); + let dest_offset = try_or_return!(interpreter.stack.peek(5)).saturating_to::(); // The size of the data that will be copied is the sixth element on the stack. - let size = try_or_return!(interpreter.stack().peek(6)).saturating_to::(); + let size = try_or_return!(interpreter.stack.peek(6)).saturating_to::(); // If none of the allowed ranges contain [dest_offset, dest_offset + size), // memory outside of the expected ranges has been touched. If the opcode @@ -2210,11 +2223,11 @@ impl Cheatcodes { // SPECIAL CASE: When a call to `stopExpectSafeMemory` is performed, this is allowed. // It allocated calldata at the current free memory pointer, and will attempt to read // from this memory region to perform the call. - let to = Address::from_word(try_or_return!(interpreter.stack().peek(1)).to_be_bytes::<32>().into()); + let to = Address::from_word(try_or_return!(interpreter.stack.peek(1)).to_be_bytes::<32>().into()); if to == CHEATCODE_ADDRESS { - let args_offset = try_or_return!(interpreter.stack().peek(3)).saturating_to::(); - let args_size = try_or_return!(interpreter.stack().peek(4)).saturating_to::(); - let memory_word = interpreter.shared_memory.slice(args_offset, args_size); + let args_offset = try_or_return!(interpreter.stack.peek(3)).saturating_to::(); + let args_size = try_or_return!(interpreter.stack.peek(4)).saturating_to::(); + let memory_word = interpreter.memory.slice_len(args_offset, args_size); if memory_word[..SELECTOR_LEN] == stopExpectSafeMemoryCall::SELECTOR { return } @@ -2227,10 +2240,10 @@ impl Cheatcodes { $(op::$opcode => { // The destination offset of the operation. - let dest_offset = try_or_return!(interpreter.stack().peek($offset_depth)).saturating_to::(); + let dest_offset = try_or_return!(interpreter.stack.peek($offset_depth)).saturating_to::(); // The size of the data that will be copied. - let size = try_or_return!(interpreter.stack().peek($size_depth)).saturating_to::(); + let size = try_or_return!(interpreter.stack.peek($size_depth)).saturating_to::(); // If none of the allowed ranges contain [dest_offset, dest_offset + size), // memory outside of the expected ranges has been touched. If the opcode @@ -2240,7 +2253,7 @@ impl Cheatcodes { range.contains(&(dest_offset + size.saturating_sub(1))) }) && ($writes || [dest_offset, (dest_offset + size).saturating_sub(1)].into_iter().any(|offset| { - offset >= interpreter.shared_memory.len() as u64 + offset >= interpreter.memory.size() as u64 }) ); @@ -2299,11 +2312,11 @@ fn disallowed_mem_write( ranges.iter().map(|r| format!("(0x{:02X}, 0x{:02X}]", r.start, r.end)).join(" U ") ); - interpreter.instruction_result = InstructionResult::Revert; - interpreter.next_action = InterpreterAction::Return { + interpreter.control.instruction_result = InstructionResult::Revert; + interpreter.control.next_action = InterpreterAction::Return { result: InterpreterResult { output: Error::encode(revert_string), - gas: interpreter.gas, + gas: interpreter.control.gas, result: InstructionResult::Revert, }, }; @@ -2311,13 +2324,13 @@ fn disallowed_mem_write( // Determines if the gas limit on a given call was manually set in the script and should therefore // not be overwritten by later estimations -fn check_if_fixed_gas_limit(ecx: InnerEcx, call_gas_limit: u64) -> bool { +fn check_if_fixed_gas_limit(ecx: &Ecx, call_gas_limit: u64) -> bool { // If the gas limit was not set in the source code it is set to the estimated gas left at the // time of the call, which should be rather close to configured gas limit. // TODO: Find a way to reliably make this determination. // For example by generating it in the compilation or EVM simulation process - U256::from(ecx.env.tx.gas_limit) > ecx.env.block.gas_limit && - U256::from(call_gas_limit) <= ecx.env.block.gas_limit + ecx.tx.gas_limit > ecx.block.gas_limit && + call_gas_limit <= ecx.block.gas_limit // Transfers in forge scripts seem to be estimated at 2300 by revm leading to "Intrinsic // gas too low" failure when simulated on chain && call_gas_limit > 2300 diff --git a/crates/cheatcodes/src/inspector/utils.rs b/crates/cheatcodes/src/inspector/utils.rs index a0d7820aa3e27..58d1f2f90d7b7 100644 --- a/crates/cheatcodes/src/inspector/utils.rs +++ b/crates/cheatcodes/src/inspector/utils.rs @@ -1,7 +1,7 @@ -use super::InnerEcx; +use super::Ecx; use crate::inspector::Cheatcodes; use alloy_primitives::{Address, Bytes, U256}; -use revm::interpreter::{CreateInputs, CreateScheme, EOFCreateInputs, EOFCreateKind}; +use revm::interpreter::{CreateInputs, CreateScheme}; /// Common behaviour of legacy and EOF create inputs. pub(crate) trait CommonCreateInput { @@ -12,8 +12,7 @@ pub(crate) trait CommonCreateInput { fn scheme(&self) -> Option; fn set_caller(&mut self, caller: Address); fn log_debug(&self, cheatcode: &mut Cheatcodes, scheme: &CreateScheme); - fn allow_cheatcodes(&self, cheatcodes: &mut Cheatcodes, ecx: InnerEcx) -> Address; - fn computed_created_address(&self) -> Option
; + fn allow_cheatcodes(&self, cheatcodes: &mut Cheatcodes, ecx: Ecx) -> Address; } impl CommonCreateInput for &mut CreateInputs { @@ -39,10 +38,11 @@ impl CommonCreateInput for &mut CreateInputs { let kind = match scheme { CreateScheme::Create => "create", CreateScheme::Create2 { .. } => "create2", + CreateScheme::Custom { .. } => "custom", }; debug!(target: "cheatcodes", tx=?cheatcode.broadcastable_transactions.back().unwrap(), "broadcastable {kind}"); } - fn allow_cheatcodes(&self, cheatcodes: &mut Cheatcodes, ecx: InnerEcx) -> Address { + fn allow_cheatcodes(&self, cheatcodes: &mut Cheatcodes, ecx: Ecx) -> Address { let old_nonce = ecx .journaled_state .state @@ -53,44 +53,4 @@ impl CommonCreateInput for &mut CreateInputs { cheatcodes.allow_cheatcodes_on_create(ecx, self.caller, created_address); created_address } - fn computed_created_address(&self) -> Option
{ - None - } -} - -impl CommonCreateInput for &mut EOFCreateInputs { - fn caller(&self) -> Address { - self.caller - } - fn gas_limit(&self) -> u64 { - self.gas_limit - } - fn value(&self) -> U256 { - self.value - } - fn init_code(&self) -> Bytes { - match &self.kind { - EOFCreateKind::Tx { initdata } => initdata.clone(), - EOFCreateKind::Opcode { initcode, .. } => initcode.raw.clone(), - } - } - fn scheme(&self) -> Option { - None - } - fn set_caller(&mut self, caller: Address) { - self.caller = caller; - } - fn log_debug(&self, cheatcode: &mut Cheatcodes, _scheme: &CreateScheme) { - debug!(target: "cheatcodes", tx=?cheatcode.broadcastable_transactions.back().unwrap(), "broadcastable eofcreate"); - } - fn allow_cheatcodes(&self, cheatcodes: &mut Cheatcodes, ecx: InnerEcx) -> Address { - let created_address = - <&mut EOFCreateInputs as CommonCreateInput>::computed_created_address(self) - .unwrap_or_default(); - cheatcodes.allow_cheatcodes_on_create(ecx, self.caller, created_address); - created_address - } - fn computed_created_address(&self) -> Option
{ - self.kind.created_address().copied() - } } diff --git a/crates/cheatcodes/src/lib.rs b/crates/cheatcodes/src/lib.rs index 732f55d7e2533..ca45a937b0b63 100644 --- a/crates/cheatcodes/src/lib.rs +++ b/crates/cheatcodes/src/lib.rs @@ -15,15 +15,15 @@ pub extern crate foundry_cheatcodes_spec as spec; #[macro_use] extern crate tracing; +use alloy_evm::eth::EthEvmContext; use alloy_primitives::Address; use foundry_evm_core::backend::DatabaseExt; -use revm::{ContextPrecompiles, InnerEvmContext}; use spec::Status; pub use config::CheatsConfig; pub use error::{Error, ErrorKind, Result}; pub use inspector::{ - BroadcastableTransaction, BroadcastableTransactions, Cheatcodes, CheatcodesExecutor, Context, + BroadcastableTransaction, BroadcastableTransactions, Cheatcodes, CheatcodesExecutor, }; pub use spec::{CheatcodeDef, Vm}; pub use Vm::ForgeContext; @@ -138,9 +138,7 @@ pub struct CheatsCtxt<'cheats, 'evm, 'db, 'db2> { /// The cheatcodes inspector state. pub(crate) state: &'cheats mut Cheatcodes, /// The EVM data. - pub(crate) ecx: &'evm mut InnerEvmContext<&'db mut (dyn DatabaseExt + 'db2)>, - /// The precompiles context. - pub(crate) precompiles: &'evm mut ContextPrecompiles<&'db mut (dyn DatabaseExt + 'db2)>, + pub(crate) ecx: &'evm mut EthEvmContext<&'db mut (dyn DatabaseExt + 'db2)>, /// The original `msg.sender`. pub(crate) caller: Address, /// Gas limit of the current cheatcode call. @@ -148,7 +146,7 @@ pub struct CheatsCtxt<'cheats, 'evm, 'db, 'db2> { } impl<'db, 'db2> std::ops::Deref for CheatsCtxt<'_, '_, 'db, 'db2> { - type Target = InnerEvmContext<&'db mut (dyn DatabaseExt + 'db2)>; + type Target = EthEvmContext<&'db mut (dyn DatabaseExt + 'db2)>; #[inline(always)] fn deref(&self) -> &Self::Target { @@ -166,6 +164,6 @@ impl std::ops::DerefMut for CheatsCtxt<'_, '_, '_, '_> { impl CheatsCtxt<'_, '_, '_, '_> { #[inline] pub(crate) fn is_precompile(&self, address: &Address) -> bool { - self.precompiles.contains(address) + self.ecx.journaled_state.inner.precompiles.contains(address) } } diff --git a/crates/cheatcodes/src/script.rs b/crates/cheatcodes/src/script.rs index dc80b7021ab04..6e2fad1f09213 100644 --- a/crates/cheatcodes/src/script.rs +++ b/crates/cheatcodes/src/script.rs @@ -9,7 +9,12 @@ use alloy_signer_local::PrivateKeySigner; use alloy_sol_types::SolValue; use foundry_wallets::{multi_wallet::MultiWallet, WalletSigner}; use parking_lot::Mutex; -use revm::primitives::{Bytecode, SignedAuthorization, SpecId}; +use revm::{ + bytecode::Bytecode, + context::JournalTr, + context_interface::transaction::SignedAuthorization, + primitives::{hardfork::SpecId, KECCAK_EMPTY}, +}; use std::sync::Arc; impl Cheatcode for broadcast_0Call { @@ -33,56 +38,85 @@ impl Cheatcode for broadcast_2Call { } } -impl Cheatcode for attachDelegationCall { +impl Cheatcode for attachDelegation_0Call { fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { signedDelegation } = self; - let SignedDelegation { v, r, s, nonce, implementation } = signedDelegation; + attach_delegation(ccx, signedDelegation, false) + } +} - let auth = Authorization { - address: *implementation, - nonce: *nonce, - chain_id: U256::from(ccx.ecx.env.cfg.chain_id), - }; - let signed_auth = SignedAuthorization::new_unchecked( - auth, - *v, - U256::from_be_bytes(r.0), - U256::from_be_bytes(s.0), - ); - write_delegation(ccx, signed_auth.clone())?; - ccx.state.active_delegation = Some(signed_auth); - Ok(Default::default()) +impl Cheatcode for attachDelegation_1Call { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + let Self { signedDelegation, crossChain } = self; + attach_delegation(ccx, signedDelegation, *crossChain) } } impl Cheatcode for signDelegation_0Call { fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { implementation, privateKey } = *self; - sign_delegation(ccx, privateKey, implementation, None, false) + sign_delegation(ccx, privateKey, implementation, None, false, false) } } impl Cheatcode for signDelegation_1Call { fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { implementation, privateKey, nonce } = *self; - sign_delegation(ccx, privateKey, implementation, Some(nonce), false) + sign_delegation(ccx, privateKey, implementation, Some(nonce), false, false) + } +} + +impl Cheatcode for signDelegation_2Call { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + let Self { implementation, privateKey, crossChain } = *self; + sign_delegation(ccx, privateKey, implementation, None, crossChain, false) } } impl Cheatcode for signAndAttachDelegation_0Call { fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { implementation, privateKey } = *self; - sign_delegation(ccx, privateKey, implementation, None, true) + sign_delegation(ccx, privateKey, implementation, None, false, true) } } impl Cheatcode for signAndAttachDelegation_1Call { fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { implementation, privateKey, nonce } = *self; - sign_delegation(ccx, privateKey, implementation, Some(nonce), true) + sign_delegation(ccx, privateKey, implementation, Some(nonce), false, true) } } +impl Cheatcode for signAndAttachDelegation_2Call { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + let Self { implementation, privateKey, crossChain } = *self; + sign_delegation(ccx, privateKey, implementation, None, crossChain, true) + } +} + +/// Helper function to attach an EIP-7702 delegation. +fn attach_delegation( + ccx: &mut CheatsCtxt, + delegation: &SignedDelegation, + cross_chain: bool, +) -> Result { + let SignedDelegation { v, r, s, nonce, implementation } = delegation; + // Set chain id to 0 if universal deployment is preferred. + // See https://github.com/ethereum/EIPs/blob/master/EIPS/eip-7702.md#protection-from-malleability-cross-chain + let chain_id = if cross_chain { U256::from(0) } else { U256::from(ccx.ecx.cfg.chain_id) }; + + let auth = Authorization { address: *implementation, nonce: *nonce, chain_id }; + let signed_auth = SignedAuthorization::new_unchecked( + auth, + *v, + U256::from_be_bytes(r.0), + U256::from_be_bytes(s.0), + ); + write_delegation(ccx, signed_auth.clone())?; + ccx.state.add_delegation(signed_auth); + Ok(Default::default()) +} + /// Helper function to sign and attach (if needed) an EIP-7702 delegation. /// Uses the provided nonce, otherwise retrieves and increments the nonce of the EOA. fn sign_delegation( @@ -90,27 +124,31 @@ fn sign_delegation( private_key: Uint<256, 4>, implementation: Address, nonce: Option, + cross_chain: bool, attach: bool, ) -> Result> { let signer = PrivateKeySigner::from_bytes(&B256::from(private_key))?; let nonce = if let Some(nonce) = nonce { nonce } else { - let authority_acc = - ccx.ecx.journaled_state.load_account(signer.address(), &mut ccx.ecx.db)?; - authority_acc.data.info.nonce - }; - let auth = Authorization { - address: implementation, - nonce, - chain_id: U256::from(ccx.ecx.env.cfg.chain_id), + let authority_acc = ccx.ecx.journaled_state.load_account(signer.address())?; + // Calculate next nonce considering existing active delegations + next_delegation_nonce( + &ccx.state.active_delegations, + signer.address(), + &ccx.state.broadcast, + authority_acc.data.info.nonce, + ) }; + let chain_id = if cross_chain { U256::from(0) } else { U256::from(ccx.ecx.cfg.chain_id) }; + + let auth = Authorization { address: implementation, nonce, chain_id }; let sig = signer.sign_hash_sync(&auth.signature_hash())?; // Attach delegation. if attach { let signed_auth = SignedAuthorization::new_unchecked(auth, sig.v() as u8, sig.r(), sig.s()); write_delegation(ccx, signed_auth.clone())?; - ccx.state.active_delegation = Some(signed_auth); + ccx.state.add_delegation(signed_auth); } Ok(SignedDelegation { v: sig.v() as u8, @@ -122,15 +160,62 @@ fn sign_delegation( .abi_encode()) } +/// Returns the next valid nonce for a delegation, considering existing active delegations. +fn next_delegation_nonce( + active_delegations: &[SignedAuthorization], + authority: Address, + broadcast: &Option, + account_nonce: u64, +) -> u64 { + match active_delegations + .iter() + .rfind(|auth| auth.recover_authority().is_ok_and(|recovered| recovered == authority)) + { + Some(auth) => { + // Increment nonce of last recorded delegation. + auth.nonce + 1 + } + None => { + // First time a delegation is added for this authority. + if let Some(broadcast) = broadcast { + // Increment nonce if authority is the sender of transaction. + if broadcast.new_origin == authority { + return account_nonce + 1 + } + } + // Return current nonce if authority is not the sender of transaction. + account_nonce + } + } +} + fn write_delegation(ccx: &mut CheatsCtxt, auth: SignedAuthorization) -> Result<()> { let authority = auth.recover_authority().map_err(|e| format!("{e}"))?; - let authority_acc = ccx.ecx.journaled_state.load_account(authority, &mut ccx.ecx.db)?; - if authority_acc.data.info.nonce != auth.nonce { - return Err("invalid nonce".into()); + let authority_acc = ccx.ecx.journaled_state.load_account(authority)?; + + let expected_nonce = next_delegation_nonce( + &ccx.state.active_delegations, + authority, + &ccx.state.broadcast, + authority_acc.data.info.nonce, + ); + + if expected_nonce != auth.nonce { + return Err(format!( + "invalid nonce for {authority:?}: expected {expected_nonce}, got {}", + auth.nonce + ) + .into()); + } + + if auth.address.is_zero() { + // Set empty code if the delegation address of authority is 0x. + // See https://github.com/ethereum/EIPs/blob/master/EIPS/eip-7702.md#behavior. + ccx.ecx.journaled_state.set_code_with_hash(authority, Bytecode::default(), KECCAK_EMPTY); + } else { + let bytecode = Bytecode::new_eip7702(*auth.address()); + ccx.ecx.journaled_state.set_code(authority, bytecode); } - authority_acc.data.info.nonce += 1; - let bytecode = Bytecode::new_eip7702(*auth.address()); - ccx.ecx.journaled_state.set_code(authority, bytecode); Ok(()) } @@ -138,7 +223,7 @@ impl Cheatcode for attachBlobCall { fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { blob } = self; ensure!( - ccx.ecx.spec_id() >= SpecId::CANCUN, + ccx.ecx.cfg.spec >= SpecId::CANCUN, "`attachBlob` is not supported before the Cancun hard fork; \ see EIP-4844: https://eips.ethereum.org/EIPS/eip-4844" ); @@ -197,7 +282,7 @@ pub struct Broadcast { /// Original `tx.origin` pub original_origin: Address, /// Depth of the broadcast - pub depth: u64, + pub depth: usize, /// Whether the prank stops by itself after the next call pub single_call: bool, } @@ -246,7 +331,7 @@ impl Wallets { /// Locks inner Mutex and returns all signer addresses in the [MultiWallet]. pub fn signers(&self) -> Result> { - Ok(self.inner.lock().multi_wallet.signers()?.keys().cloned().collect()) + Ok(self.inner.lock().multi_wallet.signers()?.keys().copied().collect()) } /// Number of signers in the [MultiWallet]. @@ -274,7 +359,7 @@ fn broadcast(ccx: &mut CheatsCtxt, new_origin: Option<&Address>, single_call: bo ); ensure!(ccx.state.broadcast.is_none(), "a broadcast is active already"); - let mut new_origin = new_origin.cloned(); + let mut new_origin = new_origin.copied(); if new_origin.is_none() { let mut wallets = ccx.state.wallets().inner.lock(); @@ -290,9 +375,9 @@ fn broadcast(ccx: &mut CheatsCtxt, new_origin: Option<&Address>, single_call: bo } let broadcast = Broadcast { - new_origin: new_origin.unwrap_or(ccx.ecx.env.tx.caller), + new_origin: new_origin.unwrap_or(ccx.ecx.tx.caller), original_caller: ccx.caller, - original_origin: ccx.ecx.env.tx.caller, + original_origin: ccx.ecx.tx.caller, depth, single_call, }; diff --git a/crates/cheatcodes/src/test.rs b/crates/cheatcodes/src/test.rs index 9a59430a9264c..683c5131bcddf 100644 --- a/crates/cheatcodes/src/test.rs +++ b/crates/cheatcodes/src/test.rs @@ -78,7 +78,7 @@ impl Cheatcode for skip_1Call { if *skipTest { // Skip should not work if called deeper than at test level. // Since we're not returning the magic skip bytes, this will cause a test failure. - ensure!(ccx.ecx.journaled_state.depth() <= 1, "`skip` can only be used at test level"); + ensure!(ccx.ecx.journaled_state.depth <= 1, "`skip` can only be used at test level"); Err([MAGIC_SKIP, reason.as_bytes()].concat().into()) } else { Ok(Default::default()) @@ -124,24 +124,29 @@ fn get_chain(state: &mut Cheatcodes, chain_alias: &str) -> Result { // Parse the chain alias - works for both chain names and IDs let alloy_chain = AlloyChain::from_str(chain_alias) .map_err(|_| fmt_err!("invalid chain alias: {chain_alias}"))?; + let chain_name = alloy_chain.to_string(); + let chain_id = alloy_chain.id(); // Check if this is an unknown chain ID by comparing the name to the chain ID // When a numeric ID is passed for an unknown chain, alloy_chain.to_string() will return the ID // So if they match, it's likely an unknown chain ID - if alloy_chain.to_string() == alloy_chain.id().to_string() { + if chain_name == chain_id.to_string() { return Err(fmt_err!("invalid chain alias: {chain_alias}")); } - // First, try to get RPC URL from the user's config in foundry.toml - let rpc_url = state.config.rpc_endpoint(chain_alias).ok().and_then(|e| e.url().ok()); - - // If we couldn't get a URL from config, return an empty string - let rpc_url = rpc_url.unwrap_or_default(); + // Try to retrieve RPC URL and chain alias from user's config in foundry.toml. + let (rpc_url, chain_alias) = if let Some(rpc_url) = + state.config.rpc_endpoint(&chain_name).ok().and_then(|e| e.url().ok()) + { + (rpc_url, chain_name.clone()) + } else { + (String::new(), chain_alias.to_string()) + }; let chain_struct = Chain { - name: alloy_chain.to_string(), - chainId: U256::from(alloy_chain.id()), - chainAlias: chain_alias.to_string(), + name: chain_name, + chainId: U256::from(chain_id), + chainAlias: chain_alias, rpcUrl: rpc_url, }; diff --git a/crates/cheatcodes/src/test/assert.rs b/crates/cheatcodes/src/test/assert.rs index a61cc4b2a2ece..7bb433c88259f 100644 --- a/crates/cheatcodes/src/test/assert.rs +++ b/crates/cheatcodes/src/test/assert.rs @@ -6,6 +6,7 @@ use foundry_evm_core::{ constants::CHEATCODE_ADDRESS, }; use itertools::Itertools; +use revm::context::JournalTr; use std::fmt::{Debug, Display}; const EQ_REL_DELTA_RESOLUTION: U256 = U256::from_limbs([18, 0, 0, 0]); @@ -190,7 +191,11 @@ fn handle_assertion_result( Err(msg.into()) } else { executor.console_log(ccx, &msg); - ccx.ecx.sstore(CHEATCODE_ADDRESS, GLOBAL_FAIL_SLOT, U256::from(1))?; + ccx.ecx.journaled_state.sstore( + CHEATCODE_ADDRESS, + GLOBAL_FAIL_SLOT, + U256::from(1), + )?; Ok(Default::default()) } } diff --git a/crates/cheatcodes/src/test/assume.rs b/crates/cheatcodes/src/test/assume.rs index 74bd79e0964b6..6cdf3e621aece 100644 --- a/crates/cheatcodes/src/test/assume.rs +++ b/crates/cheatcodes/src/test/assume.rs @@ -9,7 +9,7 @@ use std::fmt::Debug; #[derive(Clone, Debug)] pub struct AssumeNoRevert { /// The call depth at which the cheatcode was added. - pub depth: u64, + pub depth: usize, /// Acceptable revert parameters for the next call, to be thrown out if they are encountered; /// reverts with parameters not specified here will count as normal reverts and not rejects /// towards the counter. @@ -56,7 +56,7 @@ impl Cheatcode for assumeCall { impl Cheatcode for assumeNoRevert_0Call { fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { - assume_no_revert(ccx.state, ccx.ecx.journaled_state.depth(), vec![]) + assume_no_revert(ccx.state, ccx.ecx.journaled_state.depth, vec![]) } } @@ -65,7 +65,7 @@ impl Cheatcode for assumeNoRevert_1Call { let Self { potentialRevert } = self; assume_no_revert( ccx.state, - ccx.ecx.journaled_state.depth(), + ccx.ecx.journaled_state.depth, vec![AcceptableRevertParameters::from(potentialRevert)], ) } @@ -76,7 +76,7 @@ impl Cheatcode for assumeNoRevert_2Call { let Self { potentialReverts } = self; assume_no_revert( ccx.state, - ccx.ecx.journaled_state.depth(), + ccx.ecx.journaled_state.depth, potentialReverts.iter().map(AcceptableRevertParameters::from).collect(), ) } @@ -84,7 +84,7 @@ impl Cheatcode for assumeNoRevert_2Call { fn assume_no_revert( state: &mut Cheatcodes, - depth: u64, + depth: usize, parameters: Vec, ) -> Result { ensure!( diff --git a/crates/cheatcodes/src/test/expect.rs b/crates/cheatcodes/src/test/expect.rs index 9c3d79a3767bd..1917331de54de 100644 --- a/crates/cheatcodes/src/test/expect.rs +++ b/crates/cheatcodes/src/test/expect.rs @@ -8,7 +8,10 @@ use alloy_primitives::{ map::{hash_map::Entry, AddressHashMap, HashMap}, Address, Bytes, LogData as RawLog, U256, }; -use revm::interpreter::{InstructionResult, Interpreter, InterpreterAction, InterpreterResult}; +use revm::{ + context::JournalTr, + interpreter::{InstructionResult, Interpreter, InterpreterAction, InterpreterResult}, +}; use super::revert_handlers::RevertParameters; /// Tracks the expected calls per address. @@ -66,7 +69,7 @@ pub struct ExpectedRevert { /// The expected data returned by the revert, None being any. pub reason: Option>, /// The depth at which the revert is expected. - pub depth: u64, + pub depth: usize, /// The type of expected revert. pub kind: ExpectedRevertKind, /// If true then only the first 4 bytes of expected data returned by the revert are checked. @@ -76,7 +79,7 @@ pub struct ExpectedRevert { /// Address that reverted the call. pub reverted_by: Option
, /// Max call depth reached during next call execution. - pub max_depth: u64, + pub max_depth: usize, /// Number of times this revert is expected. pub count: u64, /// Actual number of times this revert has been seen. @@ -86,7 +89,7 @@ pub struct ExpectedRevert { #[derive(Clone, Debug)] pub struct ExpectedEmit { /// The depth at which we expect this emit to have occurred - pub depth: u64, + pub depth: usize, /// The log we expect pub log: Option, /// The checks to perform: @@ -132,12 +135,21 @@ impl Display for CreateScheme { } } +impl From for CreateScheme { + fn from(scheme: revm::context_interface::CreateScheme) -> Self { + match scheme { + revm::context_interface::CreateScheme::Create => Self::Create, + revm::context_interface::CreateScheme::Create2 { .. } => Self::Create2, + _ => unimplemented!("Unsupported create scheme"), + } + } +} + impl CreateScheme { - pub fn eq(&self, create_scheme: revm::primitives::CreateScheme) -> bool { + pub fn eq(&self, create_scheme: Self) -> bool { matches!( (self, create_scheme), - (Self::Create, revm::primitives::CreateScheme::Create) | - (Self::Create2, revm::primitives::CreateScheme::Create2 { .. }) + (Self::Create, Self::Create) | (Self::Create2, Self::Create2 { .. }) ) } } @@ -623,14 +635,14 @@ impl Cheatcode for _expectCheatcodeRevert_2Call { impl Cheatcode for expectSafeMemoryCall { fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { min, max } = *self; - expect_safe_memory(ccx.state, min, max, ccx.ecx.journaled_state.depth()) + expect_safe_memory(ccx.state, min, max, ccx.ecx.journaled_state.depth().try_into()?) } } impl Cheatcode for stopExpectSafeMemoryCall { fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self {} = self; - ccx.state.allowed_mem_writes.remove(&ccx.ecx.journaled_state.depth()); + ccx.state.allowed_mem_writes.remove(&ccx.ecx.journaled_state.depth().try_into()?); Ok(Default::default()) } } @@ -638,7 +650,7 @@ impl Cheatcode for stopExpectSafeMemoryCall { impl Cheatcode for expectSafeMemoryCallCall { fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { min, max } = *self; - expect_safe_memory(ccx.state, min, max, ccx.ecx.journaled_state.depth() + 1) + expect_safe_memory(ccx.state, min, max, (ccx.ecx.journaled_state.depth() + 1).try_into()?) } } @@ -742,7 +754,7 @@ fn expect_call( fn expect_emit( state: &mut Cheatcodes, - depth: u64, + depth: usize, checks: [bool; 5], address: Option
, anonymous: bool, @@ -812,11 +824,11 @@ pub(crate) fn handle_expect_emit( .expected_emits .insert(index_to_fill_or_check, (event_to_fill_or_check, count_map)); } else { - interpreter.instruction_result = InstructionResult::Revert; - interpreter.next_action = InterpreterAction::Return { + interpreter.control.instruction_result = InstructionResult::Revert; + interpreter.control.next_action = InterpreterAction::Return { result: InterpreterResult { output: Error::encode("use vm.expectEmitAnonymous to match anonymous events"), - gas: interpreter.gas, + gas: interpreter.control.gas, result: InstructionResult::Revert, }, }; @@ -956,7 +968,7 @@ fn expect_create( fn expect_revert( state: &mut Cheatcodes, reason: Option<&[u8]>, - depth: u64, + depth: usize, cheatcode: bool, partial_match: bool, reverter: Option
, diff --git a/crates/cheatcodes/src/test/revert_handlers.rs b/crates/cheatcodes/src/test/revert_handlers.rs index 2dfd4b014fa6f..92026b9a86697 100644 --- a/crates/cheatcodes/src/test/revert_handlers.rs +++ b/crates/cheatcodes/src/test/revert_handlers.rs @@ -23,7 +23,7 @@ static DUMMY_CALL_OUTPUT: Bytes = Bytes::from_static(&[0u8; 8192]); const DUMMY_CREATE_ADDRESS: Address = address!("0x0000000000000000000000000000000000000001"); fn stringify(data: &[u8]) -> String { - if let Ok(s) = String::abi_decode(data, true) { + if let Ok(s) = String::abi_decode(data) { return s; } if data.is_ascii() { @@ -240,7 +240,7 @@ fn decode_revert(revert: Vec) -> Vec { revert.get(..4).map(|s| s.try_into().unwrap()), Some(Vm::CheatcodeError::SELECTOR | alloy_sol_types::Revert::SELECTOR) ) { - if let Ok(decoded) = Vec::::abi_decode(&revert[4..], false) { + if let Ok(decoded) = Vec::::abi_decode(&revert[4..]) { return decoded; } } diff --git a/crates/cheatcodes/src/utils.rs b/crates/cheatcodes/src/utils.rs index 4735ffaa41087..a5d2e1b47ee9f 100644 --- a/crates/cheatcodes/src/utils.rs +++ b/crates/cheatcodes/src/utils.rs @@ -1,13 +1,17 @@ //! Implementations of [`Utilities`](spec::Group::Utilities) cheatcodes. use crate::{Cheatcode, Cheatcodes, CheatcodesExecutor, CheatsCtxt, Result, Vm::*}; -use alloy_dyn_abi::{DynSolType, DynSolValue}; -use alloy_primitives::{aliases::B32, map::HashMap, B64, U256}; +use alloy_dyn_abi::{eip712_parser::EncodeType, DynSolType, DynSolValue, Resolver, TypedData}; +use alloy_ens::namehash; +use alloy_primitives::{aliases::B32, keccak256, map::HashMap, Bytes, B64, U256}; use alloy_sol_types::SolValue; -use foundry_common::ens::namehash; +use foundry_common::{fs, TYPE_BINDING_PREFIX}; +use foundry_config::fs_permissions::FsAccessKind; use foundry_evm_core::constants::DEFAULT_CREATE2_DEPLOYER; use proptest::prelude::Strategy; use rand::{seq::SliceRandom, Rng, RngCore}; +use revm::context::JournalTr; +use std::path::PathBuf; /// Contains locations of traces ignored via cheatcodes. /// @@ -115,7 +119,7 @@ impl Cheatcode for randomInt_1Call { impl Cheatcode for randomBoolCall { fn apply(&self, state: &mut Cheatcodes) -> Result { - let rand_bool: bool = state.rng().gen(); + let rand_bool: bool = state.rng().random(); Ok(rand_bool.abi_encode()) } } @@ -232,9 +236,9 @@ impl Cheatcode for copyStorageCall { "target address cannot have arbitrary storage" ); - if let Ok(from_account) = ccx.load_account(*from) { + if let Ok(from_account) = ccx.ecx.journaled_state.load_account(*from) { let from_storage = from_account.storage.clone(); - if let Ok(mut to_account) = ccx.load_account(*to) { + if let Ok(mut to_account) = ccx.ecx.journaled_state.load_account(*to) { to_account.storage = from_storage; if let Some(ref mut arbitrary_storage) = &mut ccx.state.arbitrary_storage { arbitrary_storage.mark_copy(from, to); @@ -269,6 +273,14 @@ impl Cheatcode for shuffleCall { } } +impl Cheatcode for setSeedCall { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + let Self { seed } = self; + ccx.state.set_seed(*seed); + Ok(Default::default()) + } +} + /// Helper to generate a random `uint` value (with given bits or bounded if specified) /// from type strategy. fn random_uint(state: &mut Cheatcodes, bits: Option, bounds: Option<(U256, U256)>) -> Result { @@ -286,7 +298,7 @@ fn random_uint(state: &mut Cheatcodes, bits: Option, bounds: Option<(U256, ensure!(min <= max, "min must be less than or equal to max"); // Generate random between range min..=max let exclusive_modulo = max - min; - let mut random_number: U256 = state.rng().gen(); + let mut random_number: U256 = state.rng().random(); if exclusive_modulo != U256::MAX { let inclusive_modulo = exclusive_modulo + U256::from(1); random_number %= inclusive_modulo; @@ -313,3 +325,148 @@ fn random_int(state: &mut Cheatcodes, bits: Option) -> Result { .current() .abi_encode()) } + +impl Cheatcode for eip712HashType_0Call { + fn apply(&self, state: &mut Cheatcodes) -> Result { + let Self { typeNameOrDefinition } = self; + + let type_def = get_canonical_type_def(typeNameOrDefinition, state, None)?; + + Ok(keccak256(type_def.as_bytes()).to_vec()) + } +} + +impl Cheatcode for eip712HashType_1Call { + fn apply(&self, state: &mut Cheatcodes) -> Result { + let Self { bindingsPath, typeName } = self; + + let path = state.config.ensure_path_allowed(bindingsPath, FsAccessKind::Read)?; + let type_def = get_type_def_from_bindings(typeName, path, &state.config.root)?; + + Ok(keccak256(type_def.as_bytes()).to_vec()) + } +} + +impl Cheatcode for eip712HashStruct_0Call { + fn apply(&self, state: &mut Cheatcodes) -> Result { + let Self { typeNameOrDefinition, abiEncodedData } = self; + + let type_def = get_canonical_type_def(typeNameOrDefinition, state, None)?; + let primary = &type_def[..type_def.find('(').unwrap_or(type_def.len())]; + + get_struct_hash(primary, &type_def, abiEncodedData) + } +} + +impl Cheatcode for eip712HashStruct_1Call { + fn apply(&self, state: &mut Cheatcodes) -> Result { + let Self { bindingsPath, typeName, abiEncodedData } = self; + + let path = state.config.ensure_path_allowed(bindingsPath, FsAccessKind::Read)?; + let type_def = get_type_def_from_bindings(typeName, path, &state.config.root)?; + + get_struct_hash(typeName, &type_def, abiEncodedData) + } +} + +impl Cheatcode for eip712HashTypedDataCall { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { jsonData } = self; + let typed_data: TypedData = serde_json::from_str(jsonData)?; + let digest = typed_data.eip712_signing_hash()?; + + Ok(digest.to_vec()) + } +} + +/// Returns EIP-712 canonical type definition from the provided string type representation or type +/// name. If type name provided, then it looks up bindings from file generated by `forge bind-json`. +fn get_canonical_type_def( + name_or_def: &String, + state: &mut Cheatcodes, + path: Option, +) -> Result { + let type_def = if name_or_def.contains('(') { + // If the input contains '(', it must be the type definition. + EncodeType::parse(name_or_def).and_then(|parsed| parsed.canonicalize())? + } else { + // Otherwise, it must be the type name. + let path = path.as_ref().unwrap_or(&state.config.bind_json_path); + let path = state.config.ensure_path_allowed(path, FsAccessKind::Read)?; + get_type_def_from_bindings(name_or_def, path, &state.config.root)? + }; + + Ok(type_def) +} + +/// Returns the EIP-712 type definition from the bindings in the provided path. +/// Assumes that read validation for the path has already been checked. +fn get_type_def_from_bindings(name: &String, path: PathBuf, root: &PathBuf) -> Result { + let content = fs::read_to_string(&path)?; + + let type_defs: HashMap<&str, &str> = content + .lines() + .filter_map(|line| { + let relevant = line.trim().strip_prefix(TYPE_BINDING_PREFIX)?; + let (name, def) = relevant.split_once('=')?; + Some((name.trim(), def.trim().strip_prefix('"')?.strip_suffix("\";")?)) + }) + .collect(); + + match type_defs.get(name.as_str()) { + Some(value) => Ok(value.to_string()), + None => { + let bindings = + type_defs.keys().map(|k| format!(" - {k}")).collect::>().join("\n"); + + bail!( + "'{}' not found in '{}'.{}", + name, + path.strip_prefix(root).unwrap_or(&path).to_string_lossy(), + if bindings.is_empty() { + String::new() + } else { + format!("\nAvailable bindings:\n{bindings}\n") + } + ); + } + } +} + +/// Returns the EIP-712 struct hash for provided name, definition and ABI encoded data. +fn get_struct_hash(primary: &str, type_def: &String, abi_encoded_data: &Bytes) -> Result { + let mut resolver = Resolver::default(); + + // Populate the resolver by ingesting the canonical type definition, and then get the + // corresponding `DynSolType` of the primary type. + resolver + .ingest_string(type_def) + .map_err(|e| fmt_err!("Resolver failed to ingest type definition: {e}"))?; + + let resolved_sol_type = resolver + .resolve(primary) + .map_err(|e| fmt_err!("Failed to resolve EIP-712 primary type '{primary}': {e}"))?; + + // ABI-decode the bytes into `DynSolValue::CustomStruct`. + let sol_value = resolved_sol_type.abi_decode(abi_encoded_data.as_ref()).map_err(|e| { + fmt_err!("Failed to ABI decode using resolved_sol_type directly for '{primary}': {e}.") + })?; + + // Use the resolver to properly encode the data. + let encoded_data: Vec = resolver + .encode_data(&sol_value) + .map_err(|e| fmt_err!("Failed to EIP-712 encode data for struct '{primary}': {e}"))? + .ok_or_else(|| fmt_err!("EIP-712 data encoding returned 'None' for struct '{primary}'"))?; + + // Compute the type hash of the primary type. + let type_hash = resolver + .type_hash(primary) + .map_err(|e| fmt_err!("Failed to compute typeHash for EIP712 type '{primary}': {e}"))?; + + // Compute the struct hash of the concatenated type hash and encoded data. + let mut bytes_to_hash = Vec::with_capacity(32 + encoded_data.len()); + bytes_to_hash.extend_from_slice(type_hash.as_slice()); + bytes_to_hash.extend_from_slice(&encoded_data); + + Ok(keccak256(&bytes_to_hash).to_vec()) +} diff --git a/crates/chisel/Cargo.toml b/crates/chisel/Cargo.toml index 2e40c92e9f99d..8e88cb928c69e 100644 --- a/crates/chisel/Cargo.toml +++ b/crates/chisel/Cargo.toml @@ -57,13 +57,12 @@ yansi.workspace = true tracing.workspace = true walkdir.workspace = true -[target.'cfg(unix)'.dependencies] -tikv-jemallocator = { workspace = true, optional = true } - [dev-dependencies] tracing-subscriber.workspace = true [features] default = ["jemalloc"] asm-keccak = ["alloy-primitives/asm-keccak"] -jemalloc = ["dep:tikv-jemallocator"] +jemalloc = ["foundry-cli/jemalloc"] +mimalloc = ["foundry-cli/mimalloc"] +tracy-allocator = ["foundry-cli/tracy-allocator"] diff --git a/crates/chisel/bin/main.rs b/crates/chisel/bin/main.rs index fcb26f62b1b8c..13f82d136941c 100644 --- a/crates/chisel/bin/main.rs +++ b/crates/chisel/bin/main.rs @@ -2,9 +2,8 @@ use chisel::args::run; -#[cfg(all(feature = "jemalloc", unix))] #[global_allocator] -static ALLOC: tikv_jemallocator::Jemalloc = tikv_jemallocator::Jemalloc; +static ALLOC: foundry_cli::utils::Allocator = foundry_cli::utils::new_allocator(); fn main() { if let Err(err) = run() { diff --git a/crates/chisel/src/dispatcher.rs b/crates/chisel/src/dispatcher.rs index 5dd6c882ffed6..13e63c1715beb 100644 --- a/crates/chisel/src/dispatcher.rs +++ b/crates/chisel/src/dispatcher.rs @@ -116,7 +116,10 @@ pub struct EtherscanABIResponse { macro_rules! format_param { ($param:expr) => {{ let param = $param; - format!("{}{}", param.ty, if param.is_complex_type() { " memory" } else { "" }) + let memory = param.is_complex_type() || + param.selector_type() == "string" || + param.selector_type() == "bytes"; + format!("{}{}", param.ty, if memory { " memory" } else { "" }) }}; } diff --git a/crates/chisel/src/lib.rs b/crates/chisel/src/lib.rs index b369dec13b1ea..32a29caefc1b8 100644 --- a/crates/chisel/src/lib.rs +++ b/crates/chisel/src/lib.rs @@ -1,6 +1,7 @@ //! Chisel is a fast, utilitarian, and verbose Solidity REPL. #![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] +#![cfg_attr(not(test), warn(unused_crate_dependencies))] #[macro_use] extern crate foundry_common; diff --git a/crates/cli/Cargo.toml b/crates/cli/Cargo.toml index a3a510f03cb1a..0bff1f1f0461c 100644 --- a/crates/cli/Cargo.toml +++ b/crates/cli/Cargo.toml @@ -19,8 +19,10 @@ foundry-config.workspace = true foundry-debugger.workspace = true foundry-evm.workspace = true foundry-wallets.workspace = true +foundry-block-explorers.workspace = true foundry-compilers = { workspace = true, features = ["full"] } +solar-sema.workspace = true alloy-eips.workspace = true alloy-dyn-abi.workspace = true @@ -29,7 +31,9 @@ alloy-primitives.workspace = true alloy-provider.workspace = true alloy-rlp.workspace = true alloy-chains.workspace = true +alloy-ens = { workspace = true, features = ["provider"] } +cfg-if = "1.0" clap = { version = "4", features = ["derive", "env", "unicode", "wrap_help"] } color-eyre.workspace = true dotenvy = "0.15" @@ -37,6 +41,7 @@ eyre.workspace = true futures.workspace = true indicatif.workspace = true itertools.workspace = true +mimalloc = { workspace = true, optional = true } rayon.workspace = true regex = { workspace = true, default-features = false } serde_json.workspace = true @@ -46,13 +51,21 @@ strum = { workspace = true, features = ["derive"] } tokio = { workspace = true, features = ["macros"] } tracing-subscriber = { workspace = true, features = ["registry", "env-filter"] } tracing.workspace = true +tracy-client = { workspace = true, optional = true, features = ["demangle"] } yansi.workspace = true rustls = { workspace = true, features = ["ring"] } +dunce.workspace = true tracing-tracy = { version = "0.11", optional = true } [dev-dependencies] tempfile.workspace = true +[target.'cfg(unix)'.dependencies] +tikv-jemallocator = { workspace = true, optional = true } + [features] tracy = ["dep:tracing-tracy"] +tracy-allocator = ["dep:tracy-client", "tracy"] +jemalloc = ["dep:tikv-jemallocator"] +mimalloc = ["dep:mimalloc"] diff --git a/crates/cli/src/opts/build/core.rs b/crates/cli/src/opts/build/core.rs index 652e3973d3276..dd7e49ec1c3ab 100644 --- a/crates/cli/src/opts/build/core.rs +++ b/crates/cli/src/opts/build/core.rs @@ -130,11 +130,6 @@ pub struct BuildOpts { #[serde(skip_serializing_if = "Option::is_none")] pub build_info_path: Option, - /// Whether to compile contracts to EOF bytecode. - #[arg(long)] - #[serde(skip)] - pub eof: bool, - /// Skip building files whose names contain the given filter. /// /// `test` and `script` are aliases for `.t.sol` and `.s.sol`. @@ -282,10 +277,6 @@ impl Provider for BuildOpts { dict.insert("revert_strings".to_string(), revert.to_string().into()); } - if self.eof { - dict.insert("eof".to_string(), true.into()); - } - Ok(Map::from([(Config::selected_profile(), dict)])) } } diff --git a/crates/cli/src/opts/build/mod.rs b/crates/cli/src/opts/build/mod.rs index 55c61dcbbedd7..4deffb2a4c37d 100644 --- a/crates/cli/src/opts/build/mod.rs +++ b/crates/cli/src/opts/build/mod.rs @@ -8,6 +8,9 @@ pub use self::core::BuildOpts; mod paths; pub use self::paths::ProjectPathOpts; +mod utils; +pub use self::utils::{solar_pcx_from_build_opts, solar_pcx_from_solc_project}; + // A set of solc compiler settings that can be set via command line arguments, which are intended // to be merged into an existing `foundry_config::Config`. // diff --git a/crates/cli/src/opts/build/utils.rs b/crates/cli/src/opts/build/utils.rs new file mode 100644 index 0000000000000..004416c588439 --- /dev/null +++ b/crates/cli/src/opts/build/utils.rs @@ -0,0 +1,105 @@ +use crate::{opts::BuildOpts, utils::LoadConfig}; + +use eyre::Result; +use foundry_compilers::{ + artifacts::{Source, Sources}, + multi::{MultiCompilerLanguage, MultiCompilerParsedSource}, + solc::{SolcLanguage, SolcVersionedInput}, + CompilerInput, Graph, Project, +}; +use solar_sema::{interface::Session, ParsingContext}; +use std::path::PathBuf; + +/// Builds a Solar [`solar_sema::ParsingContext`] from [`BuildOpts`]. +/// +/// * Configures include paths, remappings and registers all in-memory sources so that solar can +/// operate without touching disk. +/// * If no `target_paths` are provided, all project files are processed. +/// * Only processes the subset of sources with the most up-to-date Solitidy version. +pub fn solar_pcx_from_build_opts<'sess>( + sess: &'sess Session, + build: BuildOpts, + target_paths: Option>, +) -> Result> { + // Process build options + let config = build.load_config()?; + let project = config.ephemeral_project()?; + + let sources = match target_paths { + // If target files are provided, only process those sources + Some(targets) => { + let mut sources = Sources::new(); + for t in targets.into_iter() { + let path = dunce::canonicalize(t)?; + let source = Source::read(&path)?; + sources.insert(path, source); + } + sources + } + // Otherwise, process all project files + None => project.paths.read_input_files()?, + }; + + // Only process sources with latest Solidity version to avoid conflicts. + let graph = Graph::::resolve_sources(&project.paths, sources)?; + let (version, sources, _) = graph + // resolve graph into mapping language -> version -> sources + .into_sources_by_version(&project)? + .sources + .into_iter() + // only interested in Solidity sources + .find(|(lang, _)| *lang == MultiCompilerLanguage::Solc(SolcLanguage::Solidity)) + .ok_or_else(|| eyre::eyre!("no Solidity sources"))? + .1 + .into_iter() + // always pick the latest version + .max_by(|(v1, _, _), (v2, _, _)| v1.cmp(v2)) + .unwrap(); + + let solc = SolcVersionedInput::build( + sources, + config.solc_settings()?, + SolcLanguage::Solidity, + version, + ); + + Ok(solar_pcx_from_solc_project(sess, &project, &solc, true)) +} + +/// Builds a Solar [`solar_sema::ParsingContext`] from a [`foundry_compilers::Project`] and a +/// [`SolcVersionedInput`]. +/// +/// * Configures include paths, remappings. +/// * Source files can be manually added if the param `add_source_file` is set to `false`. +pub fn solar_pcx_from_solc_project<'sess>( + sess: &'sess Session, + project: &Project, + solc: &SolcVersionedInput, + add_source_files: bool, +) -> ParsingContext<'sess> { + // Configure the parsing context with the paths, remappings and sources + let mut pcx = ParsingContext::new(sess); + + pcx.file_resolver + .set_current_dir(solc.cli_settings.base_path.as_ref().unwrap_or(&project.paths.root)); + for remapping in &project.paths.remappings { + pcx.file_resolver.add_import_remapping(solar_sema::interface::config::ImportRemapping { + context: remapping.context.clone().unwrap_or_default(), + prefix: remapping.name.clone(), + path: remapping.path.clone(), + }); + } + pcx.file_resolver.add_include_paths(solc.cli_settings.include_paths.iter().cloned()); + + if add_source_files { + for (path, source) in &solc.input.sources { + if let Ok(src_file) = + sess.source_map().new_source_file(path.clone(), source.content.as_str()) + { + pcx.add_file(src_file); + } + } + } + + pcx +} diff --git a/crates/cli/src/opts/rpc.rs b/crates/cli/src/opts/rpc.rs index 344efe73e8514..2b508720a81a1 100644 --- a/crates/cli/src/opts/rpc.rs +++ b/crates/cli/src/opts/rpc.rs @@ -2,6 +2,7 @@ use crate::opts::ChainValueParser; use alloy_chains::ChainKind; use clap::Parser; use eyre::Result; +use foundry_block_explorers::EtherscanApiVersion; use foundry_config::{ figment::{ self, @@ -114,6 +115,16 @@ pub struct EtherscanOpts { #[serde(rename = "etherscan_api_key", skip_serializing_if = "Option::is_none")] pub key: Option, + /// The Etherscan API version. + #[arg( + short, + long = "etherscan-api-version", + alias = "api-version", + env = "ETHERSCAN_API_VERSION" + )] + #[serde(rename = "etherscan_api_version", skip_serializing_if = "Option::is_none")] + pub api_version: Option, + /// The chain name or EIP-155 chain ID. #[arg( short, @@ -154,6 +165,11 @@ impl EtherscanOpts { if let Some(key) = self.key() { dict.insert("etherscan_api_key".into(), key.into()); } + + if let Some(api_version) = &self.api_version { + dict.insert("etherscan_api_version".into(), api_version.to_string().into()); + } + if let Some(chain) = self.chain { if let ChainKind::Id(id) = chain.kind() { dict.insert("chain_id".into(), (*id).into()); diff --git a/crates/cli/src/utils/abi.rs b/crates/cli/src/utils/abi.rs index c7f4d260416d7..d752312580df4 100644 --- a/crates/cli/src/utils/abi.rs +++ b/crates/cli/src/utils/abi.rs @@ -1,12 +1,11 @@ use alloy_chains::Chain; +use alloy_ens::NameOrAddress; use alloy_json_abi::Function; use alloy_primitives::{hex, Address}; use alloy_provider::{network::AnyNetwork, Provider}; use eyre::{OptionExt, Result}; -use foundry_common::{ - abi::{encode_function_args, get_func, get_func_etherscan}, - ens::NameOrAddress, -}; +use foundry_block_explorers::EtherscanApiVersion; +use foundry_common::abi::{encode_function_args, get_func, get_func_etherscan}; use futures::future::join_all; async fn resolve_name_args>(args: &[String], provider: &P) -> Vec { @@ -31,6 +30,7 @@ pub async fn parse_function_args>( chain: Chain, provider: &P, etherscan_api_key: Option<&str>, + etherscan_api_version: EtherscanApiVersion, ) -> Result<(Vec, Option)> { if sig.trim().is_empty() { eyre::bail!("Function signature or calldata must be provided.") @@ -50,7 +50,7 @@ pub async fn parse_function_args>( "If you wish to fetch function data from Etherscan, please provide an Etherscan API key.", )?; let to = to.ok_or_eyre("A 'to' address must be provided to fetch function data.")?; - get_func_etherscan(sig, to, &args, chain, etherscan_api_key).await? + get_func_etherscan(sig, to, &args, chain, etherscan_api_key, etherscan_api_version).await? }; Ok((encode_function_args(&func, &args)?, Some(func))) diff --git a/crates/cli/src/utils/allocator.rs b/crates/cli/src/utils/allocator.rs new file mode 100644 index 0000000000000..2431e35d08cff --- /dev/null +++ b/crates/cli/src/utils/allocator.rs @@ -0,0 +1,41 @@ +//! Abstract global allocator implementation. + +#[cfg(feature = "mimalloc")] +use mimalloc as _; +#[cfg(all(feature = "jemalloc", unix))] +use tikv_jemallocator as _; + +// If neither jemalloc nor mimalloc are enabled, use explicitly the system allocator. +// By default jemalloc is enabled on Unix systems. +cfg_if::cfg_if! { + if #[cfg(all(feature = "jemalloc", unix))] { + type AllocatorInner = tikv_jemallocator::Jemalloc; + } else if #[cfg(feature = "mimalloc")] { + type AllocatorInner = mimalloc::MiMalloc; + } else { + type AllocatorInner = std::alloc::System; + } +} + +// Wrap the allocator if the `tracy-allocator` feature is enabled. +cfg_if::cfg_if! { + if #[cfg(feature = "tracy-allocator")] { + type AllocatorWrapper = tracy_client::ProfiledAllocator; + tracy_client::register_demangler!(); + const fn new_allocator_wrapper() -> AllocatorWrapper { + AllocatorWrapper::new(AllocatorInner {}, 100) + } + } else { + type AllocatorWrapper = AllocatorInner; + const fn new_allocator_wrapper() -> AllocatorWrapper { + AllocatorInner {} + } + } +} + +pub type Allocator = AllocatorWrapper; + +/// Creates a new [allocator][Allocator]. +pub const fn new_allocator() -> Allocator { + new_allocator_wrapper() +} diff --git a/crates/cli/src/utils/cmd.rs b/crates/cli/src/utils/cmd.rs index a28b0780e450b..21f411d446235 100644 --- a/crates/cli/src/utils/cmd.rs +++ b/crates/cli/src/utils/cmd.rs @@ -163,6 +163,7 @@ pub fn init_progress(len: u64, label: &str) -> indicatif::ProgressBar { pub fn has_different_gas_calc(chain_id: u64) -> bool { if let Some(chain) = Chain::from(chain_id).named() { return chain.is_arbitrum() || + chain.is_elastic() || matches!( chain, NamedChain::Acala | @@ -437,7 +438,10 @@ pub async fn print_traces( /// Traverse the artifacts in the project to generate local signatures and merge them into the cache /// file. -pub fn cache_local_signatures(output: &ProjectCompileOutput, cache_dir: &Path) -> Result<()> { +pub fn cache_local_signatures(output: &ProjectCompileOutput) -> Result<()> { + let Some(cache_dir) = Config::foundry_cache_dir() else { + eyre::bail!("Failed to get `cache_dir` to generate local signatures."); + }; let path = cache_dir.join("signatures"); let mut signatures = SignaturesCache::load(&path); for (_, artifact) in output.artifacts() { diff --git a/crates/cli/src/utils/mod.rs b/crates/cli/src/utils/mod.rs index 68cb49a6891fd..391d519d05621 100644 --- a/crates/cli/src/utils/mod.rs +++ b/crates/cli/src/utils/mod.rs @@ -26,6 +26,9 @@ pub use suggestions::*; mod abi; pub use abi::*; +mod allocator; +pub use allocator::*; + // reexport all `foundry_config::utils` #[doc(hidden)] pub use foundry_config::utils::*; diff --git a/crates/common/Cargo.toml b/crates/common/Cargo.toml index 0b81ac057e94e..ecfc9279ac39a 100644 --- a/crates/common/Cargo.toml +++ b/crates/common/Cargo.toml @@ -18,7 +18,6 @@ foundry-common-fmt.workspace = true foundry-compilers.workspace = true foundry-config.workspace = true -alloy-contract.workspace = true alloy-dyn-abi = { workspace = true, features = ["arbitrary", "eip712"] } alloy-eips.workspace = true alloy-json-abi.workspace = true @@ -50,7 +49,6 @@ solar-sema.workspace = true tower.workspace = true -async-trait.workspace = true clap = { version = "4", features = ["derive", "env", "unicode", "wrap_help"] } comfy-table.workspace = true dunce.workspace = true diff --git a/crates/common/fmt/Cargo.toml b/crates/common/fmt/Cargo.toml index 2a56b3b10e010..2c9c5223df30f 100644 --- a/crates/common/fmt/Cargo.toml +++ b/crates/common/fmt/Cargo.toml @@ -25,8 +25,7 @@ alloy-serde.workspace = true serde.workspace = true serde_json.workspace = true chrono.workspace = true -revm-primitives.workspace = true -comfy-table.workspace = true +revm.workspace = true yansi.workspace = true [dev-dependencies] diff --git a/crates/common/fmt/src/eof.rs b/crates/common/fmt/src/eof.rs deleted file mode 100644 index 1ae9de70b6b3a..0000000000000 --- a/crates/common/fmt/src/eof.rs +++ /dev/null @@ -1,79 +0,0 @@ -use comfy_table::{modifiers::UTF8_ROUND_CORNERS, ContentArrangement, Table}; -use revm_primitives::{ - eof::{EofBody, EofHeader}, - Eof, -}; -use std::fmt::{self, Write}; - -pub fn pretty_eof(eof: &Eof) -> Result { - let Eof { - header: - EofHeader { - types_size, - code_sizes, - container_sizes, - data_size, - sum_code_sizes: _, - sum_container_sizes: _, - }, - body: - EofBody { types_section, code_section, container_section, data_section, is_data_filled: _ }, - raw: _, - } = eof; - - let mut result = String::new(); - - let mut table = Table::new(); - table.apply_modifier(UTF8_ROUND_CORNERS); - table.add_row(vec!["type_size", &types_size.to_string()]); - table.add_row(vec!["num_code_sections", &code_sizes.len().to_string()]); - if !code_sizes.is_empty() { - table.add_row(vec!["code_sizes", &format!("{code_sizes:?}")]); - } - table.add_row(vec!["num_container_sections", &container_sizes.len().to_string()]); - if !container_sizes.is_empty() { - table.add_row(vec!["container_sizes", &format!("{container_sizes:?}")]); - } - table.add_row(vec!["data_size", &data_size.to_string()]); - - write!(result, "Header:\n{table}")?; - - if !code_section.is_empty() { - let mut table = Table::new(); - table.apply_modifier(UTF8_ROUND_CORNERS); - table.set_content_arrangement(ContentArrangement::Dynamic); - table.set_header(vec!["", "Inputs", "Outputs", "Max stack height", "Code"]); - for (idx, (code, type_section)) in code_section.iter().zip(types_section).enumerate() { - table.add_row(vec![ - &idx.to_string(), - &type_section.inputs.to_string(), - &type_section.outputs.to_string(), - &type_section.max_stack_size.to_string(), - &code.to_string(), - ]); - } - - write!(result, "\n\nCode sections:\n{table}")?; - } - - if !container_section.is_empty() { - let mut table = Table::new(); - table.apply_modifier(UTF8_ROUND_CORNERS); - table.set_content_arrangement(ContentArrangement::Dynamic); - for (idx, container) in container_section.iter().enumerate() { - table.add_row(vec![&idx.to_string(), &container.to_string()]); - } - - write!(result, "\n\nContainer sections:\n{table}")?; - } - - if !data_section.is_empty() { - let mut table = Table::new(); - table.apply_modifier(UTF8_ROUND_CORNERS); - table.set_content_arrangement(ContentArrangement::Dynamic); - table.add_row(vec![&data_section.to_string()]); - write!(result, "\n\nData section:\n{table}")?; - } - - Ok(result) -} diff --git a/crates/common/fmt/src/lib.rs b/crates/common/fmt/src/lib.rs index b76016cd45d25..473e5d8a33c38 100644 --- a/crates/common/fmt/src/lib.rs +++ b/crates/common/fmt/src/lib.rs @@ -1,5 +1,7 @@ //! Helpers for formatting Ethereum types. +#![cfg_attr(not(test), warn(unused_crate_dependencies))] + mod console; pub use console::{console_format, ConsoleFmt, FormatSpec}; @@ -11,6 +13,3 @@ pub use exp::{format_int_exp, format_uint_exp, to_exp_notation}; mod ui; pub use ui::{get_pretty_block_attr, get_pretty_tx_attr, EthValue, UIfmt}; - -mod eof; -pub use eof::pretty_eof; diff --git a/crates/common/fmt/src/ui.rs b/crates/common/fmt/src/ui.rs index 16600324bcb0f..89e18e5620183 100644 --- a/crates/common/fmt/src/ui.rs +++ b/crates/common/fmt/src/ui.rs @@ -12,7 +12,7 @@ use alloy_rpc_types::{ AccessListItem, Block, BlockTransactions, Header, Log, Transaction, TransactionReceipt, }; use alloy_serde::{OtherFields, WithOtherFields}; -use revm_primitives::SignedAuthorization; +use revm::context_interface::transaction::SignedAuthorization; use serde::Deserialize; /// length of the name column for pretty formatting `{:>20}{value}` diff --git a/crates/common/src/abi.rs b/crates/common/src/abi.rs index fa9f241719fdb..cdba532d842dd 100644 --- a/crates/common/src/abi.rs +++ b/crates/common/src/abi.rs @@ -4,7 +4,9 @@ use alloy_dyn_abi::{DynSolType, DynSolValue, FunctionExt, JsonAbiExt}; use alloy_json_abi::{Error, Event, Function, Param}; use alloy_primitives::{hex, Address, LogData}; use eyre::{Context, ContextCompat, Result}; -use foundry_block_explorers::{contract::ContractMetadata, errors::EtherscanError, Client}; +use foundry_block_explorers::{ + contract::ContractMetadata, errors::EtherscanError, Client, EtherscanApiVersion, +}; use foundry_config::Chain; use std::{future::Future, pin::Pin}; @@ -61,11 +63,8 @@ pub fn abi_decode_calldata( calldata = &calldata[4..]; } - let res = if input { - func.abi_decode_input(calldata, false) - } else { - func.abi_decode_output(calldata, false) - }?; + let res = + if input { func.abi_decode_input(calldata) } else { func.abi_decode_output(calldata) }?; // in case the decoding worked but nothing was decoded if res.is_empty() { @@ -120,8 +119,9 @@ pub async fn get_func_etherscan( args: &[String], chain: Chain, etherscan_api_key: &str, + etherscan_api_version: EtherscanApiVersion, ) -> Result { - let client = Client::new(chain, etherscan_api_key)?; + let client = Client::new_with_api_version(chain, etherscan_api_key, etherscan_api_version)?; let source = find_source(client, contract).await?; let metadata = source.items.first().wrap_err("etherscan returned empty metadata")?; @@ -216,7 +216,7 @@ mod tests { assert_eq!(event.inputs.len(), 3); // Only the address fields get indexed since total_params > num_indexed_params - let parsed = event.decode_log(&log, false).unwrap(); + let parsed = event.decode_log(&log).unwrap(); assert_eq!(event.inputs.iter().filter(|param| param.indexed).count(), 2); assert_eq!(parsed.indexed[0], DynSolValue::Address(Address::from_word(param0))); @@ -241,7 +241,7 @@ mod tests { // All parameters get indexed since num_indexed_params == total_params assert_eq!(event.inputs.iter().filter(|param| param.indexed).count(), 3); - let parsed = event.decode_log(&log, false).unwrap(); + let parsed = event.decode_log(&log).unwrap(); assert_eq!(parsed.indexed[0], DynSolValue::Address(Address::from_word(param0))); assert_eq!(parsed.indexed[1], DynSolValue::Uint(U256::from_be_bytes([3; 32]), 256)); diff --git a/crates/common/src/compile.rs b/crates/common/src/compile.rs index 9d7f3735c5227..f160f2f8de74b 100644 --- a/crates/common/src/compile.rs +++ b/crates/common/src/compile.rs @@ -146,7 +146,6 @@ impl ProjectCompiler { self.dynamic_test_linking = preprocess; self } - /// Compiles the project. pub fn compile>( mut self, diff --git a/crates/common/src/constants.rs b/crates/common/src/constants.rs index ab94aad05a84e..5e30eec89c9f0 100644 --- a/crates/common/src/constants.rs +++ b/crates/common/src/constants.rs @@ -2,7 +2,7 @@ use alloy_consensus::Typed2718; use alloy_network::AnyTxEnvelope; -use alloy_primitives::{address, Address, PrimitiveSignature, B256}; +use alloy_primitives::{address, Address, Signature, B256}; use std::time::Duration; /// The dev chain-id, inherited from hardhat @@ -45,14 +45,17 @@ pub const SYSTEM_TRANSACTION_TYPE: u8 = 126; /// Default user agent set as the header for requests that don't specify one. pub const DEFAULT_USER_AGENT: &str = concat!("foundry/", env!("CARGO_PKG_VERSION")); +/// Prefix for auto-generated type bindings using `forge bind-json`. +pub const TYPE_BINDING_PREFIX: &str = "string constant schema_"; + /// Returns whether the sender is a known L2 system sender that is the first tx in every block. /// -/// Transactions from these senders usually don't have a any fee information. +/// Transactions from these senders usually don't have a any fee information OR set absurdly high fees that exceed the gas limit (See: ) /// -/// See: [ARBITRUM_SENDER], [OPTIMISM_SYSTEM_ADDRESS] +/// See: [ARBITRUM_SENDER], [OPTIMISM_SYSTEM_ADDRESS] and [Address::ZERO] #[inline] pub fn is_known_system_sender(sender: Address) -> bool { - [ARBITRUM_SENDER, OPTIMISM_SYSTEM_ADDRESS].contains(&sender) + [ARBITRUM_SENDER, OPTIMISM_SYSTEM_ADDRESS, Address::ZERO].contains(&sender) } pub fn is_impersonated_tx(tx: &AnyTxEnvelope) -> bool { @@ -62,13 +65,12 @@ pub fn is_impersonated_tx(tx: &AnyTxEnvelope) -> bool { false } -pub fn is_impersonated_sig(sig: &PrimitiveSignature, ty: u8) -> bool { - let impersonated_sig = PrimitiveSignature::from_scalars_and_parity( - B256::with_last_byte(1), - B256::with_last_byte(1), - false, - ); - if ty != SYSTEM_TRANSACTION_TYPE && sig == &impersonated_sig { +pub fn is_impersonated_sig(sig: &Signature, ty: u8) -> bool { + let impersonated_sig = + Signature::from_scalars_and_parity(B256::with_last_byte(1), B256::with_last_byte(1), false); + if ty != SYSTEM_TRANSACTION_TYPE && + (sig == &impersonated_sig || sig.r() == impersonated_sig.r()) + { return true; } false diff --git a/crates/common/src/contracts.rs b/crates/common/src/contracts.rs index ecfb67d67f1cd..789491d074dbf 100644 --- a/crates/common/src/contracts.rs +++ b/crates/common/src/contracts.rs @@ -149,7 +149,7 @@ impl ContractsByArtifact { // Try to decode ctor args with contract abi. if let Some(constructor) = contract.abi.constructor() { let constructor_args = &code[deployed_bytecode.len()..]; - if constructor.abi_decode_input(constructor_args, false).is_ok() { + if constructor.abi_decode_input(constructor_args).is_ok() { // If we can decode args with current abi then remove args from // code to compare. code = &code[..deployed_bytecode.len()] @@ -280,7 +280,7 @@ impl ContractsByArtifact { eyre::bail!("{id} has more than one implementation."); } - Ok(contracts.first().cloned()) + Ok(contracts.first().copied()) } /// Finds abi for contract which has the same contract name or identifier as `id`. @@ -454,6 +454,29 @@ pub fn find_target_path(project: &Project, identifier: &PathOrContractInfo) -> R match identifier { PathOrContractInfo::Path(path) => Ok(canonicalized(project.root().join(path))), PathOrContractInfo::ContractInfo(info) => { + if let Some(path) = info.path.as_ref() { + let path = canonicalized(project.root().join(path)); + let sources = project.sources()?; + let contract_path = sources + .iter() + .find_map(|(src_path, _)| { + if **src_path == path { + return Some(src_path.clone()); + } + None + }) + .ok_or_else(|| { + eyre::eyre!( + "Could not find source file for contract `{}` at {}", + info.name, + path.strip_prefix(project.root()).unwrap().display() + ) + })?; + return Ok(contract_path) + } + // If ContractInfo.path hasn't been provided we try to find the contract using the name. + // This will fail if projects have multiple contracts with the same name. In that case, + // path must be specified. let path = project.find_contract_path(&info.name)?; Ok(path) } diff --git a/crates/common/src/ens.rs b/crates/common/src/ens.rs deleted file mode 100644 index 9d19ac258cd23..0000000000000 --- a/crates/common/src/ens.rs +++ /dev/null @@ -1,254 +0,0 @@ -//! ENS Name resolving utilities. - -#![allow(missing_docs)] - -use self::EnsResolver::EnsResolverInstance; -use alloy_primitives::{address, Address, Keccak256, B256}; -use alloy_provider::{Network, Provider}; -use alloy_sol_types::sol; -use async_trait::async_trait; -use std::{borrow::Cow, str::FromStr}; - -// ENS Registry and Resolver contracts. -sol! { - /// ENS Registry contract. - #[sol(rpc)] - contract EnsRegistry { - /// Returns the resolver for the specified node. - function resolver(bytes32 node) view returns (address); - } - - /// ENS Resolver interface. - #[sol(rpc)] - contract EnsResolver { - /// Returns the address associated with the specified node. - function addr(bytes32 node) view returns (address); - - /// Returns the name associated with an ENS node, for reverse records. - function name(bytes32 node) view returns (string); - } -} - -/// ENS registry address (`0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e`) -pub const ENS_ADDRESS: Address = address!("0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e"); - -pub const ENS_REVERSE_REGISTRAR_DOMAIN: &str = "addr.reverse"; - -/// Error type for ENS resolution. -#[derive(Debug, thiserror::Error)] -pub enum EnsError { - /// Failed to get resolver from the ENS registry. - #[error("Failed to get resolver from the ENS registry: {0}")] - Resolver(alloy_contract::Error), - /// Failed to get resolver from the ENS registry. - #[error("ENS resolver not found for name {0:?}")] - ResolverNotFound(String), - /// Failed to lookup ENS name from an address. - #[error("Failed to lookup ENS name from an address: {0}")] - Lookup(alloy_contract::Error), - /// Failed to resolve ENS name to an address. - #[error("Failed to resolve ENS name to an address: {0}")] - Resolve(alloy_contract::Error), -} - -/// ENS name or Ethereum Address. -#[derive(Clone, Debug, PartialEq, Eq)] -pub enum NameOrAddress { - /// An ENS Name (format does not get checked) - Name(String), - /// An Ethereum Address - Address(Address), -} - -impl NameOrAddress { - /// Resolves the name to an Ethereum Address. - pub async fn resolve>( - &self, - provider: &P, - ) -> Result { - match self { - Self::Name(name) => provider.resolve_name(name).await, - Self::Address(addr) => Ok(*addr), - } - } -} - -impl From for NameOrAddress { - fn from(name: String) -> Self { - Self::Name(name) - } -} - -impl From<&String> for NameOrAddress { - fn from(name: &String) -> Self { - Self::Name(name.clone()) - } -} - -impl From
for NameOrAddress { - fn from(addr: Address) -> Self { - Self::Address(addr) - } -} - -impl FromStr for NameOrAddress { - type Err =
::Err; - - fn from_str(s: &str) -> Result { - match Address::from_str(s) { - Ok(addr) => Ok(Self::Address(addr)), - Err(err) => { - if s.contains('.') { - Ok(Self::Name(s.to_string())) - } else { - Err(err) - } - } - } - } -} - -/// Extension trait for ENS contract calls. -#[async_trait] -pub trait ProviderEnsExt> { - /// Returns the resolver for the specified node. The `&str` is only used for error messages. - async fn get_resolver( - &self, - node: B256, - error_name: &str, - ) -> Result, EnsError>; - - /// Performs a forward lookup of an ENS name to an address. - async fn resolve_name(&self, name: &str) -> Result { - let node = namehash(name); - let resolver = self.get_resolver(node, name).await?; - let addr = resolver - .addr(node) - .call() - .await - .map_err(EnsError::Resolve) - .inspect_err(|e| { - let _ = sh_eprintln!("{e:?}"); - })? - ._0; - Ok(addr) - } - - /// Performs a reverse lookup of an address to an ENS name. - async fn lookup_address(&self, address: &Address) -> Result { - let name = reverse_address(address); - let node = namehash(&name); - let resolver = self.get_resolver(node, &name).await?; - let name = resolver.name(node).call().await.map_err(EnsError::Lookup)?._0; - Ok(name) - } -} - -#[async_trait] -impl ProviderEnsExt for P -where - P: Provider, - N: Network, -{ - async fn get_resolver( - &self, - node: B256, - error_name: &str, - ) -> Result, EnsError> { - let registry = EnsRegistry::new(ENS_ADDRESS, self); - let address = registry.resolver(node).call().await.map_err(EnsError::Resolver)?._0; - if address == Address::ZERO { - return Err(EnsError::ResolverNotFound(error_name.to_string())); - } - Ok(EnsResolverInstance::new(address, self)) - } -} - -/// Returns the ENS namehash as specified in [EIP-137](https://eips.ethereum.org/EIPS/eip-137) -pub fn namehash(name: &str) -> B256 { - if name.is_empty() { - return B256::ZERO - } - - // Remove the variation selector `U+FE0F` if present. - const VARIATION_SELECTOR: char = '\u{fe0f}'; - let name = if name.contains(VARIATION_SELECTOR) { - Cow::Owned(name.replace(VARIATION_SELECTOR, "")) - } else { - Cow::Borrowed(name) - }; - - // Generate the node starting from the right. - // This buffer is `[node @ [u8; 32], label_hash @ [u8; 32]]`. - let mut buffer = [0u8; 64]; - for label in name.rsplit('.') { - // node = keccak256([node, keccak256(label)]) - - // Hash the label. - let mut label_hasher = Keccak256::new(); - label_hasher.update(label.as_bytes()); - label_hasher.finalize_into(&mut buffer[32..]); - - // Hash both the node and the label hash, writing into the node. - let mut buffer_hasher = Keccak256::new(); - buffer_hasher.update(buffer.as_slice()); - buffer_hasher.finalize_into(&mut buffer[..32]); - } - buffer[..32].try_into().unwrap() -} - -/// Returns the reverse-registrar name of an address. -pub fn reverse_address(addr: &Address) -> String { - format!("{addr:x}.{ENS_REVERSE_REGISTRAR_DOMAIN}") -} - -#[cfg(test)] -mod test { - use super::*; - use alloy_primitives::hex; - - fn assert_hex(hash: B256, val: &str) { - assert_eq!(hash.0[..], hex::decode(val).unwrap()[..]); - } - - #[test] - fn test_namehash() { - for (name, expected) in &[ - ("", "0x0000000000000000000000000000000000000000000000000000000000000000"), - ("eth", "0x93cdeb708b7545dc668eb9280176169d1c33cfd8ed6f04690a0bcc88a93fc4ae"), - ("foo.eth", "0xde9b09fd7c5f901e23a3f19fecc54828e9c848539801e86591bd9801b019f84f"), - ("alice.eth", "0x787192fc5378cc32aa956ddfdedbf26b24e8d78e40109add0eea2c1a012c3dec"), - ("ret↩️rn.eth", "0x3de5f4c02db61b221e7de7f1c40e29b6e2f07eb48d65bf7e304715cd9ed33b24"), - ] { - assert_hex(namehash(name), expected); - } - } - - #[test] - fn test_reverse_address() { - for (addr, expected) in [ - ( - "0x314159265dd8dbb310642f98f50c066173c1259b", - "314159265dd8dbb310642f98f50c066173c1259b.addr.reverse", - ), - ( - "0x28679A1a632125fbBf7A68d850E50623194A709E", - "28679a1a632125fbbf7a68d850e50623194a709e.addr.reverse", - ), - ] { - assert_eq!(reverse_address(&addr.parse().unwrap()), expected, "{addr}"); - } - } - - #[test] - fn test_invalid_address() { - for addr in [ - "0x314618", - "0x000000000000000000000000000000000000000", // 41 - "0x00000000000000000000000000000000000000000", // 43 - "0x28679A1a632125fbBf7A68d850E50623194A709E123", // 44 - ] { - assert!(NameOrAddress::from_str(addr).is_err()); - } - } -} diff --git a/crates/common/src/fs.rs b/crates/common/src/fs.rs index 3d061759c50cf..19675e425a4ce 100644 --- a/crates/common/src/fs.rs +++ b/crates/common/src/fs.rs @@ -118,7 +118,7 @@ pub fn open(path: impl AsRef) -> Result { /// ref: pub fn normalize_path(path: &Path) -> PathBuf { let mut components = path.components().peekable(); - let mut ret = if let Some(c @ Component::Prefix(..)) = components.peek().cloned() { + let mut ret = if let Some(c @ Component::Prefix(..)) = components.peek().copied() { components.next(); PathBuf::from(c.as_os_str()) } else { diff --git a/crates/common/src/lib.rs b/crates/common/src/lib.rs index 5981d317ab1d9..a0912c38caf8e 100644 --- a/crates/common/src/lib.rs +++ b/crates/common/src/lib.rs @@ -21,7 +21,6 @@ pub mod calc; pub mod compile; pub mod constants; pub mod contracts; -pub mod ens; pub mod errors; pub mod evm; pub mod fs; diff --git a/crates/common/src/preprocessor/data.rs b/crates/common/src/preprocessor/data.rs index 78fe1098fcc56..6cc988137e167 100644 --- a/crates/common/src/preprocessor/data.rs +++ b/crates/common/src/preprocessor/data.rs @@ -178,6 +178,7 @@ impl ContractData { let helper = format!( r#" +// SPDX-License-Identifier: MIT pragma solidity >=0.4.0; import "{path}"; diff --git a/crates/common/src/preprocessor/deps.rs b/crates/common/src/preprocessor/deps.rs index 7c702c280bf3b..7d0f31b1d6b92 100644 --- a/crates/common/src/preprocessor/deps.rs +++ b/crates/common/src/preprocessor/deps.rs @@ -6,7 +6,7 @@ use foundry_compilers::Updates; use itertools::Itertools; use solar_parse::interface::Session; use solar_sema::{ - hir::{ContractId, Expr, ExprKind, Hir, NamedArg, TypeKind, Visit}, + hir::{CallArgs, ContractId, Expr, ExprKind, Hir, NamedArg, Stmt, StmtKind, TypeKind, Visit}, interface::{data_structures::Never, source_map::FileName, SourceMap}, }; use std::{ @@ -105,6 +105,8 @@ enum BytecodeDependencyKind { value: Option, /// `salt` (if any) used when creating contract. salt: Option, + /// Whether it's a try contract creation statement. + try_stmt: bool, }, } @@ -182,42 +184,17 @@ impl<'hir> Visit<'hir> for BytecodeDependencyCollector<'hir> { fn visit_expr(&mut self, expr: &'hir Expr<'hir>) -> ControlFlow { match &expr.kind { - ExprKind::Call(ty, call_args, named_args) => { - if let ExprKind::New(ty_new) = &ty.kind { - if let TypeKind::Custom(item_id) = ty_new.kind { - if let Some(contract_id) = item_id.as_contract() { - let name_loc = span_to_range(self.source_map, ty_new.span); - let name = &self.src[name_loc]; - - // Calculate offset to remove named args, e.g. for an expression like - // `new Counter {value: 333} ( address(this))` - // the offset will be used to replace `{value: 333} (` with `(` - let call_args_offset = if named_args.is_some() && !call_args.is_empty() - { - (call_args.span.lo() - ty_new.span.hi()).to_usize() - } else { - 0 - }; - - let args_len = expr.span.hi() - ty_new.span.hi(); - self.collect_dependency(BytecodeDependency { - kind: BytecodeDependencyKind::New { - name: name.to_string(), - args_length: args_len.to_usize(), - call_args_offset, - value: named_arg( - self.src, - named_args, - "value", - self.source_map, - ), - salt: named_arg(self.src, named_args, "salt", self.source_map), - }, - loc: span_to_range(self.source_map, ty.span), - referenced_contract: contract_id, - }); - } - } + ExprKind::Call(call_expr, call_args, named_args) => { + if let Some(dependency) = handle_call_expr( + self.src, + self.source_map, + expr, + call_expr, + call_args, + named_args, + false, + ) { + self.collect_dependency(dependency); } } ExprKind::Member(member_expr, ident) => { @@ -239,6 +216,78 @@ impl<'hir> Visit<'hir> for BytecodeDependencyCollector<'hir> { } self.walk_expr(expr) } + + fn visit_stmt(&mut self, stmt: &'hir Stmt<'hir>) -> ControlFlow { + if let StmtKind::Try(stmt_try) = stmt.kind { + if let ExprKind::Call(call_expr, call_args, named_args) = &stmt_try.expr.kind { + if let Some(dependency) = handle_call_expr( + self.src, + self.source_map, + &stmt_try.expr, + call_expr, + call_args, + named_args, + true, + ) { + self.collect_dependency(dependency); + for clause in stmt_try.clauses { + for &var in clause.args { + self.visit_nested_var(var)?; + } + for stmt in clause.block.stmts { + self.visit_stmt(stmt)?; + } + } + return ControlFlow::Continue(()); + } + } + } + self.walk_stmt(stmt) + } +} + +/// Helper function to analyze and extract bytecode dependency from a given call expression. +fn handle_call_expr( + src: &str, + source_map: &SourceMap, + parent_expr: &Expr<'_>, + call_expr: &Expr<'_>, + call_args: &CallArgs<'_>, + named_args: &Option<&[NamedArg<'_>]>, + try_stmt: bool, +) -> Option { + if let ExprKind::New(ty_new) = &call_expr.kind { + if let TypeKind::Custom(item_id) = ty_new.kind { + if let Some(contract_id) = item_id.as_contract() { + let name_loc = span_to_range(source_map, ty_new.span); + let name = &src[name_loc]; + + // Calculate offset to remove named args, e.g. for an expression like + // `new Counter {value: 333} ( address(this))` + // the offset will be used to replace `{value: 333} ( ` with `(` + let call_args_offset = if named_args.is_some() && !call_args.is_empty() { + (call_args.span.lo() - ty_new.span.hi()).to_usize() + } else { + 0 + }; + + let args_len = parent_expr.span.hi() - ty_new.span.hi(); + return Some(BytecodeDependency { + kind: BytecodeDependencyKind::New { + name: name.to_string(), + args_length: args_len.to_usize(), + call_args_offset, + value: named_arg(src, named_args, "value", source_map), + salt: named_arg(src, named_args, "salt", source_map), + try_stmt, + }, + loc: span_to_range(source_map, call_expr.span), + referenced_contract: contract_id, + }) + } + } + } + None } /// Helper function to extract value of a given named arg. @@ -300,8 +349,14 @@ pub(crate) fn remove_bytecode_dependencies( call_args_offset, value, salt, + try_stmt, } => { - let mut update = format!("{name}(payable({vm}.deployCode({{"); + let (mut update, closing_seq) = if *try_stmt { + (String::new(), "})") + } else { + (format!("{name}(payable("), "})))") + }; + update.push_str(&format!("{vm}.deployCode({{")); update.push_str(&format!("_artifact: \"{artifact}\"")); if let Some(value) = value { @@ -323,17 +378,15 @@ pub(crate) fn remove_bytecode_dependencies( "_args: encodeArgs{id}(DeployHelper{id}.FoundryPpConstructorArgs", id = dep.referenced_contract.get() )); - if *call_args_offset > 0 { - update.push('('); - } updates.insert((dep.loc.start, dep.loc.end + call_args_offset, update)); + updates.insert(( dep.loc.end + args_length, dep.loc.end + args_length, - ")})))".to_string(), + format!("){closing_seq}"), )); } else { - update.push_str("})))"); + update.push_str(closing_seq); updates.insert((dep.loc.start, dep.loc.end + args_length, update)); } } diff --git a/crates/common/src/provider/mod.rs b/crates/common/src/provider/mod.rs index 6c2d561ba0c1f..0603b91f97d2d 100644 --- a/crates/common/src/provider/mod.rs +++ b/crates/common/src/provider/mod.rs @@ -280,7 +280,7 @@ impl ProviderBuilder { } let provider = AlloyProviderBuilder::<_, _, AnyNetwork>::default() - .on_provider(RootProvider::new(client)); + .connect_provider(RootProvider::new(client)); Ok(provider) } @@ -323,7 +323,7 @@ impl ProviderBuilder { let provider = AlloyProviderBuilder::<_, _, AnyNetwork>::default() .with_recommended_fillers() .wallet(wallet) - .on_provider(RootProvider::new(client)); + .connect_provider(RootProvider::new(client)); Ok(provider) } diff --git a/crates/common/src/provider/runtime_transport.rs b/crates/common/src/provider/runtime_transport.rs index 9a59f6ed20423..f4c5e789dd137 100644 --- a/crates/common/src/provider/runtime_transport.rs +++ b/crates/common/src/provider/runtime_transport.rs @@ -198,11 +198,15 @@ impl RuntimeTransport { /// Connects to a WS transport. async fn connect_ws(&self) -> Result { let auth = self.jwt.as_ref().and_then(|jwt| build_auth(jwt.clone()).ok()); - let ws = WsConnect { url: self.url.to_string(), auth, config: None } + let mut ws = WsConnect::new(self.url.to_string()); + if let Some(auth) = auth { + ws = ws.with_auth(auth); + }; + let service = ws .into_service() .await .map_err(|e| RuntimeTransportError::TransportError(e, self.url.to_string()))?; - Ok(InnerTransport::Ws(ws)) + Ok(InnerTransport::Ws(service)) } /// Connects to an IPC transport. @@ -313,9 +317,8 @@ fn url_to_file_path(url: &Url) -> Result { let url_str = url.as_str(); - if url_str.starts_with(PREFIX) { - let pipe_name = &url_str[PREFIX.len()..]; - let pipe_path = format!(r"\\.\pipe\{}", pipe_name); + if let Some(pipe_name) = url_str.strip_prefix(PREFIX) { + let pipe_path = format!(r"\\.\pipe\{pipe_name}"); return Ok(PathBuf::from(pipe_path)); } diff --git a/crates/common/src/traits.rs b/crates/common/src/traits.rs index f5f3ea14ce460..a3344c57b6dc7 100644 --- a/crates/common/src/traits.rs +++ b/crates/common/src/traits.rs @@ -116,6 +116,8 @@ pub enum TestFunctionKind { FuzzTest { should_fail: bool }, /// `invariant*` or `statefulFuzz*`. InvariantTest, + /// `table*`, with arguments. + TableTest, /// `afterInvariant`. AfterInvariant, /// `fixture*`. @@ -140,6 +142,7 @@ impl TestFunctionKind { _ if name.starts_with("invariant") || name.starts_with("statefulFuzz") => { Self::InvariantTest } + _ if name.starts_with("table") => Self::TableTest, _ if name.eq_ignore_ascii_case("setup") => Self::Setup, _ if name.eq_ignore_ascii_case("afterinvariant") => Self::AfterInvariant, _ if name.starts_with("fixture") => Self::Fixture, @@ -156,6 +159,7 @@ impl TestFunctionKind { Self::FuzzTest { should_fail: false } => "fuzz", Self::FuzzTest { should_fail: true } => "fuzz fail", Self::InvariantTest => "invariant", + Self::TableTest => "table", Self::AfterInvariant => "afterInvariant", Self::Fixture => "fixture", Self::Unknown => "unknown", @@ -171,7 +175,10 @@ impl TestFunctionKind { /// Returns `true` if this function is a unit, fuzz, or invariant test. #[inline] pub const fn is_any_test(&self) -> bool { - matches!(self, Self::UnitTest { .. } | Self::FuzzTest { .. } | Self::InvariantTest) + matches!( + self, + Self::UnitTest { .. } | Self::FuzzTest { .. } | Self::TableTest | Self::InvariantTest + ) } /// Returns `true` if this function is a test that should fail. @@ -198,6 +205,12 @@ impl TestFunctionKind { matches!(self, Self::InvariantTest) } + /// Returns `true` if this function is a table test. + #[inline] + pub const fn is_table_test(&self) -> bool { + matches!(self, Self::TableTest) + } + /// Returns `true` if this function is an `afterInvariant` function. #[inline] pub const fn is_after_invariant(&self) -> bool { diff --git a/crates/common/src/transactions.rs b/crates/common/src/transactions.rs index a3ef9c4d8a642..0933764b5c6d1 100644 --- a/crates/common/src/transactions.rs +++ b/crates/common/src/transactions.rs @@ -1,6 +1,6 @@ //! Wrappers for transactions. -use alloy_consensus::{Transaction, TxEnvelope}; +use alloy_consensus::{transaction::SignerRecoverable, Transaction, TxEnvelope}; use alloy_eips::eip7702::SignedAuthorization; use alloy_network::AnyTransactionReceipt; use alloy_primitives::{Address, TxKind, U256}; @@ -175,20 +175,6 @@ pub fn get_pretty_tx_receipt_attr( } } -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_extract_revert_reason() { - let error_string_1 = "server returned an error response: error code 3: execution reverted: Transaction too old"; - let error_string_2 = "server returned an error response: error code 3: Invalid signature"; - - assert_eq!(extract_revert_reason(error_string_1), Some("Transaction too old".to_string())); - assert_eq!(extract_revert_reason(error_string_2), None); - } -} - /// Used for broadcasting transactions /// A transaction can either be a [`TransactionRequest`] waiting to be signed /// or a [`TxEnvelope`], already signed @@ -212,7 +198,7 @@ impl TransactionMaybeSigned { /// Creates a new signed transaction for broadcast. pub fn new_signed( tx: TxEnvelope, - ) -> core::result::Result { + ) -> core::result::Result { let from = tx.recover_signer()?; Ok(Self::Signed { tx, from }) } @@ -286,9 +272,23 @@ impl From for TransactionMaybeSigned { } impl TryFrom for TransactionMaybeSigned { - type Error = alloy_primitives::SignatureError; + type Error = alloy_consensus::crypto::RecoveryError; fn try_from(tx: TxEnvelope) -> core::result::Result { Self::new_signed(tx) } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_extract_revert_reason() { + let error_string_1 = "server returned an error response: error code 3: execution reverted: Transaction too old"; + let error_string_2 = "server returned an error response: error code 3: Invalid signature"; + + assert_eq!(extract_revert_reason(error_string_1), Some("Transaction too old".to_string())); + assert_eq!(extract_revert_reason(error_string_2), None); + } +} diff --git a/crates/common/src/version.rs b/crates/common/src/version.rs index f69457bf7c1e3..aae9788aab93f 100644 --- a/crates/common/src/version.rs +++ b/crates/common/src/version.rs @@ -23,5 +23,4 @@ pub const IS_NIGHTLY_VERSION: bool = option_env!("FOUNDRY_IS_NIGHTLY_VERSION").i /// The warning message for nightly versions. pub const NIGHTLY_VERSION_WARNING_MESSAGE: &str = "This is a nightly build of Foundry. It is recommended to use the latest stable version. \ - Visit https://book.getfoundry.sh/announcements for more information. \n\ To mute this warning set `FOUNDRY_DISABLE_NIGHTLY_WARNING` in your environment. \n"; diff --git a/crates/config/Cargo.toml b/crates/config/Cargo.toml index 816f06054734f..3bd2fa44b9e6a 100644 --- a/crates/config/Cargo.toml +++ b/crates/config/Cargo.toml @@ -19,9 +19,11 @@ foundry-compilers = { workspace = true, features = ["svm-solc"] } alloy-chains = { workspace = true, features = ["serde"] } alloy-primitives = { workspace = true, features = ["serde"] } -revm-primitives.workspace = true + +revm.workspace = true solar-parse.workspace = true +solar-interface.workspace = true dirs.workspace = true dunce.workspace = true @@ -29,7 +31,7 @@ eyre.workspace = true figment = { workspace = true, features = ["toml", "env"] } glob = "0.3" globset = "0.4" -heck = "0.5" +heck.workspace = true itertools.workspace = true mesc.workspace = true number_prefix = "0.4" @@ -45,6 +47,7 @@ toml_edit = "0.22" tracing.workspace = true walkdir.workspace = true yansi.workspace = true +clap = { version = "4", features = ["derive"] } [target.'cfg(target_os = "windows")'.dependencies] path-slash = "0.2" diff --git a/crates/config/README.md b/crates/config/README.md index be3055f5816f7..bd2b683f1bae4 100644 --- a/crates/config/README.md +++ b/crates/config/README.md @@ -79,7 +79,7 @@ allow_paths = [] # additional solc include paths include_paths = [] force = false -evm_version = 'shanghai' +evm_version = 'prague' gas_reports = ['*'] gas_reports_ignore = [] ## Sets the concrete solc version to use, this overrides the `auto_detect_solc` value diff --git a/crates/config/src/etherscan.rs b/crates/config/src/etherscan.rs index 099dc0e344c2f..76d50b09bc934 100644 --- a/crates/config/src/etherscan.rs +++ b/crates/config/src/etherscan.rs @@ -9,6 +9,7 @@ use figment::{ value::{Dict, Map}, Error, Metadata, Profile, Provider, }; +use foundry_block_explorers::EtherscanApiVersion; use heck::ToKebabCase; use serde::{Deserialize, Deserializer, Serialize, Serializer}; use std::{ @@ -83,13 +84,13 @@ impl EtherscanConfigs { } /// Returns all (alias -> url) pairs - pub fn resolved(self) -> ResolvedEtherscanConfigs { + pub fn resolved(self, default_api_version: EtherscanApiVersion) -> ResolvedEtherscanConfigs { ResolvedEtherscanConfigs { configs: self .configs .into_iter() .map(|(name, e)| { - let resolved = e.resolve(Some(&name)); + let resolved = e.resolve(Some(&name), default_api_version); (name, resolved) }) .collect(), @@ -173,6 +174,9 @@ pub struct EtherscanConfig { /// Etherscan API URL #[serde(default, skip_serializing_if = "Option::is_none")] pub url: Option, + /// Etherscan API Version. Defaults to v2 + #[serde(default, alias = "api-version", skip_serializing_if = "Option::is_none")] + pub api_version: Option, /// The etherscan API KEY that's required to make requests pub key: EtherscanApiKey, } @@ -187,8 +191,11 @@ impl EtherscanConfig { pub fn resolve( self, alias: Option<&str>, + default_api_version: EtherscanApiVersion, ) -> Result { - let Self { chain, mut url, key } = self; + let Self { chain, mut url, key, api_version } = self; + + let api_version = api_version.unwrap_or(default_api_version); if let Some(url) = &mut url { *url = interpolate(url)?; @@ -219,17 +226,23 @@ impl EtherscanConfig { match (chain, url) { (Some(chain), Some(api_url)) => Ok(ResolvedEtherscanConfig { api_url, + api_version, browser_url: chain.etherscan_urls().map(|(_, url)| url.to_string()), key, chain: Some(chain), }), - (Some(chain), None) => ResolvedEtherscanConfig::create(key, chain).ok_or_else(|| { - let msg = alias.map(|a| format!(" `{a}`")).unwrap_or_default(); - EtherscanConfigError::UnknownChain(msg, chain) + (Some(chain), None) => ResolvedEtherscanConfig::create(key, chain, api_version) + .ok_or_else(|| { + let msg = alias.map(|a| format!(" `{a}`")).unwrap_or_default(); + EtherscanConfigError::UnknownChain(msg, chain) + }), + (None, Some(api_url)) => Ok(ResolvedEtherscanConfig { + api_url, + browser_url: None, + key, + chain: None, + api_version, }), - (None, Some(api_url)) => { - Ok(ResolvedEtherscanConfig { api_url, browser_url: None, key, chain: None }) - } (None, None) => { let msg = alias .map(|a| format!(" for Etherscan config with unknown alias `{a}`")) @@ -251,6 +264,9 @@ pub struct ResolvedEtherscanConfig { pub browser_url: Option, /// The resolved API key. pub key: String, + /// Etherscan API Version. + #[serde(default)] + pub api_version: EtherscanApiVersion, /// The chain name or EIP-155 chain ID. #[serde(default, skip_serializing_if = "Option::is_none")] pub chain: Option, @@ -258,11 +274,16 @@ pub struct ResolvedEtherscanConfig { impl ResolvedEtherscanConfig { /// Creates a new instance using the api key and chain - pub fn create(api_key: impl Into, chain: impl Into) -> Option { + pub fn create( + api_key: impl Into, + chain: impl Into, + api_version: EtherscanApiVersion, + ) -> Option { let chain = chain.into(); let (api_url, browser_url) = chain.etherscan_urls()?; Some(Self { api_url: api_url.to_string(), + api_version, browser_url: Some(browser_url.to_string()), key: api_key.into(), chain: Some(chain), @@ -294,13 +315,10 @@ impl ResolvedEtherscanConfig { self, ) -> Result { - let Self { api_url, browser_url, key: api_key, chain } = self; - let (mainnet_api, mainnet_url) = NamedChain::Mainnet.etherscan_urls().expect("exist; qed"); + let Self { api_url, browser_url, key: api_key, chain, api_version } = self; - let cache = chain - // try to match against mainnet, which is usually the most common target - .or_else(|| (api_url == mainnet_api).then(Chain::mainnet)) - .and_then(Config::foundry_etherscan_chain_cache_dir); + let chain = chain.unwrap_or_default(); + let cache = Config::foundry_etherscan_chain_cache_dir(chain); if let Some(cache_path) = &cache { // we also create the `sources` sub dir here @@ -314,15 +332,15 @@ impl ResolvedEtherscanConfig { .user_agent(ETHERSCAN_USER_AGENT) .tls_built_in_root_certs(api_url.scheme() == "https") .build()?; - foundry_block_explorers::Client::builder() + let mut client_builder = foundry_block_explorers::Client::builder() .with_client(client) + .with_api_version(api_version) .with_api_key(api_key) - .with_api_url(api_url)? - // the browser url is not used/required by the client so we can simply set the - // mainnet browser url here - .with_url(browser_url.as_deref().unwrap_or(mainnet_url))? - .with_cache(cache, Duration::from_secs(24 * 60 * 60)) - .build() + .with_cache(cache, Duration::from_secs(24 * 60 * 60)); + if let Some(browser_url) = browser_url { + client_builder = client_builder.with_url(browser_url)?; + } + client_builder.chain(chain)?.build() } } @@ -423,12 +441,36 @@ mod tests { chain: Some(Mainnet.into()), url: None, key: EtherscanApiKey::Key("ABCDEFG".to_string()), + api_version: None, }, ); - let mut resolved = configs.resolved(); + let mut resolved = configs.resolved(EtherscanApiVersion::V2); let config = resolved.remove("mainnet").unwrap().unwrap(); - let _ = config.into_client().unwrap(); + // None version = None + assert_eq!(config.api_version, EtherscanApiVersion::V2); + let client = config.into_client().unwrap(); + assert_eq!(*client.etherscan_api_version(), EtherscanApiVersion::V2); + } + + #[test] + fn can_create_v1_client_via_chain() { + let mut configs = EtherscanConfigs::default(); + configs.insert( + "mainnet".to_string(), + EtherscanConfig { + chain: Some(Mainnet.into()), + url: None, + api_version: Some(EtherscanApiVersion::V1), + key: EtherscanApiKey::Key("ABCDEG".to_string()), + }, + ); + + let mut resolved = configs.resolved(EtherscanApiVersion::V2); + let config = resolved.remove("mainnet").unwrap().unwrap(); + assert_eq!(config.api_version, EtherscanApiVersion::V1); + let client = config.into_client().unwrap(); + assert_eq!(*client.etherscan_api_version(), EtherscanApiVersion::V1); } #[test] @@ -440,10 +482,11 @@ mod tests { chain: Some(Mainnet.into()), url: Some("https://api.etherscan.io/api".to_string()), key: EtherscanApiKey::Key("ABCDEFG".to_string()), + api_version: None, }, ); - let mut resolved = configs.resolved(); + let mut resolved = configs.resolved(EtherscanApiVersion::V2); let config = resolved.remove("mainnet").unwrap().unwrap(); let _ = config.into_client().unwrap(); } @@ -457,20 +500,22 @@ mod tests { EtherscanConfig { chain: Some(Mainnet.into()), url: Some("https://api.etherscan.io/api".to_string()), + api_version: None, key: EtherscanApiKey::Env(format!("${{{env}}}")), }, ); - let mut resolved = configs.clone().resolved(); + let mut resolved = configs.clone().resolved(EtherscanApiVersion::V2); let config = resolved.remove("mainnet").unwrap(); assert!(config.is_err()); std::env::set_var(env, "ABCDEFG"); - let mut resolved = configs.resolved(); + let mut resolved = configs.resolved(EtherscanApiVersion::V2); let config = resolved.remove("mainnet").unwrap().unwrap(); assert_eq!(config.key, "ABCDEFG"); - let _ = config.into_client().unwrap(); + let client = config.into_client().unwrap(); + assert_eq!(*client.etherscan_api_version(), EtherscanApiVersion::V2); std::env::remove_var(env); } @@ -484,10 +529,11 @@ mod tests { chain: None, url: Some("https://api.etherscan.io/api".to_string()), key: EtherscanApiKey::Key("ABCDEFG".to_string()), + api_version: None, }, ); - let mut resolved = configs.clone().resolved(); + let mut resolved = configs.clone().resolved(EtherscanApiVersion::V2); let config = resolved.remove("blast_sepolia").unwrap().unwrap(); assert_eq!(config.chain, Some(Chain::blast_sepolia())); } @@ -498,11 +544,13 @@ mod tests { chain: None, url: Some("https://api.etherscan.io/api".to_string()), key: EtherscanApiKey::Key("ABCDEFG".to_string()), + api_version: None, }; - let resolved = config.clone().resolve(Some("base_sepolia")).unwrap(); + let resolved = + config.clone().resolve(Some("base_sepolia"), EtherscanApiVersion::V2).unwrap(); assert_eq!(resolved.chain, Some(Chain::base_sepolia())); - let resolved = config.resolve(Some("base-sepolia")).unwrap(); + let resolved = config.resolve(Some("base-sepolia"), EtherscanApiVersion::V2).unwrap(); assert_eq!(resolved.chain, Some(Chain::base_sepolia())); } } diff --git a/crates/config/src/invariant.rs b/crates/config/src/invariant.rs index 0c5dbc567d363..b5939ad6f5f1d 100644 --- a/crates/config/src/invariant.rs +++ b/crates/config/src/invariant.rs @@ -48,7 +48,7 @@ impl Default for InvariantConfig { max_assume_rejects: 65536, gas_report_samples: 256, failure_persist_dir: None, - show_metrics: false, + show_metrics: true, timeout: None, show_solidity: false, } @@ -68,7 +68,7 @@ impl InvariantConfig { max_assume_rejects: 65536, gas_report_samples: 256, failure_persist_dir: Some(cache_dir), - show_metrics: false, + show_metrics: true, timeout: None, show_solidity: false, } diff --git a/crates/config/src/lib.rs b/crates/config/src/lib.rs index c78bde6d8d8e7..1222b4703fa38 100644 --- a/crates/config/src/lib.rs +++ b/crates/config/src/lib.rs @@ -9,7 +9,7 @@ extern crate tracing; use crate::cache::StorageCachingConfig; -use alloy_primitives::{address, Address, B256, U256}; +use alloy_primitives::{address, map::AddressHashMap, Address, FixedBytes, B256, U256}; use eyre::{ContextCompat, WrapErr}; use figment::{ providers::{Env, Format, Serialized, Toml}, @@ -21,7 +21,7 @@ use foundry_compilers::{ artifacts::{ output_selection::{ContractOutputSelection, OutputSelection}, remappings::{RelativeRemapping, Remapping}, - serde_helpers, BytecodeHash, DebuggingSettings, EofVersion, EvmVersion, Libraries, + serde_helpers, BytecodeHash, DebuggingSettings, EvmVersion, Libraries, ModelCheckerSettings, ModelCheckerTarget, Optimizer, OptimizerDetails, RevertStrings, Settings, SettingsMetadata, Severity, }, @@ -39,7 +39,7 @@ use foundry_compilers::{ RestrictionsWithVersion, VyperLanguage, }; use regex::Regex; -use revm_primitives::{map::AddressHashMap, FixedBytes, SpecId}; +use revm::primitives::hardfork::SpecId; use semver::Version; use serde::{Deserialize, Serialize, Serializer}; use std::{ @@ -74,6 +74,9 @@ use cache::{Cache, ChainCache}; pub mod fmt; pub use fmt::FormatterConfig; +pub mod lint; +pub use lint::{LinterConfig, Severity as LintSeverity}; + pub mod fs_permissions; pub use fs_permissions::FsPermissions; use fs_permissions::PathPermission; @@ -96,6 +99,7 @@ pub mod fix; // reexport so cli types can implement `figment::Provider` to easily merge compiler arguments pub use alloy_chains::{Chain, NamedChain}; pub use figment; +use foundry_block_explorers::EtherscanApiVersion; pub mod providers; pub use providers::Remappings; @@ -114,7 +118,7 @@ pub mod soldeer; use soldeer::{SoldeerConfig, SoldeerDependencyConfig}; mod vyper; -use vyper::VyperConfig; +pub use vyper::{normalize_evm_version_vyper, VyperConfig}; mod bind_json; use bind_json::BindJsonConfig; @@ -281,6 +285,8 @@ pub struct Config { pub eth_rpc_headers: Option>, /// etherscan API key, or alias for an `EtherscanConfig` in `etherscan` table pub etherscan_api_key: Option, + /// etherscan API version + pub etherscan_api_version: Option, /// Multiple etherscan api configs and their aliases #[serde(default, skip_serializing_if = "EtherscanConfigs::is_empty")] pub etherscan: EtherscanConfigs, @@ -446,6 +452,8 @@ pub struct Config { pub build_info_path: Option, /// Configuration for `forge fmt` pub fmt: FormatterConfig, + /// Configuration for `forge lint` + pub lint: LinterConfig, /// Configuration for `forge doc` pub doc: DocConfig, /// Configuration for `forge bind-json` @@ -497,10 +505,6 @@ pub struct Config { #[serde(default, skip_serializing_if = "Vec::is_empty")] pub extra_args: Vec, - /// Optional EOF version. - #[serde(default, skip_serializing_if = "Option::is_none")] - pub eof_version: Option, - /// Whether to enable Odyssey features. #[serde(alias = "alphanet")] pub odyssey: bool, @@ -508,9 +512,6 @@ pub struct Config { /// Timeout for transactions in seconds. pub transaction_timeout: u64, - /// Use EOF-enabled solc for compilation. - pub eof: bool, - /// Warnings gathered when loading the Config. See [`WarningsProvider`] for more information. #[serde(rename = "__warnings", default, skip_serializing)] pub warnings: Vec, @@ -562,6 +563,7 @@ impl Config { "rpc_endpoints", "etherscan", "fmt", + "lint", "doc", "fuzz", "invariant", @@ -880,8 +882,6 @@ impl Config { config.libs.sort_unstable(); config.libs.dedup(); - config.sanitize_eof_settings(); - config } @@ -899,26 +899,6 @@ impl Config { } } - /// Adjusts settings if EOF compilation is enabled. - /// - /// This includes enabling optimizer, via_ir, eof_version and ensuring that evm_version is not - /// lower than Osaka. - pub fn sanitize_eof_settings(&mut self) { - if self.eof { - self.optimizer = Some(true); - self.normalize_optimizer_settings(); - - if self.eof_version.is_none() { - self.eof_version = Some(EofVersion::V1); - } - - self.via_ir = true; - if self.evm_version < EvmVersion::Osaka { - self.evm_version = EvmVersion::Osaka; - } - } - } - /// Returns the directory in which dependencies should be installed /// /// Returns the first dir from `libs` that is not `node_modules` or `lib` if `libs` is empty @@ -1377,17 +1357,23 @@ impl Config { &self, chain: Option, ) -> Result, EtherscanConfigError> { + let default_api_version = self.etherscan_api_version.unwrap_or_default(); + if let Some(maybe_alias) = self.etherscan_api_key.as_ref().or(self.eth_rpc_url.as_ref()) { if self.etherscan.contains_key(maybe_alias) { - return self.etherscan.clone().resolved().remove(maybe_alias).transpose(); + return self + .etherscan + .clone() + .resolved(default_api_version) + .remove(maybe_alias) + .transpose(); } } // try to find by comparing chain IDs after resolving - if let Some(res) = chain - .or(self.chain) - .and_then(|chain| self.etherscan.clone().resolved().find_chain(chain)) - { + if let Some(res) = chain.or(self.chain).and_then(|chain| { + self.etherscan.clone().resolved(default_api_version).find_chain(chain) + }) { match (res, self.etherscan_api_key.as_ref()) { (Ok(mut config), Some(key)) => { // we update the key, because if an etherscan_api_key is set, it should take @@ -1405,8 +1391,11 @@ impl Config { // etherscan fallback via API key if let Some(key) = self.etherscan_api_key.as_ref() { - let chain = chain.or(self.chain).unwrap_or_default(); - return Ok(ResolvedEtherscanConfig::create(key, chain)); + return Ok(ResolvedEtherscanConfig::create( + key, + chain.or(self.chain).unwrap_or_default(), + default_api_version, + )); } Ok(None) @@ -1421,6 +1410,17 @@ impl Config { self.get_etherscan_config_with_chain(chain).ok().flatten().map(|c| c.key) } + /// Helper function to get the API version. + /// + /// See also [Self::get_etherscan_config_with_chain] + pub fn get_etherscan_api_version(&self, chain: Option) -> EtherscanApiVersion { + self.get_etherscan_config_with_chain(chain) + .ok() + .flatten() + .map(|c| c.api_version) + .unwrap_or_default() + } + /// Returns the remapping for the project's _src_ directory /// /// **Note:** this will add an additional `/=` remapping here so imports that @@ -1478,7 +1478,7 @@ impl Config { extra_output.push(ContractOutputSelection::Metadata); } - ConfigurableArtifacts::new(extra_output, self.extra_output_files.iter().cloned()) + ConfigurableArtifacts::new(extra_output, self.extra_output_files.iter().copied()) } /// Parses all libraries in the form of @@ -1530,7 +1530,6 @@ impl Config { remappings: Vec::new(), // Set with `with_extra_output` below. output_selection: Default::default(), - eof_version: self.eof_version, } .with_extra_output(self.configured_artifacts_handler().output_selection()); @@ -1549,7 +1548,7 @@ impl Config { /// - evm version pub fn vyper_settings(&self) -> Result { Ok(VyperSettings { - evm_version: Some(self.evm_version), + evm_version: Some(normalize_evm_version_vyper(self.evm_version)), optimize: self.vyper.optimize, bytecode_metadata: None, // TODO: We don't yet have a way to deserialize other outputs correctly, so request only @@ -2315,7 +2314,7 @@ impl Default for Config { allow_paths: vec![], include_paths: vec![], force: false, - evm_version: EvmVersion::Cancun, + evm_version: EvmVersion::Prague, gas_reports: vec!["*".to_string()], gas_reports_ignore: vec![], gas_reports_include_tests: false, @@ -2369,6 +2368,7 @@ impl Default for Config { eth_rpc_timeout: None, eth_rpc_headers: None, etherscan_api_key: None, + etherscan_api_version: None, verbosity: 0, remappings: vec![], auto_detect_remappings: true, @@ -2396,6 +2396,7 @@ impl Default for Config { build_info: false, build_info_path: None, fmt: Default::default(), + lint: Default::default(), doc: Default::default(), bind_json: Default::default(), labels: Default::default(), @@ -2409,12 +2410,10 @@ impl Default for Config { legacy_assertions: false, warnings: vec![], extra_args: vec![], - eof_version: None, odyssey: false, transaction_timeout: 120, additional_compiler_profiles: Default::default(), compilation_restrictions: Default::default(), - eof: false, script_execution_protection: true, _non_exhaustive: (), } @@ -2570,7 +2569,7 @@ mod tests { use super::*; use crate::{ cache::{CachedChains, CachedEndpoints}, - endpoints::{RpcEndpoint, RpcEndpointType}, + endpoints::RpcEndpointType, etherscan::ResolvedEtherscanConfigs, }; use endpoints::{RpcAuth, RpcEndpointConfig}; @@ -2580,7 +2579,7 @@ mod tests { }; use similar_asserts::assert_eq; use soldeer_core::remappings::RemappingsLocation; - use std::{collections::BTreeMap, fs::File, io::Write}; + use std::{fs::File, io::Write}; use tempfile::tempdir; use NamedChain::Moonbeam; @@ -3063,11 +3062,66 @@ mod tests { let config = Config::load().unwrap(); - assert!(config.etherscan.clone().resolved().has_unresolved()); + assert!(config.etherscan.clone().resolved(EtherscanApiVersion::V2).has_unresolved()); + + jail.set_env("_CONFIG_ETHERSCAN_MOONBEAM", "123456789"); + + let configs = config.etherscan.resolved(EtherscanApiVersion::V2); + assert!(!configs.has_unresolved()); + + let mb_urls = Moonbeam.etherscan_urls().unwrap(); + let mainnet_urls = NamedChain::Mainnet.etherscan_urls().unwrap(); + assert_eq!( + configs, + ResolvedEtherscanConfigs::new([ + ( + "mainnet", + ResolvedEtherscanConfig { + api_url: mainnet_urls.0.to_string(), + chain: Some(NamedChain::Mainnet.into()), + browser_url: Some(mainnet_urls.1.to_string()), + api_version: EtherscanApiVersion::V2, + key: "FX42Z3BBJJEWXWGYV2X1CIPRSCN".to_string(), + } + ), + ( + "moonbeam", + ResolvedEtherscanConfig { + api_url: mb_urls.0.to_string(), + chain: Some(Moonbeam.into()), + browser_url: Some(mb_urls.1.to_string()), + api_version: EtherscanApiVersion::V2, + key: "123456789".to_string(), + } + ), + ]) + ); + + Ok(()) + }); + } + + #[test] + fn test_resolve_etherscan_with_versions() { + figment::Jail::expect_with(|jail| { + jail.create_file( + "foundry.toml", + r#" + [profile.default] + + [etherscan] + mainnet = { key = "FX42Z3BBJJEWXWGYV2X1CIPRSCN", api_version = "v2" } + moonbeam = { key = "${_CONFIG_ETHERSCAN_MOONBEAM}", api_version = "v1" } + "#, + )?; + + let config = Config::load().unwrap(); + + assert!(config.etherscan.clone().resolved(EtherscanApiVersion::V2).has_unresolved()); jail.set_env("_CONFIG_ETHERSCAN_MOONBEAM", "123456789"); - let configs = config.etherscan.resolved(); + let configs = config.etherscan.resolved(EtherscanApiVersion::V2); assert!(!configs.has_unresolved()); let mb_urls = Moonbeam.etherscan_urls().unwrap(); @@ -3081,6 +3135,7 @@ mod tests { api_url: mainnet_urls.0.to_string(), chain: Some(NamedChain::Mainnet.into()), browser_url: Some(mainnet_urls.1.to_string()), + api_version: EtherscanApiVersion::V2, key: "FX42Z3BBJJEWXWGYV2X1CIPRSCN".to_string(), } ), @@ -3090,6 +3145,7 @@ mod tests { api_url: mb_urls.0.to_string(), chain: Some(Moonbeam.into()), browser_url: Some(mb_urls.1.to_string()), + api_version: EtherscanApiVersion::V1, key: "123456789".to_string(), } ), @@ -4437,6 +4493,31 @@ mod tests { }); } + #[test] + fn test_lint_config() { + figment::Jail::expect_with(|jail| { + jail.create_file( + "foundry.toml", + r" + [lint] + severity = ['high', 'medium'] + exclude_lints = ['incorrect-shift'] + ", + )?; + let loaded = Config::load().unwrap().sanitized(); + assert_eq!( + loaded.lint, + LinterConfig { + severity: vec![LintSeverity::High, LintSeverity::Med], + exclude_lints: vec!["incorrect-shift".into()], + ..Default::default() + } + ); + + Ok(()) + }); + } + #[test] fn test_invariant_config() { figment::Jail::expect_with(|jail| { diff --git a/crates/config/src/lint.rs b/crates/config/src/lint.rs new file mode 100644 index 0000000000000..ec9b2b4b00748 --- /dev/null +++ b/crates/config/src/lint.rs @@ -0,0 +1,108 @@ +//! Configuration specific to the `forge lint` command and the `forge_lint` package + +use clap::ValueEnum; +use core::fmt; +use serde::{Deserialize, Deserializer, Serialize}; +use solar_interface::diagnostics::Level; +use std::str::FromStr; +use yansi::Paint; + +/// Contains the config and rule set +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +pub struct LinterConfig { + /// Specifies which lints to run based on severity. + /// + /// If uninformed, all severities are checked. + pub severity: Vec, + + /// Deny specific lints based on their ID (e.g. "mixed-case-function"). + pub exclude_lints: Vec, + + /// Globs to ignore + pub ignore: Vec, + + /// Whether to run linting during `forge build`. + /// + /// Defaults to true. Set to false to disable automatic linting during builds. + pub lint_on_build: bool, +} +impl Default for LinterConfig { + fn default() -> Self { + Self { + lint_on_build: true, + severity: Vec::new(), + exclude_lints: Vec::new(), + ignore: Vec::new(), + } + } +} + +/// Severity of a lint +#[derive(Debug, Clone, Copy, PartialEq, Eq, ValueEnum, Serialize)] +pub enum Severity { + High, + Med, + Low, + Info, + Gas, +} + +impl Severity { + pub fn color(&self, message: &str) -> String { + match self { + Self::High => Paint::red(message).bold().to_string(), + Self::Med => Paint::rgb(message, 255, 135, 61).bold().to_string(), + Self::Low => Paint::yellow(message).bold().to_string(), + Self::Info => Paint::cyan(message).bold().to_string(), + Self::Gas => Paint::green(message).bold().to_string(), + } + } +} + +impl From for Level { + fn from(severity: Severity) -> Self { + match severity { + Severity::High | Severity::Med | Severity::Low => Self::Warning, + Severity::Info | Severity::Gas => Self::Note, + } + } +} + +impl fmt::Display for Severity { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let colored = match self { + Self::High => self.color("High"), + Self::Med => self.color("Med"), + Self::Low => self.color("Low"), + Self::Info => self.color("Info"), + Self::Gas => self.color("Gas"), + }; + write!(f, "{colored}") + } +} + +// Custom deserialization to make `Severity` parsing case-insensitive +impl<'de> Deserialize<'de> for Severity { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let s = String::deserialize(deserializer)?; + FromStr::from_str(&s).map_err(serde::de::Error::custom) + } +} + +impl FromStr for Severity { + type Err = String; + + fn from_str(s: &str) -> Result { + match s.to_lowercase().as_str() { + "high" => Ok(Self::High), + "med" | "medium" => Ok(Self::Med), + "low" => Ok(Self::Low), + "info" => Ok(Self::Info), + "gas" => Ok(Self::Gas), + _ => Err(format!("unknown variant: found `{s}`, expected `one of `High`, `Med`, `Low`, `Info`, `Gas``")), + } + } +} diff --git a/crates/config/src/utils.rs b/crates/config/src/utils.rs index 86b25d1043797..bcb20ab698afb 100644 --- a/crates/config/src/utils.rs +++ b/crates/config/src/utils.rs @@ -7,7 +7,7 @@ use foundry_compilers::artifacts::{ remappings::{Remapping, RemappingError}, EvmVersion, }; -use revm_primitives::SpecId; +use revm::primitives::hardfork::SpecId; use serde::{de::Error, Deserialize, Deserializer}; use std::{ io, diff --git a/crates/config/src/vyper.rs b/crates/config/src/vyper.rs index dbd47faec208d..12596e3bce401 100644 --- a/crates/config/src/vyper.rs +++ b/crates/config/src/vyper.rs @@ -1,6 +1,6 @@ //! Vyper specific configuration types. -use foundry_compilers::artifacts::vyper::VyperOptimizationMode; +use foundry_compilers::artifacts::{vyper::VyperOptimizationMode, EvmVersion}; use serde::{Deserialize, Serialize}; use std::path::PathBuf; @@ -16,3 +16,13 @@ pub struct VyperConfig { #[serde(default, skip_serializing_if = "Option::is_none")] pub experimental_codegen: Option, } + +/// Vyper does not yet support the Prague EVM version, so we normalize it to Cancun. +/// This is a temporary workaround until Vyper supports Prague. +pub fn normalize_evm_version_vyper(evm_version: EvmVersion) -> EvmVersion { + if evm_version >= EvmVersion::Prague { + return EvmVersion::Cancun; + } + + evm_version +} diff --git a/crates/debugger/src/dump.rs b/crates/debugger/src/dump.rs index 83af7b0e777f7..2d50b4079ed4b 100644 --- a/crates/debugger/src/dump.rs +++ b/crates/debugger/src/dump.rs @@ -5,7 +5,7 @@ use foundry_compilers::{ artifacts::sourcemap::{Jump, SourceElement}, multi::MultiCompilerLanguage, }; -use foundry_evm_core::utils::PcIcMap; +use foundry_evm_core::ic::PcIcMap; use foundry_evm_traces::debug::{ArtifactData, ContractSources, SourceData}; use serde::Serialize; use std::{collections::HashMap, path::Path}; diff --git a/crates/debugger/src/op.rs b/crates/debugger/src/op.rs index bc8e96ccb3997..8abf33d6f533d 100644 --- a/crates/debugger/src/op.rs +++ b/crates/debugger/src/op.rs @@ -1,6 +1,3 @@ -use alloy_primitives::Bytes; -use revm::interpreter::opcode; - /// Named parameter of an EVM opcode. #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] pub(crate) struct OpcodeParam { @@ -14,32 +11,8 @@ impl OpcodeParam { /// Returns the list of named parameters for the given opcode, accounts for special opcodes /// requiring immediate bytes to determine stack items. #[inline] - pub(crate) fn of(op: u8, immediate: Option<&Bytes>) -> Option> { - match op { - // Handle special cases requiring immediate bytes - opcode::DUPN => immediate - .and_then(|i| i.first().copied()) - .map(|i| vec![Self { name: "dup_value", index: i as usize }]), - opcode::SWAPN => immediate.and_then(|i| { - i.first().map(|i| { - vec![ - Self { name: "a", index: 1 }, - Self { name: "swap_value", index: *i as usize }, - ] - }) - }), - opcode::EXCHANGE => immediate.and_then(|i| { - i.first().map(|imm| { - let n = (imm >> 4) + 1; - let m = (imm & 0xf) + 1; - vec![ - Self { name: "value1", index: n as usize }, - Self { name: "value2", index: m as usize }, - ] - }) - }), - _ => Some(MAP[op as usize].to_vec()), - } + pub(crate) fn of(op: u8) -> &'static [Self] { + MAP[op as usize] } } @@ -68,16 +41,21 @@ const fn map_opcode(op: u8) -> &'static [OpcodeParam] { } // https://www.evm.codes - // https://github.com/smlxl/evm.codes - // https://github.com/klkvr/evm.codes - // https://github.com/klkvr/evm.codes/blob/HEAD/opcodes.json - // jq -rf opcodes.jq opcodes.json - /* - def mkargs(input): - input | split(" | ") | to_entries | map("\(.key): \"\(.value)\"") | join(", "); - - to_entries[] | "0x\(.key)(\(mkargs(.value.input)))," - */ + // https://raw.githubusercontent.com/duneanalytics/evm.codes/refs/heads/main/opcodes.json + // + // jq -r ' + // def mkargs(input): + // input + // | split(" | ") + // | to_entries + // | map("\(.key): \"\(.value)\"") + // | join(", "); + // to_entries[] + // | "0x\(.key)(\(mkargs(.value.input)))," + // ' opcodes.json + // + // NOTE: the labels generated for `DUPN` and `SWAPN` have incorrect indices and have been + // manually adjusted in the `map!` macro below. map! { 0x00(), 0x01(0: "a", 1: "b"), @@ -152,7 +130,7 @@ const fn map_opcode(op: u8) -> &'static [OpcodeParam] { 0x46(), 0x47(), 0x48(), - 0x49(), + 0x49(0: "index"), 0x4a(), 0x4b(), 0x4c(), @@ -171,11 +149,9 @@ const fn map_opcode(op: u8) -> &'static [OpcodeParam] { 0x59(), 0x5a(), 0x5b(), - 0x5c(), - 0x5d(), - 0x5e(), - - // PUSHN + 0x5c(0: "key"), + 0x5d(0: "key", 1: "value"), + 0x5e(0: "destOffset", 1: "offset", 2: "size"), 0x5f(), 0x60(), 0x61(), @@ -297,7 +273,7 @@ const fn map_opcode(op: u8) -> &'static [OpcodeParam] { 0xd0(0: "offset"), 0xd1(), 0xd2(), - 0xd3(0: "memOffset", 1: "offset", 2: "size"), + 0xd3(0: "mem_offset", 1: "offset", 2: "size"), 0xd4(), 0xd5(), 0xd6(), @@ -322,9 +298,9 @@ const fn map_opcode(op: u8) -> &'static [OpcodeParam] { 0xe9(), 0xea(), 0xeb(), - 0xec(0: "value", 1: "salt", 2: "offset", 3: "size"), + 0xec(0: "value", 1: "salt", 2: "input_offset", 3: "input_size"), 0xed(), - 0xee(0: "offset", 1: "size"), + 0xee(0: "aux_data_offset", 1: "aux_data_size"), 0xef(), 0xf0(0: "value", 1: "offset", 2: "size"), 0xf1(0: "gas", 1: "address", 2: "value", 3: "argsOffset", 4: "argsSize", 5: "retOffset", 6: "retSize"), @@ -334,10 +310,10 @@ const fn map_opcode(op: u8) -> &'static [OpcodeParam] { 0xf5(0: "value", 1: "offset", 2: "size", 3: "salt"), 0xf6(), 0xf7(0: "offset"), - 0xf8(0: "address", 1: "argsOffset", 2: "argsSize", 3: "value"), - 0xf9(0: "address", 1: "argsOffset", 2: "argsSize"), + 0xf8(0: "target_address", 1: "input_offset", 2: "input_size", 3: "value"), + 0xf9(0: "target_address", 1: "input_offset", 2: "input_size"), 0xfa(0: "gas", 1: "address", 2: "argsOffset", 3: "argsSize", 4: "retOffset", 5: "retSize"), - 0xfb(0: "address", 1: "argsOffset", 2: "argsSize"), + 0xfb(0: "target_address", 1: "input_offset", 2: "input_size"), 0xfc(), 0xfd(0: "offset", 1: "size"), 0xfe(), diff --git a/crates/debugger/src/tui/context.rs b/crates/debugger/src/tui/context.rs index 0c61a1fcd41c4..cf4c6204b79bb 100644 --- a/crates/debugger/src/tui/context.rs +++ b/crates/debugger/src/tui/context.rs @@ -4,7 +4,7 @@ use crate::{debugger::DebuggerContext, DebugNode, ExitReason}; use alloy_primitives::{hex, Address}; use crossterm::event::{Event, KeyCode, KeyEvent, KeyModifiers, MouseEvent, MouseEventKind}; use foundry_evm_core::buffer::BufferKind; -use revm::interpreter::OpCode; +use revm::bytecode::opcode::OpCode; use revm_inspectors::tracing::types::{CallKind, CallTraceStep}; use std::ops::ControlFlow; @@ -330,25 +330,11 @@ fn pretty_opcode(step: &CallTraceStep) -> String { } fn is_jump(step: &CallTraceStep, prev: &CallTraceStep) -> bool { - if !matches!( - prev.op, - OpCode::JUMP | - OpCode::JUMPI | - OpCode::JUMPF | - OpCode::RJUMP | - OpCode::RJUMPI | - OpCode::RJUMPV | - OpCode::CALLF | - OpCode::RETF - ) { + if !matches!(prev.op, OpCode::JUMP | OpCode::JUMPI) { return false } let immediate_len = prev.immediate_bytes.as_ref().map_or(0, |b| b.len()); - if step.pc != prev.pc + 1 + immediate_len { - true - } else { - step.code_section_idx != prev.code_section_idx - } + step.pc != prev.pc + 1 + immediate_len } diff --git a/crates/debugger/src/tui/draw.rs b/crates/debugger/src/tui/draw.rs index a918bc89ea733..00f51c4c5654c 100644 --- a/crates/debugger/src/tui/draw.rs +++ b/crates/debugger/src/tui/draw.rs @@ -199,7 +199,6 @@ impl TUIContext<'_> { CallKind::CallCode => "Contract callcode", CallKind::DelegateCall => "Contract delegatecall", CallKind::AuthCall => "Contract authcall", - CallKind::EOFCreate => "EOF contract creation", }; let title = format!( "{} {} ", @@ -376,11 +375,10 @@ impl TUIContext<'_> { .collect::>(); let title = format!( - "Address: {} | PC: {} | Gas used in call: {} | Code section: {}", + "Address: {} | PC: {} | Gas used in call: {}", self.address(), self.current_step().pc, self.current_step().gas_used, - self.current_step().code_section_idx, ); let block = Block::default().title(title).borders(Borders::ALL); let list = List::new(items) @@ -399,7 +397,7 @@ impl TUIContext<'_> { let min_len = decimal_digits(stack_len).max(2); - let params = OpcodeParam::of(step.op.get(), step.immediate_bytes.as_ref()); + let params = OpcodeParam::of(step.op.get()); let text: Vec> = stack .map(|stack| { @@ -409,10 +407,7 @@ impl TUIContext<'_> { .enumerate() .skip(self.draw_memory.current_stack_startline) .map(|(i, stack_item)| { - let param = params - .as_ref() - .and_then(|params| params.iter().find(|param| param.index == i)); - + let param = params.iter().find(|param| param.index == i); let mut spans = Vec::with_capacity(1 + 32 * 2 + 3); // Stack index. diff --git a/crates/doc/src/parser/comment.rs b/crates/doc/src/parser/comment.rs index bf2b0ad7b4f0d..9a64662665c31 100644 --- a/crates/doc/src/parser/comment.rs +++ b/crates/doc/src/parser/comment.rs @@ -170,13 +170,13 @@ impl<'a> CommentsRef<'a> { /// Filter a collection of comments and return only those that match provided tags. pub fn include_tags(&self, tags: &[CommentTag]) -> Self { // Cloning only references here - CommentsRef(self.iter().cloned().filter(|c| tags.contains(&c.tag)).collect()) + CommentsRef(self.iter().copied().filter(|c| tags.contains(&c.tag)).collect()) } /// Filter a collection of comments and return only those that do not match provided tags. pub fn exclude_tags(&self, tags: &[CommentTag]) -> Self { // Cloning only references here - CommentsRef(self.iter().cloned().filter(|c| !tags.contains(&c.tag)).collect()) + CommentsRef(self.iter().copied().filter(|c| !tags.contains(&c.tag)).collect()) } /// Check if the collection contains a target comment. @@ -200,7 +200,7 @@ impl<'a> CommentsRef<'a> { /// Filter a collection of comments and only return the custom tags. pub fn get_custom_tags(&self) -> Self { - CommentsRef(self.iter().cloned().filter(|c| c.is_custom()).collect()) + CommentsRef(self.iter().copied().filter(|c| c.is_custom()).collect()) } } diff --git a/crates/evm/core/Cargo.toml b/crates/evm/core/Cargo.toml index 5730e6fb3ab4a..45097ca47f1fa 100644 --- a/crates/evm/core/Cargo.toml +++ b/crates/evm/core/Cargo.toml @@ -20,6 +20,7 @@ foundry-config.workspace = true foundry-evm-abi.workspace = true alloy-dyn-abi = { workspace = true, features = ["arbitrary", "eip712"] } +alloy-evm.workspace = true alloy-genesis.workspace = true alloy-json-abi.workspace = true alloy-primitives = { workspace = true, features = [ @@ -43,11 +44,13 @@ revm = { workspace = true, features = [ "optional_block_gas_limit", "optional_no_base_fee", "arbitrary", - "optimism", "c-kzg", "blst", + "secp256r1", ] } revm-inspectors.workspace = true +op-revm.workspace = true +alloy-op-evm.workspace = true auto_impl.workspace = true eyre.workspace = true diff --git a/crates/evm/core/src/backend/cow.rs b/crates/evm/core/src/backend/cow.rs index 3ffb9fc84f3cd..4220150e6536b 100644 --- a/crates/evm/core/src/backend/cow.rs +++ b/crates/evm/core/src/backend/cow.rs @@ -3,23 +3,25 @@ use super::BackendError; use crate::{ backend::{ - diagnostic::RevertDiagnostic, Backend, DatabaseExt, LocalForkId, RevertStateSnapshotAction, + diagnostic::RevertDiagnostic, Backend, DatabaseExt, JournaledState, LocalForkId, + RevertStateSnapshotAction, }, fork::{CreateFork, ForkId}, - InspectorExt, + AsEnvMut, Env, EnvMut, InspectorExt, }; +use alloy_evm::Evm; use alloy_genesis::GenesisAccount; use alloy_primitives::{Address, B256, U256}; use alloy_rpc_types::TransactionRequest; use eyre::WrapErr; use foundry_fork_db::DatabaseError; use revm::{ - db::DatabaseRef, - primitives::{ - Account, AccountInfo, Bytecode, Env, EnvWithHandlerCfg, HashMap as Map, ResultAndState, - SpecId, - }, - Database, DatabaseCommit, JournaledState, + bytecode::Bytecode, + context_interface::result::ResultAndState, + database::DatabaseRef, + primitives::{hardfork::SpecId, HashMap as Map}, + state::{Account, AccountInfo}, + Database, DatabaseCommit, }; use std::{borrow::Cow, collections::BTreeMap}; @@ -54,7 +56,7 @@ pub struct CowBackend<'a> { impl<'a> CowBackend<'a> { /// Creates a new `CowBackend` with the given `Backend`. pub fn new_borrowed(backend: &'a Backend) -> Self { - Self { backend: Cow::Borrowed(backend), is_initialized: false, spec_id: SpecId::LATEST } + Self { backend: Cow::Borrowed(backend), is_initialized: false, spec_id: SpecId::default() } } /// Executes the configured transaction of the `env` without committing state changes @@ -64,18 +66,19 @@ impl<'a> CowBackend<'a> { #[instrument(name = "inspect", level = "debug", skip_all)] pub fn inspect( &mut self, - env: &mut EnvWithHandlerCfg, + env: &mut Env, inspector: &mut I, ) -> eyre::Result { // this is a new call to inspect with a new env, so even if we've cloned the backend // already, we reset the initialized state self.is_initialized = false; - self.spec_id = env.handler_cfg.spec_id; - let mut evm = crate::utils::new_evm_with_inspector(self, env.clone(), inspector); + self.spec_id = env.evm_env.cfg_env.spec; + + let mut evm = crate::evm::new_evm_with_inspector(self, env.to_owned(), inspector); - let res = evm.transact().wrap_err("EVM error")?; + let res = evm.transact(env.tx.clone()).wrap_err("EVM error")?; - env.env = evm.context.evm.inner.env; + *env = evm.as_env_mut().to_owned(); Ok(res) } @@ -90,10 +93,11 @@ impl<'a> CowBackend<'a> { /// Returns a mutable instance of the Backend. /// /// If this is the first time this is called, the backed is cloned and initialized. - fn backend_mut(&mut self, env: &Env) -> &mut Backend { + fn backend_mut(&mut self, env: &EnvMut<'_>) -> &mut Backend { if !self.is_initialized { let backend = self.backend.to_mut(); - let env = EnvWithHandlerCfg::new_with_spec_id(Box::new(env.clone()), self.spec_id); + let mut env = env.to_owned(); + env.evm_env.cfg_env.spec = self.spec_id; backend.initialize(&env); self.is_initialized = true; return backend @@ -111,7 +115,7 @@ impl<'a> CowBackend<'a> { } impl DatabaseExt for CowBackend<'_> { - fn snapshot_state(&mut self, journaled_state: &JournaledState, env: &Env) -> U256 { + fn snapshot_state(&mut self, journaled_state: &JournaledState, env: &mut EnvMut<'_>) -> U256 { self.backend_mut(env).snapshot_state(journaled_state, env) } @@ -119,7 +123,7 @@ impl DatabaseExt for CowBackend<'_> { &mut self, id: U256, journaled_state: &JournaledState, - current: &mut Env, + current: &mut EnvMut<'_>, action: RevertStateSnapshotAction, ) -> Option { self.backend_mut(current).revert_state(id, journaled_state, current, action) @@ -154,7 +158,7 @@ impl DatabaseExt for CowBackend<'_> { fn select_fork( &mut self, id: LocalForkId, - env: &mut Env, + env: &mut EnvMut<'_>, journaled_state: &mut JournaledState, ) -> eyre::Result<()> { self.backend_mut(env).select_fork(id, env, journaled_state) @@ -164,7 +168,7 @@ impl DatabaseExt for CowBackend<'_> { &mut self, id: Option, block_number: u64, - env: &mut Env, + env: &mut EnvMut<'_>, journaled_state: &mut JournaledState, ) -> eyre::Result<()> { self.backend_mut(env).roll_fork(id, block_number, env, journaled_state) @@ -174,7 +178,7 @@ impl DatabaseExt for CowBackend<'_> { &mut self, id: Option, transaction: B256, - env: &mut Env, + env: &mut EnvMut<'_>, journaled_state: &mut JournaledState, ) -> eyre::Result<()> { self.backend_mut(env).roll_fork_to_transaction(id, transaction, env, journaled_state) @@ -184,21 +188,32 @@ impl DatabaseExt for CowBackend<'_> { &mut self, id: Option, transaction: B256, - env: Env, + mut env: Env, journaled_state: &mut JournaledState, inspector: &mut dyn InspectorExt, ) -> eyre::Result<()> { - self.backend_mut(&env).transact(id, transaction, env, journaled_state, inspector) + self.backend_mut(&env.as_env_mut()).transact( + id, + transaction, + env, + journaled_state, + inspector, + ) } fn transact_from_tx( &mut self, transaction: &TransactionRequest, - env: Env, + mut env: Env, journaled_state: &mut JournaledState, inspector: &mut dyn InspectorExt, ) -> eyre::Result<()> { - self.backend_mut(&env).transact_from_tx(transaction, env, journaled_state, inspector) + self.backend_mut(&env.as_env_mut()).transact_from_tx( + transaction, + env, + journaled_state, + inspector, + ) } fn active_fork_id(&self) -> Option { @@ -230,7 +245,7 @@ impl DatabaseExt for CowBackend<'_> { allocs: &BTreeMap, journaled_state: &mut JournaledState, ) -> Result<(), BackendError> { - self.backend_mut(&Env::default()).load_allocs(allocs, journaled_state) + self.backend_mut(&Env::default().as_env_mut()).load_allocs(allocs, journaled_state) } fn clone_account( @@ -239,7 +254,11 @@ impl DatabaseExt for CowBackend<'_> { target: &Address, journaled_state: &mut JournaledState, ) -> Result<(), BackendError> { - self.backend_mut(&Env::default()).clone_account(source, target, journaled_state) + self.backend_mut(&Env::default().as_env_mut()).clone_account( + source, + target, + journaled_state, + ) } fn is_persistent(&self, acc: &Address) -> bool { diff --git a/crates/evm/core/src/backend/error.rs b/crates/evm/core/src/backend/error.rs index 1dabeafded152..42456cceadd16 100644 --- a/crates/evm/core/src/backend/error.rs +++ b/crates/evm/core/src/backend/error.rs @@ -1,6 +1,6 @@ use alloy_primitives::Address; pub use foundry_fork_db::{DatabaseError, DatabaseResult}; -use revm::primitives::EVMError; +use revm::context_interface::result::EVMError; use std::convert::Infallible; pub type BackendResult = Result; @@ -59,7 +59,6 @@ impl> From> for BackendError { EVMError::Database(err) => err.into(), EVMError::Custom(err) => Self::msg(err), EVMError::Header(err) => Self::msg(err.to_string()), - EVMError::Precompile(err) => Self::msg(err), EVMError::Transaction(err) => Self::msg(err.to_string()), } } diff --git a/crates/evm/core/src/backend/in_memory_db.rs b/crates/evm/core/src/backend/in_memory_db.rs index c1956f96eb54f..0ffa718fb3671 100644 --- a/crates/evm/core/src/backend/in_memory_db.rs +++ b/crates/evm/core/src/backend/in_memory_db.rs @@ -4,8 +4,10 @@ use crate::state_snapshot::StateSnapshots; use alloy_primitives::{Address, B256, U256}; use foundry_fork_db::DatabaseError; use revm::{ - db::{CacheDB, DatabaseRef, EmptyDB}, - primitives::{Account, AccountInfo, Bytecode, HashMap as Map}, + bytecode::Bytecode, + database::{CacheDB, DatabaseRef, EmptyDB}, + primitives::HashMap as Map, + state::{Account, AccountInfo}, Database, DatabaseCommit, }; diff --git a/crates/evm/core/src/backend/mod.rs b/crates/evm/core/src/backend/mod.rs index 24eab386ee495..892bf40c075bf 100644 --- a/crates/evm/core/src/backend/mod.rs +++ b/crates/evm/core/src/backend/mod.rs @@ -2,11 +2,14 @@ use crate::{ constants::{CALLER, CHEATCODE_ADDRESS, DEFAULT_CREATE2_DEPLOYER, TEST_CONTRACT_ADDRESS}, + evm::new_evm_with_inspector, fork::{CreateFork, ForkId, MultiFork}, state_snapshot::StateSnapshots, - utils::{configure_tx_env, configure_tx_req_env, new_evm_with_inspector}, - InspectorExt, + utils::{configure_tx_env, configure_tx_req_env}, + AsEnvMut, Env, EnvMut, InspectorExt, }; +use alloy_consensus::Typed2718; +use alloy_evm::Evm; use alloy_genesis::GenesisAccount; use alloy_network::{AnyRpcBlock, AnyTxEnvelope, TransactionResponse}; use alloy_primitives::{keccak256, uint, Address, TxKind, B256, U256}; @@ -15,14 +18,15 @@ use eyre::Context; use foundry_common::{is_known_system_sender, SYSTEM_TRANSACTION_TYPE}; pub use foundry_fork_db::{cache::BlockchainDbMeta, BlockchainDb, SharedBackend}; use revm::{ - db::{CacheDB, DatabaseRef}, - inspectors::NoOpInspector, + bytecode::Bytecode, + context::JournalInner, + context_interface::{block::BlobExcessGasAndPrice, result::ResultAndState}, + database::{CacheDB, DatabaseRef}, + inspector::NoOpInspector, precompile::{PrecompileSpecId, Precompiles}, - primitives::{ - Account, AccountInfo, BlobExcessGasAndPrice, Bytecode, Env, EnvWithHandlerCfg, EvmState, - EvmStorageSlot, HashMap as Map, Log, ResultAndState, SpecId, KECCAK_EMPTY, - }, - Database, DatabaseCommit, JournaledState, + primitives::{hardfork::SpecId, HashMap as Map, Log, KECCAK_EMPTY}, + state::{Account, AccountInfo, EvmState, EvmStorageSlot}, + Database, DatabaseCommit, JournalEntry, }; use std::{ collections::{BTreeMap, HashMap, HashSet}, @@ -68,6 +72,8 @@ const DEFAULT_PERSISTENT_ACCOUNTS: [Address; 3] = pub const GLOBAL_FAIL_SLOT: U256 = uint!(0x6661696c65640000000000000000000000000000000000000000000000000000_U256); +pub type JournaledState = JournalInner; + /// An extension trait that allows us to easily extend the `revm::Inspector` capabilities #[auto_impl::auto_impl(&mut)] pub trait DatabaseExt: Database + DatabaseCommit { @@ -76,7 +82,7 @@ pub trait DatabaseExt: Database + DatabaseCommit { /// A state snapshot is associated with a new unique id that's created for the snapshot. /// State snapshots can be reverted: [DatabaseExt::revert_state], however, depending on the /// [RevertStateSnapshotAction], it will keep the snapshot alive or delete it. - fn snapshot_state(&mut self, journaled_state: &JournaledState, env: &Env) -> U256; + fn snapshot_state(&mut self, journaled_state: &JournaledState, env: &mut EnvMut<'_>) -> U256; /// Reverts the snapshot if it exists /// @@ -94,7 +100,7 @@ pub trait DatabaseExt: Database + DatabaseCommit { &mut self, id: U256, journaled_state: &JournaledState, - env: &mut Env, + env: &mut EnvMut<'_>, action: RevertStateSnapshotAction, ) -> Option; @@ -113,7 +119,7 @@ pub trait DatabaseExt: Database + DatabaseCommit { fn create_select_fork( &mut self, fork: CreateFork, - env: &mut Env, + env: &mut EnvMut<'_>, journaled_state: &mut JournaledState, ) -> eyre::Result { let id = self.create_fork(fork)?; @@ -127,7 +133,7 @@ pub trait DatabaseExt: Database + DatabaseCommit { fn create_select_fork_at_transaction( &mut self, fork: CreateFork, - env: &mut Env, + env: &mut EnvMut<'_>, journaled_state: &mut JournaledState, transaction: B256, ) -> eyre::Result { @@ -158,7 +164,7 @@ pub trait DatabaseExt: Database + DatabaseCommit { fn select_fork( &mut self, id: LocalForkId, - env: &mut Env, + env: &mut EnvMut<'_>, journaled_state: &mut JournaledState, ) -> eyre::Result<()>; @@ -173,7 +179,7 @@ pub trait DatabaseExt: Database + DatabaseCommit { &mut self, id: Option, block_number: u64, - env: &mut Env, + env: &mut EnvMut<'_>, journaled_state: &mut JournaledState, ) -> eyre::Result<()>; @@ -189,7 +195,7 @@ pub trait DatabaseExt: Database + DatabaseCommit { &mut self, id: Option, transaction: B256, - env: &mut Env, + env: &mut EnvMut<'_>, journaled_state: &mut JournaledState, ) -> eyre::Result<()>; @@ -433,7 +439,7 @@ pub struct Backend { mem_db: FoundryEvmInMemoryDB, /// The journaled_state to use to initialize new forks with /// - /// The way [`revm::JournaledState`] works is, that it holds the "hot" accounts loaded from the + /// The way [`JournaledState`] works is, that it holds the "hot" accounts loaded from the /// underlying `Database` that feeds the Account and State data to the journaled_state so it /// can apply changes to the state while the EVM executes. /// @@ -738,11 +744,11 @@ impl Backend { /// Initializes settings we need to keep track of. /// /// We need to track these mainly to prevent issues when switching between different evms - pub(crate) fn initialize(&mut self, env: &EnvWithHandlerCfg) { + pub(crate) fn initialize(&mut self, env: &Env) { self.set_caller(env.tx.caller); - self.set_spec_id(env.handler_cfg.spec_id); + self.set_spec_id(env.evm_env.cfg_env.spec); - let test_contract = match env.tx.transact_to { + let test_contract = match env.tx.kind { TxKind::Call(to) => to, TxKind::Create => { let nonce = self @@ -755,11 +761,6 @@ impl Backend { self.set_test_contract(test_contract); } - /// Returns the `EnvWithHandlerCfg` with the current `spec_id` set. - fn env_with_handler_cfg(&self, env: Env) -> EnvWithHandlerCfg { - EnvWithHandlerCfg::new_with_spec_id(Box::new(env), self.inner.spec_id) - } - /// Executes the configured test call of the `env` without committing state changes. /// /// Note: in case there are any cheatcodes executed that modify the environment, this will @@ -767,15 +768,15 @@ impl Backend { #[instrument(name = "inspect", level = "debug", skip_all)] pub fn inspect( &mut self, - env: &mut EnvWithHandlerCfg, + env: &mut Env, inspector: &mut I, ) -> eyre::Result { self.initialize(env); - let mut evm = crate::utils::new_evm_with_inspector(self, env.clone(), inspector); + let mut evm = crate::evm::new_evm_with_inspector(self, env.to_owned(), inspector); - let res = evm.transact().wrap_err("EVM error")?; + let res = evm.transact(env.tx.clone()).wrap_err("EVM error")?; - env.env = evm.context.evm.inner.env; + *env = evm.as_env_mut().to_owned(); Ok(res) } @@ -869,7 +870,7 @@ impl Backend { pub fn replay_until( &mut self, id: LocalForkId, - env: Env, + mut env: Env, tx_hash: B256, journaled_state: &mut JournaledState, ) -> eyre::Result>> { @@ -878,15 +879,14 @@ impl Backend { let persistent_accounts = self.inner.persistent_accounts.clone(); let fork_id = self.ensure_fork_id(id)?.clone(); - let env = self.env_with_handler_cfg(env); let fork = self.inner.get_fork_by_id_mut(id)?; - let full_block = fork.db.db.get_full_block(env.block.number.to::())?; + let full_block = fork.db.db.get_full_block(env.evm_env.block_env.number)?; for tx in full_block.inner.transactions.txns() { // System transactions such as on L2s don't contain any pricing info so we skip them // otherwise this would cause reverts - if is_known_system_sender(tx.from()) || - tx.transaction_type() == Some(SYSTEM_TRANSACTION_TYPE) + if is_known_system_sender(tx.inner().inner.signer()) || + tx.ty() == SYSTEM_TRANSACTION_TYPE { trace!(tx=?tx.tx_hash(), "skipping system transaction"); continue; @@ -900,7 +900,7 @@ impl Backend { commit_transaction( &tx.inner, - env.clone(), + &mut env.as_env_mut(), journaled_state, fork, &fork_id, @@ -914,12 +914,12 @@ impl Backend { } impl DatabaseExt for Backend { - fn snapshot_state(&mut self, journaled_state: &JournaledState, env: &Env) -> U256 { + fn snapshot_state(&mut self, journaled_state: &JournaledState, env: &mut EnvMut<'_>) -> U256 { trace!("create snapshot"); let id = self.inner.state_snapshots.insert(BackendStateSnapshot::new( self.create_db_snapshot(), journaled_state.clone(), - env.clone(), + env.to_owned(), )); trace!(target: "backend", "Created new snapshot {}", id); id @@ -929,7 +929,7 @@ impl DatabaseExt for Backend { &mut self, id: U256, current_state: &JournaledState, - current: &mut Env, + current: &mut EnvMut<'_>, action: RevertStateSnapshotAction, ) -> Option { trace!(?id, "revert snapshot"); @@ -970,7 +970,7 @@ impl DatabaseExt for Backend { .map(|acc| acc.info.clone()) .unwrap_or_default(); - if !fork.db.accounts.contains_key(&caller) { + if !fork.db.cache.accounts.contains_key(&caller) { // update the caller account which is required by the evm fork.db.insert_account_info(caller, caller_account.clone()); } @@ -981,7 +981,7 @@ impl DatabaseExt for Backend { } } - update_current_env_with_fork_env(current, env); + update_current_env_with_fork_env(&mut current.as_env_mut(), env); trace!(target: "backend", "Reverted snapshot {}", id); Some(journaled_state) @@ -1027,7 +1027,7 @@ impl DatabaseExt for Backend { self.roll_fork_to_transaction( Some(id), transaction, - &mut env, + &mut env.as_env_mut(), &mut self.inner.new_journaled_state(), )?; Ok(id) @@ -1038,7 +1038,7 @@ impl DatabaseExt for Backend { fn select_fork( &mut self, id: LocalForkId, - env: &mut Env, + env: &mut EnvMut<'_>, active_journaled_state: &mut JournaledState, ) -> eyre::Result<()> { trace!(?id, "select fork"); @@ -1102,11 +1102,27 @@ impl DatabaseExt for Backend { // update the shared state and track let mut fork = self.inner.take_fork(idx); - // Make sure all persistent accounts on the newly selected fork starts from the init - // state (from setup). - for addr in &self.inner.persistent_accounts { - if let Some(account) = self.fork_init_journaled_state.state.get(addr) { - fork.journaled_state.state.insert(*addr, account.clone()); + // Make sure all persistent accounts on the newly selected fork reflect same state as + // the active db / previous fork. + // This can get out of sync when multiple forks are created on test `setUp`, then a + // fork is selected and persistent contract is changed. If first action in test is to + // select a different fork, then the persistent contract state won't reflect changes + // done in `setUp` for the other fork. + // See and . + let persistent_accounts = self.inner.persistent_accounts.clone(); + if let Some(db) = self.active_fork_db_mut() { + for addr in persistent_accounts { + let Ok(db_account) = db.load_account(addr) else { continue }; + + let Some(fork_account) = fork.journaled_state.state.get_mut(&addr) else { + continue + }; + + for (key, val) in &db_account.storage { + if let Some(fork_storage) = fork_account.storage.get_mut(key) { + fork_storage.present_value = *val; + } + } } } @@ -1127,7 +1143,7 @@ impl DatabaseExt for Backend { .map(|acc| acc.info.clone()) .unwrap_or_default(); - if !fork.db.accounts.contains_key(&caller) { + if !fork.db.cache.accounts.contains_key(&caller) { // update the caller account which is required by the evm fork.db.insert_account_info(caller, caller_account.clone()); } @@ -1153,7 +1169,7 @@ impl DatabaseExt for Backend { &mut self, id: Option, block_number: u64, - env: &mut Env, + env: &mut EnvMut<'_>, journaled_state: &mut JournaledState, ) -> eyre::Result<()> { trace!(?id, ?block_number, "roll fork"); @@ -1203,7 +1219,7 @@ impl DatabaseExt for Backend { ); } } else { - let _ = active.journaled_state.load_account(*addr, &mut active.db); + let _ = active.journaled_state.load_account(&mut active.db, *addr); } } @@ -1217,7 +1233,7 @@ impl DatabaseExt for Backend { &mut self, id: Option, transaction: B256, - env: &mut Env, + env: &mut EnvMut<'_>, journaled_state: &mut JournaledState, ) -> eyre::Result<()> { trace!(?id, ?transaction, "roll fork to transaction"); @@ -1231,9 +1247,9 @@ impl DatabaseExt for Backend { update_env_block(env, &block); - // replay all transactions that came before - let env = env.clone(); + let env = env.to_owned(); + // replay all transactions that came before self.replay_until(id, env, transaction, journaled_state)?; Ok(()) @@ -1265,13 +1281,12 @@ impl DatabaseExt for Backend { // So we modify the env to match the transaction's block. let (_fork_block, block) = self.get_block_number_and_block_for_transaction(id, transaction)?; - update_env_block(&mut env, &block); + update_env_block(&mut env.as_env_mut(), &block); - let env = self.env_with_handler_cfg(env); let fork = self.inner.get_fork_by_id_mut(id)?; commit_transaction( - &tx, - env, + &tx.inner, + &mut env.as_env_mut(), journaled_state, fork, &fork_id, @@ -1292,13 +1307,12 @@ impl DatabaseExt for Backend { self.commit(journaled_state.state.clone()); let res = { - configure_tx_req_env(&mut env, tx, None)?; - let env = self.env_with_handler_cfg(env); + configure_tx_req_env(&mut env.as_env_mut(), tx, None)?; let mut db = self.clone(); - let mut evm = new_evm_with_inspector(&mut db, env, inspector); - evm.context.evm.journaled_state.depth = journaled_state.depth + 1; - evm.transact()? + let mut evm = new_evm_with_inspector(&mut db, env.to_owned(), inspector); + evm.journaled_state.depth = journaled_state.depth + 1; + evm.transact(env.tx)? }; self.commit(res.state); @@ -1405,7 +1419,7 @@ impl DatabaseExt for Backend { ) -> Result<(), BackendError> { // Fetch the account from the journaled state. Will create a new account if it does // not already exist. - let mut state_acc = journaled_state.load_account(*target, self)?; + let mut state_acc = journaled_state.load_account(self, *target)?; // Set the account's bytecode and code hash, if the `bytecode` field is present. if let Some(bytecode) = source.code.as_ref() { @@ -1439,7 +1453,7 @@ impl DatabaseExt for Backend { state_acc.info.balance = source.balance; // Touch the account to ensure the loaded information persists if called in `setUp`. - journaled_state.touch(target); + journaled_state.touch(*target); Ok(()) } @@ -1474,9 +1488,9 @@ impl DatabaseExt for Backend { fn set_blockhash(&mut self, block_number: U256, block_hash: B256) { if let Some(db) = self.active_fork_db_mut() { - db.block_hashes.insert(block_number, block_hash); + db.cache.block_hashes.insert(block_number.saturating_to(), block_hash); } else { - self.mem_db.block_hashes.insert(block_number, block_hash); + self.mem_db.cache.block_hashes.insert(block_number.saturating_to(), block_hash); } } } @@ -1803,7 +1817,13 @@ impl BackendInner { /// Returns a new, empty, `JournaledState` with set precompiles pub fn new_journaled_state(&self) -> JournaledState { - JournaledState::new(self.spec_id, self.precompiles().addresses().copied().collect()) + let mut journal = { + let mut journal_inner = JournalInner::new(); + journal_inner.set_spec_id(self.spec_id); + journal_inner + }; + journal.precompiles.extend(self.precompiles().addresses().copied()); + journal } } @@ -1819,7 +1839,7 @@ impl Default for BackendInner { caller: None, next_fork_id: Default::default(), persistent_accounts: Default::default(), - spec_id: SpecId::LATEST, + spec_id: SpecId::default(), // grant the cheatcode,default test and caller address access to execute cheatcodes // itself cheatcode_access_accounts: HashSet::from([ @@ -1832,9 +1852,9 @@ impl Default for BackendInner { } /// This updates the currently used env with the fork's environment -pub(crate) fn update_current_env_with_fork_env(current: &mut Env, fork: Env) { - current.block = fork.block; - current.cfg = fork.cfg; +pub(crate) fn update_current_env_with_fork_env(current: &mut EnvMut<'_>, fork: Env) { + *current.block = fork.evm_env.block_env; + *current.cfg = fork.evm_env.cfg_env; current.tx.chain_id = fork.tx.chain_id; } @@ -1851,12 +1871,6 @@ pub(crate) fn merge_account_data( merge_journaled_state_data(addr, active_journaled_state, &mut target_fork.journaled_state); } - // need to mock empty journal entries in case the current checkpoint is higher than the existing - // journal entries - while active_journaled_state.journal.len() > target_fork.journaled_state.journal.len() { - target_fork.journaled_state.journal.push(Default::default()); - } - *active_journaled_state = target_fork.journaled_state.clone(); } @@ -1886,17 +1900,17 @@ fn merge_db_account_data( ) { trace!(?addr, "merging database data"); - let Some(acc) = active.accounts.get(&addr) else { return }; + let Some(acc) = active.cache.accounts.get(&addr) else { return }; // port contract cache over - if let Some(code) = active.contracts.get(&acc.info.code_hash) { + if let Some(code) = active.cache.contracts.get(&acc.info.code_hash) { trace!("merging contract cache"); - fork_db.contracts.insert(acc.info.code_hash, code.clone()); + fork_db.cache.contracts.insert(acc.info.code_hash, code.clone()); } // port account storage over use std::collections::hash_map::Entry; - match fork_db.accounts.entry(addr) { + match fork_db.cache.accounts.entry(addr) { Entry::Vacant(vacant) => { trace!("target account not present - inserting from active"); // if the fork_db doesn't have the target account @@ -1923,14 +1937,14 @@ fn is_contract_in_state(journaled_state: &JournaledState, acc: Address) -> bool } /// Updates the env's block with the block's data -fn update_env_block(env: &mut Env, block: &AnyRpcBlock) { - env.block.timestamp = U256::from(block.header.timestamp); - env.block.coinbase = block.header.beneficiary; +fn update_env_block(env: &mut EnvMut<'_>, block: &AnyRpcBlock) { + env.block.timestamp = block.header.timestamp; + env.block.beneficiary = block.header.beneficiary; env.block.difficulty = block.header.difficulty; env.block.prevrandao = Some(block.header.mix_hash.unwrap_or_default()); - env.block.basefee = U256::from(block.header.base_fee_per_gas.unwrap_or_default()); - env.block.gas_limit = U256::from(block.header.gas_limit); - env.block.number = U256::from(block.header.number); + env.block.basefee = block.header.base_fee_per_gas.unwrap_or_default(); + env.block.gas_limit = block.header.gas_limit; + env.block.number = block.header.number; if let Some(excess_blob_gas) = block.header.excess_blob_gas { env.block.blob_excess_gas_and_price = Some(BlobExcessGasAndPrice::new(excess_blob_gas, false)); @@ -1941,14 +1955,14 @@ fn update_env_block(env: &mut Env, block: &AnyRpcBlock) { /// state, with an inspector. fn commit_transaction( tx: &Transaction, - mut env: EnvWithHandlerCfg, + env: &mut EnvMut<'_>, journaled_state: &mut JournaledState, fork: &mut Fork, fork_id: &ForkId, persistent_accounts: &HashSet
, inspector: &mut dyn InspectorExt, ) -> eyre::Result<()> { - configure_tx_env(&mut env.env, tx); + configure_tx_env(env, tx); let now = Instant::now(); let res = { @@ -1957,10 +1971,10 @@ fn commit_transaction( let depth = journaled_state.depth; let mut db = Backend::new_with_fork(fork_id, fork, journaled_state)?; - let mut evm = crate::utils::new_evm_with_inspector(&mut db as _, env, inspector); + let mut evm = crate::evm::new_evm_with_inspector(&mut db as _, env.to_owned(), inspector); // Adjust inner EVM depth to ensure that inspectors receive accurate data. - evm.context.evm.inner.journaled_state.depth = depth + 1; - evm.transact().wrap_err("backend: failed committing transaction")? + evm.journaled_state.depth = depth + 1; + evm.transact(env.tx.clone()).wrap_err("backend: failed committing transaction")? }; trace!(elapsed = ?now.elapsed(), "transacted transaction"); @@ -2012,7 +2026,7 @@ mod tests { use foundry_common::provider::get_http_provider; use foundry_config::{Config, NamedChain}; use foundry_fork_db::cache::{BlockchainDb, BlockchainDbMeta}; - use revm::DatabaseRef; + use revm::database::DatabaseRef; const ENDPOINT: Option<&str> = option_env!("ETH_RPC_URL"); @@ -2053,8 +2067,7 @@ mod tests { } drop(backend); - let meta = - BlockchainDbMeta { cfg_env: env.cfg, block_env: env.block, hosts: Default::default() }; + let meta = BlockchainDbMeta { block_env: env.evm_env.block_env, hosts: Default::default() }; let db = BlockchainDb::new( meta, diff --git a/crates/evm/core/src/backend/snapshot.rs b/crates/evm/core/src/backend/snapshot.rs index 36c4657c2618c..d26d9a55e750d 100644 --- a/crates/evm/core/src/backend/snapshot.rs +++ b/crates/evm/core/src/backend/snapshot.rs @@ -1,8 +1,10 @@ -use alloy_primitives::{map::AddressHashMap, B256, U256}; -use revm::{ - primitives::{AccountInfo, Env, HashMap}, - JournaledState, +use super::JournaledState; +use crate::Env; +use alloy_primitives::{ + map::{AddressHashMap, HashMap}, + B256, U256, }; +use revm::state::AccountInfo; use serde::{Deserialize, Serialize}; /// A minimal abstraction of a state at a certain point in time diff --git a/crates/evm/core/src/buffer.rs b/crates/evm/core/src/buffer.rs index 1db7420d78736..e600708f43173 100644 --- a/crates/evm/core/src/buffer.rs +++ b/crates/evm/core/src/buffer.rs @@ -1,5 +1,5 @@ use alloy_primitives::U256; -use revm::interpreter::opcode; +use revm::bytecode::opcode; /// Used to keep track of which buffer is currently active to be drawn by the debugger. #[derive(Debug, PartialEq)] @@ -75,13 +75,6 @@ pub fn get_buffer_accesses(op: u8, stack: &[U256]) -> Option { opcode::CALL | opcode::CALLCODE => (Some((BufferKind::Memory, 4, 5)), None), opcode::DELEGATECALL | opcode::STATICCALL => (Some((BufferKind::Memory, 3, 4)), None), opcode::MCOPY => (Some((BufferKind::Memory, 2, 3)), Some((1, 3))), - opcode::RETURNDATALOAD => (Some((BufferKind::Returndata, 1, -1)), None), - opcode::EOFCREATE => (Some((BufferKind::Memory, 3, 4)), None), - opcode::RETURNCONTRACT => (Some((BufferKind::Memory, 1, 2)), None), - opcode::DATACOPY => (None, Some((1, 3))), - opcode::EXTCALL | opcode::EXTSTATICCALL | opcode::EXTDELEGATECALL => { - (Some((BufferKind::Memory, 2, 3)), None) - } _ => Default::default(), }; diff --git a/crates/evm/core/src/decode.rs b/crates/evm/core/src/decode.rs index 7c8a03da3e300..c3dab88f49cd5 100644 --- a/crates/evm/core/src/decode.rs +++ b/crates/evm/core/src/decode.rs @@ -55,7 +55,7 @@ pub fn decode_console_logs(logs: &[Log]) -> Vec { /// This function returns [None] if it is not a DSTest log or the result of a Hardhat /// `console.log`. pub fn decode_console_log(log: &Log) -> Option { - console::ds::ConsoleEvents::decode_log(log, false).ok().map(|decoded| decoded.to_string()) + console::ds::ConsoleEvents::decode_log(log).ok().map(|decoded| decoded.to_string()) } /// Decodes revert data. @@ -151,7 +151,7 @@ impl RevertDecoder { } // Solidity's `Panic(uint256)` and `Vm`'s custom errors. - if let Ok(e) = alloy_sol_types::ContractError::::abi_decode(err, false) { + if let Ok(e) = alloy_sol_types::ContractError::::abi_decode(err) { return Some(e.to_string()); } @@ -163,7 +163,7 @@ impl RevertDecoder { for error in errors { // If we don't decode, don't return an error, try to decode as a string // later. - if let Ok(decoded) = error.abi_decode_input(data, false) { + if let Ok(decoded) = error.abi_decode_input(data) { return Some(format!( "{}({})", error.name, @@ -211,7 +211,7 @@ impl RevertDecoder { /// Helper function that decodes provided error as an ABI encoded or an ASCII string (if not empty). fn decode_as_non_empty_string(err: &[u8]) -> Option { // ABI-encoded `string`. - if let Ok(s) = String::abi_decode(err, true) { + if let Ok(s) = String::abi_decode(err) { if !s.is_empty() { return Some(s); } @@ -272,7 +272,7 @@ mod tests { "0xe17594de" "756688fe00000000000000000000000000000000000000000000000000000000" ); - assert_eq!(decoder.decode(data, None), "ValidationFailed(0x)"); + assert_eq!(decoder.decode(data, None), "custom error 0xe17594de: 756688fe00000000000000000000000000000000000000000000000000000000"); /* abi.encodeWithSelector(ValidationFailed.selector, abi.encodeWithSelector(InvalidNonce.selector)) diff --git a/crates/evm/core/src/either_evm.rs b/crates/evm/core/src/either_evm.rs new file mode 100644 index 0000000000000..2ef9f894f144b --- /dev/null +++ b/crates/evm/core/src/either_evm.rs @@ -0,0 +1,276 @@ +use alloy_evm::{eth::EthEvmContext, Database, EthEvm, Evm, EvmEnv}; +use alloy_op_evm::OpEvm; +use alloy_primitives::{Address, Bytes}; +use op_revm::{OpContext, OpHaltReason, OpSpecId, OpTransaction, OpTransactionError}; +use revm::{ + context::{ + result::{EVMError, ExecutionResult, HaltReason, ResultAndState}, + BlockEnv, TxEnv, + }, + handler::PrecompileProvider, + interpreter::InterpreterResult, + primitives::hardfork::SpecId, + DatabaseCommit, Inspector, +}; + +/// Alias for result type returned by [`Evm::transact`] methods. +type EitherEvmResult = + Result, EVMError>; + +/// Alias for result type returned by [`Evm::transact_commit`] methods. +type EitherExecResult = + Result, EVMError>; + +/// [`EitherEvm`] delegates its calls to one of the two evm implementations; either [`EthEvm`] or +/// [`OpEvm`]. +/// +/// Calls are delegated to [`OpEvm`] only if optimism is enabled. +/// +/// The call delegation is handled via its own implementation of the [`Evm`] trait. +/// +/// The [`Evm::transact`] and other such calls work over the [`OpTransaction`] type. +/// +/// However, the [`Evm::HaltReason`] and [`Evm::Error`] leverage the optimism [`OpHaltReason`] and +/// [`OpTransactionError`] as these are supersets of the eth types. This makes it easier to map eth +/// types to op types and also prevents ignoring of any error that maybe thrown by [`OpEvm`]. +#[allow(clippy::large_enum_variant)] +pub enum EitherEvm +where + DB: Database, +{ + /// [`EthEvm`] implementation. + Eth(EthEvm), + /// [`OpEvm`] implementation. + Op(OpEvm), +} + +impl EitherEvm +where + DB: Database, + I: Inspector> + Inspector>, + P: PrecompileProvider, Output = InterpreterResult> + + PrecompileProvider, Output = InterpreterResult>, +{ + /// Converts the [`EthEvm::transact`] result to [`EitherEvmResult`]. + fn map_eth_result( + &self, + result: Result, EVMError>, + ) -> EitherEvmResult { + match result { + Ok(result) => { + // Map the halt reason + Ok(result.map_haltreason(OpHaltReason::Base)) + } + Err(e) => Err(self.map_eth_err(e)), + } + } + + /// Converts the [`EthEvm::transact_commit`] result to [`EitherExecResult`]. + fn map_exec_result( + &self, + result: Result>, + ) -> EitherExecResult { + match result { + Ok(result) => { + // Map the halt reason + Ok(result.map_haltreason(OpHaltReason::Base)) + } + Err(e) => Err(self.map_eth_err(e)), + } + } + + /// Maps [`EVMError`] to [`EVMError`]. + fn map_eth_err(&self, err: EVMError) -> EVMError { + match err { + EVMError::Transaction(invalid_tx) => { + EVMError::Transaction(OpTransactionError::Base(invalid_tx)) + } + EVMError::Database(e) => EVMError::Database(e), + EVMError::Header(e) => EVMError::Header(e), + EVMError::Custom(e) => EVMError::Custom(e), + } + } +} + +impl Evm for EitherEvm +where + DB: Database, + I: Inspector> + Inspector>, + P: PrecompileProvider, Output = InterpreterResult> + + PrecompileProvider, Output = InterpreterResult>, +{ + type DB = DB; + type Error = EVMError; + type HaltReason = OpHaltReason; + type Tx = OpTransaction; + type Inspector = I; + type Precompiles = P; + type Spec = SpecId; + + fn chain_id(&self) -> u64 { + match self { + Self::Eth(evm) => evm.chain_id(), + Self::Op(evm) => evm.chain_id(), + } + } + + fn block(&self) -> &BlockEnv { + match self { + Self::Eth(evm) => evm.block(), + Self::Op(evm) => evm.block(), + } + } + + fn db_mut(&mut self) -> &mut Self::DB { + match self { + Self::Eth(evm) => evm.db_mut(), + Self::Op(evm) => evm.db_mut(), + } + } + + fn into_db(self) -> Self::DB + where + Self: Sized, + { + match self { + Self::Eth(evm) => evm.into_db(), + Self::Op(evm) => evm.into_db(), + } + } + + fn finish(self) -> (Self::DB, EvmEnv) + where + Self: Sized, + { + match self { + Self::Eth(evm) => evm.finish(), + Self::Op(evm) => { + let (db, env) = evm.finish(); + (db, map_env(env)) + } + } + } + + fn precompiles(&self) -> &Self::Precompiles { + match self { + Self::Eth(evm) => evm.precompiles(), + Self::Op(evm) => evm.precompiles(), + } + } + + fn precompiles_mut(&mut self) -> &mut Self::Precompiles { + match self { + Self::Eth(evm) => evm.precompiles_mut(), + Self::Op(evm) => evm.precompiles_mut(), + } + } + + fn inspector(&self) -> &Self::Inspector { + match self { + Self::Eth(evm) => evm.inspector(), + Self::Op(evm) => evm.inspector(), + } + } + + fn inspector_mut(&mut self) -> &mut Self::Inspector { + match self { + Self::Eth(evm) => evm.inspector_mut(), + Self::Op(evm) => evm.inspector_mut(), + } + } + + fn enable_inspector(&mut self) { + match self { + Self::Eth(evm) => evm.enable_inspector(), + Self::Op(evm) => evm.enable_inspector(), + } + } + + fn disable_inspector(&mut self) { + match self { + Self::Eth(evm) => evm.disable_inspector(), + Self::Op(evm) => evm.disable_inspector(), + } + } + + fn set_inspector_enabled(&mut self, enabled: bool) { + match self { + Self::Eth(evm) => evm.set_inspector_enabled(enabled), + Self::Op(evm) => evm.set_inspector_enabled(enabled), + } + } + + fn into_env(self) -> EvmEnv + where + Self: Sized, + { + match self { + Self::Eth(evm) => evm.into_env(), + Self::Op(evm) => map_env(evm.into_env()), + } + } + + fn transact( + &mut self, + tx: impl alloy_evm::IntoTxEnv, + ) -> Result, Self::Error> { + match self { + Self::Eth(evm) => { + let eth = evm.transact(tx.into_tx_env().base); + self.map_eth_result(eth) + } + Self::Op(evm) => evm.transact(tx), + } + } + + fn transact_commit( + &mut self, + tx: impl alloy_evm::IntoTxEnv, + ) -> Result, Self::Error> + where + Self::DB: DatabaseCommit, + { + match self { + Self::Eth(evm) => { + let eth = evm.transact_commit(tx.into_tx_env().base); + self.map_exec_result(eth) + } + Self::Op(evm) => evm.transact_commit(tx), + } + } + + fn transact_raw( + &mut self, + tx: Self::Tx, + ) -> Result, Self::Error> { + match self { + Self::Eth(evm) => { + let res = evm.transact_raw(tx.base); + self.map_eth_result(res) + } + Self::Op(evm) => evm.transact_raw(tx), + } + } + + fn transact_system_call( + &mut self, + caller: Address, + contract: Address, + data: Bytes, + ) -> Result, Self::Error> { + match self { + Self::Eth(evm) => { + let eth = evm.transact_system_call(caller, contract, data); + self.map_eth_result(eth) + } + Self::Op(evm) => evm.transact_system_call(caller, contract, data), + } + } +} + +/// Maps [`EvmEnv`] to [`EvmEnv`]. +fn map_env(env: EvmEnv) -> EvmEnv { + let eth_spec_id = env.spec_id().into_eth_spec(); + let cfg = env.cfg_env.with_spec(eth_spec_id); + EvmEnv { cfg_env: cfg, block_env: env.block_env } +} diff --git a/crates/evm/core/src/env.rs b/crates/evm/core/src/env.rs new file mode 100644 index 0000000000000..a7708f132380d --- /dev/null +++ b/crates/evm/core/src/env.rs @@ -0,0 +1,103 @@ +pub use alloy_evm::EvmEnv; +use revm::{ + context::{BlockEnv, CfgEnv, JournalInner, JournalTr, TxEnv}, + primitives::hardfork::SpecId, + Context, Database, Journal, JournalEntry, +}; + +/// Helper container type for [`EvmEnv`] and [`TxEnv`]. +#[derive(Clone, Debug, Default)] +pub struct Env { + pub evm_env: EvmEnv, + pub tx: TxEnv, +} + +/// Helper container type for [`EvmEnv`] and [`TxEnv`]. +impl Env { + pub fn default_with_spec_id(spec_id: SpecId) -> Self { + let mut cfg = CfgEnv::default(); + cfg.spec = spec_id; + + Self::from(cfg, BlockEnv::default(), TxEnv::default()) + } + + pub fn from(cfg: CfgEnv, block: BlockEnv, tx: TxEnv) -> Self { + Self { evm_env: EvmEnv { cfg_env: cfg, block_env: block }, tx } + } + + pub fn new_with_spec_id(cfg: CfgEnv, block: BlockEnv, tx: TxEnv, spec_id: SpecId) -> Self { + let mut cfg = cfg; + cfg.spec = spec_id; + + Self::from(cfg, block, tx) + } +} + +/// Helper struct with mutable references to the block and cfg environments. +pub struct EnvMut<'a> { + pub block: &'a mut BlockEnv, + pub cfg: &'a mut CfgEnv, + pub tx: &'a mut TxEnv, +} + +impl EnvMut<'_> { + /// Returns a copy of the environment. + pub fn to_owned(&self) -> Env { + Env { + evm_env: EvmEnv { cfg_env: self.cfg.to_owned(), block_env: self.block.to_owned() }, + tx: self.tx.to_owned(), + } + } +} + +pub trait AsEnvMut { + fn as_env_mut(&mut self) -> EnvMut<'_>; +} + +impl AsEnvMut for EnvMut<'_> { + fn as_env_mut(&mut self) -> EnvMut<'_> { + EnvMut { block: self.block, cfg: self.cfg, tx: self.tx } + } +} + +impl AsEnvMut for Env { + fn as_env_mut(&mut self) -> EnvMut<'_> { + EnvMut { + block: &mut self.evm_env.block_env, + cfg: &mut self.evm_env.cfg_env, + tx: &mut self.tx, + } + } +} + +impl, C> AsEnvMut + for Context +{ + fn as_env_mut(&mut self) -> EnvMut<'_> { + EnvMut { block: &mut self.block, cfg: &mut self.cfg, tx: &mut self.tx } + } +} + +pub trait ContextExt { + type DB: Database; + + fn as_db_env_and_journal( + &mut self, + ) -> (&mut Self::DB, &mut JournalInner, EnvMut<'_>); +} + +impl ContextExt + for Context, C> +{ + type DB = DB; + + fn as_db_env_and_journal( + &mut self, + ) -> (&mut Self::DB, &mut JournalInner, EnvMut<'_>) { + ( + &mut self.journaled_state.database, + &mut self.journaled_state.inner, + EnvMut { block: &mut self.block, cfg: &mut self.cfg, tx: &mut self.tx }, + ) + } +} diff --git a/crates/evm/core/src/evm.rs b/crates/evm/core/src/evm.rs new file mode 100644 index 0000000000000..ab216fe4117d2 --- /dev/null +++ b/crates/evm/core/src/evm.rs @@ -0,0 +1,387 @@ +use std::ops::{Deref, DerefMut}; + +use crate::{ + backend::DatabaseExt, constants::DEFAULT_CREATE2_DEPLOYER_CODEHASH, Env, InspectorExt, +}; +use alloy_consensus::constants::KECCAK_EMPTY; +use alloy_evm::{ + eth::EthEvmContext, + precompiles::{DynPrecompile, PrecompilesMap}, + Evm, EvmEnv, +}; +use alloy_primitives::{Address, Bytes, U256}; +use foundry_fork_db::DatabaseError; +use revm::{ + context::{ + result::{EVMError, HaltReason, ResultAndState}, + BlockEnv, CfgEnv, ContextTr, CreateScheme, Evm as RevmEvm, JournalTr, LocalContext, TxEnv, + }, + handler::{ + instructions::EthInstructions, EthFrame, EthPrecompiles, FrameInitOrResult, FrameResult, + Handler, ItemOrResult, MainnetHandler, + }, + inspector::InspectorHandler, + interpreter::{ + interpreter::EthInterpreter, return_ok, CallInput, CallInputs, CallOutcome, CallScheme, + CallValue, CreateInputs, CreateOutcome, FrameInput, Gas, InstructionResult, + InterpreterResult, + }, + precompile::{secp256r1::P256VERIFY, PrecompileSpecId, Precompiles}, + primitives::hardfork::SpecId, + Context, ExecuteEvm, Journal, +}; + +pub fn new_evm_with_inspector<'i, 'db, I: InspectorExt + ?Sized>( + db: &'db mut dyn DatabaseExt, + env: Env, + inspector: &'i mut I, +) -> FoundryEvm<'db, &'i mut I> { + let ctx = EthEvmContext { + journaled_state: { + let mut journal = Journal::new(db); + journal.set_spec_id(env.evm_env.cfg_env.spec); + journal + }, + block: env.evm_env.block_env, + cfg: env.evm_env.cfg_env, + tx: env.tx, + chain: (), + local: LocalContext::default(), + error: Ok(()), + }; + let spec = ctx.cfg.spec; + + let mut evm = FoundryEvm { + inner: RevmEvm::new_with_inspector( + ctx, + inspector, + EthInstructions::default(), + get_precompiles(spec), + ), + }; + + inject_precompiles(&mut evm); + + evm +} + +pub fn new_evm_with_existing_context<'a>( + ctx: EthEvmContext<&'a mut dyn DatabaseExt>, + inspector: &'a mut dyn InspectorExt, +) -> FoundryEvm<'a, &'a mut dyn InspectorExt> { + let spec = ctx.cfg.spec; + + let mut evm = FoundryEvm { + inner: RevmEvm::new_with_inspector( + ctx, + inspector, + EthInstructions::default(), + get_precompiles(spec), + ), + }; + + inject_precompiles(&mut evm); + + evm +} + +/// Conditionally inject additional precompiles into the EVM context. +fn inject_precompiles(evm: &mut FoundryEvm<'_, impl InspectorExt>) { + if evm.inspector().is_odyssey() { + evm.precompiles_mut().apply_precompile(P256VERIFY.address(), |_| { + Some(DynPrecompile::from(P256VERIFY.precompile())) + }); + } +} + +/// Get the precompiles for the given spec. +fn get_precompiles(spec: SpecId) -> PrecompilesMap { + PrecompilesMap::from_static( + EthPrecompiles { + precompiles: Precompiles::new(PrecompileSpecId::from_spec_id(spec)), + spec, + } + .precompiles, + ) +} + +/// Get the call inputs for the CREATE2 factory. +fn get_create2_factory_call_inputs( + salt: U256, + inputs: &CreateInputs, + deployer: Address, +) -> CallInputs { + let calldata = [&salt.to_be_bytes::<32>()[..], &inputs.init_code[..]].concat(); + CallInputs { + caller: inputs.caller, + bytecode_address: deployer, + target_address: deployer, + scheme: CallScheme::Call, + value: CallValue::Transfer(inputs.value), + input: CallInput::Bytes(calldata.into()), + gas_limit: inputs.gas_limit, + is_static: false, + return_memory_offset: 0..0, + is_eof: false, + } +} + +pub struct FoundryEvm<'db, I: InspectorExt> { + #[allow(clippy::type_complexity)] + pub inner: RevmEvm< + EthEvmContext<&'db mut dyn DatabaseExt>, + I, + EthInstructions>, + PrecompilesMap, + >, +} + +impl FoundryEvm<'_, I> { + pub fn run_execution( + &mut self, + frame: FrameInput, + ) -> Result> { + let mut handler = FoundryHandler::<_>::default(); + + // Create first frame action + let frame = handler.inspect_first_frame_init(&mut self.inner, frame)?; + let frame_result = match frame { + ItemOrResult::Item(frame) => handler.inspect_run_exec_loop(&mut self.inner, frame)?, + ItemOrResult::Result(result) => result, + }; + + Ok(frame_result) + } +} + +impl<'db, I: InspectorExt> Evm for FoundryEvm<'db, I> { + type Precompiles = PrecompilesMap; + type Inspector = I; + type DB = &'db mut dyn DatabaseExt; + type Error = EVMError; + type HaltReason = HaltReason; + type Spec = SpecId; + type Tx = TxEnv; + + fn chain_id(&self) -> u64 { + self.inner.ctx.cfg.chain_id + } + + fn block(&self) -> &BlockEnv { + &self.inner.block + } + + fn db_mut(&mut self) -> &mut Self::DB { + self.inner.db() + } + + fn precompiles(&self) -> &Self::Precompiles { + &self.inner.precompiles + } + + fn precompiles_mut(&mut self) -> &mut Self::Precompiles { + &mut self.inner.precompiles + } + + fn inspector(&self) -> &Self::Inspector { + &self.inner.inspector + } + + fn inspector_mut(&mut self) -> &mut Self::Inspector { + &mut self.inner.inspector + } + + fn set_inspector_enabled(&mut self, _enabled: bool) { + unimplemented!("FoundryEvm is always inspecting") + } + + fn transact_raw( + &mut self, + tx: Self::Tx, + ) -> Result, Self::Error> { + let mut handler = FoundryHandler::<_>::default(); + self.inner.set_tx(tx); + handler.inspect_run(&mut self.inner) + } + + fn transact_system_call( + &mut self, + _caller: Address, + _contract: Address, + _data: Bytes, + ) -> Result, Self::Error> { + unimplemented!() + } + + fn finish(self) -> (Self::DB, EvmEnv) + where + Self: Sized, + { + let Context { block: block_env, cfg: cfg_env, journaled_state, .. } = self.inner.ctx; + + (journaled_state.database, EvmEnv { block_env, cfg_env }) + } +} + +impl<'db, I: InspectorExt> Deref for FoundryEvm<'db, I> { + type Target = Context; + + fn deref(&self) -> &Self::Target { + &self.inner.ctx + } +} + +impl DerefMut for FoundryEvm<'_, I> { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.inner.ctx + } +} + +pub struct FoundryHandler<'db, I: InspectorExt> { + #[allow(clippy::type_complexity)] + inner: MainnetHandler< + RevmEvm< + EthEvmContext<&'db mut dyn DatabaseExt>, + I, + EthInstructions>, + PrecompilesMap, + >, + EVMError, + EthFrame< + RevmEvm< + EthEvmContext<&'db mut dyn DatabaseExt>, + I, + EthInstructions>, + PrecompilesMap, + >, + EVMError, + EthInterpreter, + >, + >, + create2_overrides: Vec<(usize, CallInputs)>, +} + +impl Default for FoundryHandler<'_, I> { + fn default() -> Self { + Self { inner: MainnetHandler::default(), create2_overrides: Vec::new() } + } +} + +impl<'db, I: InspectorExt> Handler for FoundryHandler<'db, I> { + type Evm = RevmEvm< + EthEvmContext<&'db mut dyn DatabaseExt>, + I, + EthInstructions>, + PrecompilesMap, + >; + type Error = EVMError; + type Frame = EthFrame< + RevmEvm< + EthEvmContext<&'db mut dyn DatabaseExt>, + I, + EthInstructions>, + PrecompilesMap, + >, + EVMError, + EthInterpreter, + >; + type HaltReason = HaltReason; + + fn frame_return_result( + &mut self, + frame: &mut Self::Frame, + evm: &mut Self::Evm, + result: ::FrameResult, + ) -> Result<(), Self::Error> { + let result = if self + .create2_overrides + .last() + .is_some_and(|(depth, _)| *depth == evm.journal().depth) + { + let (_, call_inputs) = self.create2_overrides.pop().unwrap(); + let FrameResult::Call(mut result) = result else { + unreachable!("create2 override should be a call frame"); + }; + + // Decode address from output. + let address = match result.instruction_result() { + return_ok!() => Address::try_from(result.output().as_ref()) + .map_err(|_| { + result.result = InterpreterResult { + result: InstructionResult::Revert, + output: "invalid CREATE2 factory output".into(), + gas: Gas::new(call_inputs.gas_limit), + }; + }) + .ok(), + _ => None, + }; + + FrameResult::Create(CreateOutcome { result: result.result, address }) + } else { + result + }; + + self.inner.frame_return_result(frame, evm, result) + } +} + +impl InspectorHandler for FoundryHandler<'_, I> { + type IT = EthInterpreter; + + fn inspect_frame_call( + &mut self, + frame: &mut Self::Frame, + evm: &mut Self::Evm, + ) -> Result, Self::Error> { + let frame_or_result = self.inner.inspect_frame_call(frame, evm)?; + + let ItemOrResult::Item(FrameInput::Create(inputs)) = &frame_or_result else { + return Ok(frame_or_result) + }; + + let CreateScheme::Create2 { salt } = inputs.scheme else { return Ok(frame_or_result) }; + + if !evm.inspector.should_use_create2_factory(&mut evm.ctx, inputs) { + return Ok(frame_or_result) + } + + let gas_limit = inputs.gas_limit; + + // Get CREATE2 deployer. + let create2_deployer = evm.inspector.create2_deployer(); + + // Generate call inputs for CREATE2 factory. + let call_inputs = get_create2_factory_call_inputs(salt, inputs, create2_deployer); + + // Push data about current override to the stack. + self.create2_overrides.push((evm.journal().depth(), call_inputs.clone())); + + // Sanity check that CREATE2 deployer exists. + let code_hash = evm.journal().load_account(create2_deployer)?.info.code_hash; + if code_hash == KECCAK_EMPTY { + return Ok(ItemOrResult::Result(FrameResult::Call(CallOutcome { + result: InterpreterResult { + result: InstructionResult::Revert, + output: Bytes::copy_from_slice( + format!("missing CREATE2 deployer: {create2_deployer}").as_bytes(), + ), + gas: Gas::new(gas_limit), + }, + memory_offset: 0..0, + }))) + } else if code_hash != DEFAULT_CREATE2_DEPLOYER_CODEHASH { + return Ok(ItemOrResult::Result(FrameResult::Call(CallOutcome { + result: InterpreterResult { + result: InstructionResult::Revert, + output: "invalid CREATE2 deployer bytecode".into(), + gas: Gas::new(gas_limit), + }, + memory_offset: 0..0, + }))) + } + + // Return the created CALL frame instead + Ok(ItemOrResult::Item(FrameInput::Call(Box::new(call_inputs)))) + } +} diff --git a/crates/evm/core/src/fork/database.rs b/crates/evm/core/src/fork/database.rs index 53595e451fd2f..30549f028aace 100644 --- a/crates/evm/core/src/fork/database.rs +++ b/crates/evm/core/src/fork/database.rs @@ -9,8 +9,9 @@ use alloy_rpc_types::BlockId; use foundry_fork_db::{BlockchainDb, DatabaseError, SharedBackend}; use parking_lot::Mutex; use revm::{ - db::{CacheDB, DatabaseRef}, - primitives::{Account, AccountInfo, Bytecode}, + bytecode::Bytecode, + database::{CacheDB, DatabaseRef}, + state::{Account, AccountInfo}, Database, DatabaseCommit, }; use std::sync::Arc; @@ -209,7 +210,12 @@ pub struct ForkDbStateSnapshot { impl ForkDbStateSnapshot { fn get_storage(&self, address: Address, index: U256) -> Option { - self.local.accounts.get(&address).and_then(|account| account.storage.get(&index)).copied() + self.local + .cache + .accounts + .get(&address) + .and_then(|account| account.storage.get(&index)) + .copied() } } @@ -220,7 +226,7 @@ impl DatabaseRef for ForkDbStateSnapshot { type Error = DatabaseError; fn basic_ref(&self, address: Address) -> Result, Self::Error> { - match self.local.accounts.get(&address) { + match self.local.cache.accounts.get(&address) { Some(account) => Ok(Some(account.info.clone())), None => { let mut acc = self.state_snapshot.accounts.get(&address).cloned(); @@ -238,7 +244,7 @@ impl DatabaseRef for ForkDbStateSnapshot { } fn storage_ref(&self, address: Address, index: U256) -> Result { - match self.local.accounts.get(&address) { + match self.local.cache.accounts.get(&address) { Some(account) => match account.storage.get(&index) { Some(entry) => Ok(*entry), None => match self.get_storage(address, index) { @@ -274,11 +280,7 @@ mod tests { async fn fork_db_insert_basic_default() { let rpc = foundry_test_utils::rpc::next_http_rpc_endpoint(); let provider = get_http_provider(rpc.clone()); - let meta = BlockchainDbMeta { - cfg_env: Default::default(), - block_env: Default::default(), - hosts: BTreeSet::from([rpc]), - }; + let meta = BlockchainDbMeta { block_env: Default::default(), hosts: BTreeSet::from([rpc]) }; let db = BlockchainDb::new(meta, None); let backend = SharedBackend::spawn_backend(Arc::new(provider), db.clone(), None).await; diff --git a/crates/evm/core/src/fork/init.rs b/crates/evm/core/src/fork/init.rs index c84cd945c4ded..8b23ae5b8531a 100644 --- a/crates/evm/core/src/fork/init.rs +++ b/crates/evm/core/src/fork/init.rs @@ -1,11 +1,11 @@ -use crate::utils::apply_chain_and_block_specific_env_changes; +use crate::{utils::apply_chain_and_block_specific_env_changes, AsEnvMut, Env, EvmEnv}; use alloy_consensus::BlockHeader; -use alloy_primitives::{Address, U256}; +use alloy_primitives::Address; use alloy_provider::{network::BlockResponse, Network, Provider}; use alloy_rpc_types::BlockNumberOrTag; use eyre::WrapErr; use foundry_common::NON_ARCHIVE_NODE_WARNING; -use revm::primitives::{BlockEnv, CfgEnv, Env, TxEnv}; +use revm::context::{BlockEnv, CfgEnv, TxEnv}; /// Initializes a REVM block environment based on a forked /// ethereum provider. @@ -46,38 +46,51 @@ pub async fn environment>( eyre::bail!("failed to get block for block number: {block_number}") }; - let mut cfg = CfgEnv::default(); - cfg.chain_id = override_chain_id.unwrap_or(rpc_chain_id); - cfg.memory_limit = memory_limit; - cfg.limit_contract_code_size = Some(usize::MAX); - // EIP-3607 rejects transactions from senders with deployed code. - // If EIP-3607 is enabled it can cause issues during fuzz/invariant tests if the caller - // is a contract. So we disable the check by default. - cfg.disable_eip3607 = true; - cfg.disable_block_gas_limit = disable_block_gas_limit; + let cfg = configure_env( + override_chain_id.unwrap_or(rpc_chain_id), + memory_limit, + disable_block_gas_limit, + ); let mut env = Env { - cfg, - block: BlockEnv { - number: U256::from(block.header().number()), - timestamp: U256::from(block.header().timestamp()), - coinbase: block.header().beneficiary(), - difficulty: block.header().difficulty(), - prevrandao: block.header().mix_hash(), - basefee: U256::from(block.header().base_fee_per_gas().unwrap_or_default()), - gas_limit: U256::from(block.header().gas_limit()), - ..Default::default() + evm_env: EvmEnv { + cfg_env: cfg, + block_env: BlockEnv { + number: block.header().number(), + timestamp: block.header().timestamp(), + beneficiary: block.header().beneficiary(), + difficulty: block.header().difficulty(), + prevrandao: block.header().mix_hash(), + basefee: block.header().base_fee_per_gas().unwrap_or_default(), + gas_limit: block.header().gas_limit(), + ..Default::default() + }, }, tx: TxEnv { caller: origin, - gas_price: U256::from(gas_price.unwrap_or(fork_gas_price)), + gas_price: gas_price.unwrap_or(fork_gas_price), chain_id: Some(override_chain_id.unwrap_or(rpc_chain_id)), gas_limit: block.header().gas_limit() as u64, ..Default::default() }, }; - apply_chain_and_block_specific_env_changes::(&mut env, &block); + apply_chain_and_block_specific_env_changes::(env.as_env_mut(), &block); Ok((env, block)) } + +/// Configures the environment for the given chain id and memory limit. +pub fn configure_env(chain_id: u64, memory_limit: u64, disable_block_gas_limit: bool) -> CfgEnv { + let mut cfg = CfgEnv::default(); + cfg.chain_id = chain_id; + cfg.memory_limit = memory_limit; + cfg.limit_contract_code_size = Some(usize::MAX); + // EIP-3607 rejects transactions from senders with deployed code. + // If EIP-3607 is enabled it can cause issues during fuzz/invariant tests if the caller + // is a contract. So we disable the check by default. + cfg.disable_eip3607 = true; + cfg.disable_block_gas_limit = disable_block_gas_limit; + cfg.disable_nonce_check = true; + cfg +} diff --git a/crates/evm/core/src/fork/mod.rs b/crates/evm/core/src/fork/mod.rs index 9401c2d32ed58..3049f9f957a63 100644 --- a/crates/evm/core/src/fork/mod.rs +++ b/crates/evm/core/src/fork/mod.rs @@ -1,8 +1,8 @@ use super::opts::EvmOpts; -use revm::primitives::Env; +use crate::Env; mod init; -pub use init::environment; +pub use init::{configure_env, environment}; pub mod database; diff --git a/crates/evm/core/src/fork/multi.rs b/crates/evm/core/src/fork/multi.rs index 2470c315410a2..59a05d67be2f2 100644 --- a/crates/evm/core/src/fork/multi.rs +++ b/crates/evm/core/src/fork/multi.rs @@ -4,8 +4,9 @@ //! concurrently active pairs at once. use super::CreateFork; +use crate::Env; use alloy_consensus::BlockHeader; -use alloy_primitives::{map::HashMap, U256}; +use alloy_primitives::map::HashMap; use alloy_provider::network::BlockResponse; use foundry_common::provider::{ProviderBuilder, RetryProvider}; use foundry_config::Config; @@ -16,7 +17,6 @@ use futures::{ task::{Context, Poll}, Future, FutureExt, StreamExt, }; -use revm::primitives::Env; use std::{ fmt::{self, Write}, pin::Pin, @@ -150,7 +150,7 @@ impl MultiFork { } /// Updates block number and timestamp of given fork with new values. - pub fn update_block(&self, fork: ForkId, number: U256, timestamp: U256) -> eyre::Result<()> { + pub fn update_block(&self, fork: ForkId, number: u64, timestamp: u64) -> eyre::Result<()> { trace!(?fork, ?number, ?timestamp, "update fork block"); self.handler .clone() @@ -200,7 +200,7 @@ enum Request { /// Returns the environment of the fork. GetEnv(ForkId, GetEnvSender), /// Updates the block number and timestamp of the fork. - UpdateBlock(ForkId, U256, U256), + UpdateBlock(ForkId, u64, u64), /// Shutdowns the entire `MultiForkHandler`, see `ShutDownMultiFork` ShutDown(OneshotSender<()>), /// Returns the Fork Url for the `ForkId` if it exists. @@ -302,10 +302,10 @@ impl MultiForkHandler { /// Update fork block number and timestamp. Used to preserve values set by `roll` and `warp` /// cheatcodes when new fork selected. - fn update_block(&mut self, fork_id: ForkId, block_number: U256, block_timestamp: U256) { + fn update_block(&mut self, fork_id: ForkId, block_number: u64, block_timestamp: u64) { if let Some(fork) = self.forks.get_mut(&fork_id) { - fork.opts.env.block.number = block_number; - fork.opts.env.block.timestamp = block_timestamp; + fork.opts.env.evm_env.block_env.number = block_number; + fork.opts.env.evm_env.block_env.timestamp = block_timestamp; } } @@ -519,7 +519,7 @@ async fn create_fork(mut fork: CreateFork) -> eyre::Result<(ForkId, CreatedFork, // Initialise the fork environment. let (env, block) = fork.evm_opts.fork_evm_env(&fork.url).await?; fork.env = env; - let meta = BlockchainDbMeta::new(fork.env.clone(), fork.url.clone()); + let meta = BlockchainDbMeta::new(fork.env.evm_env.block_env.clone(), fork.url.clone()); // We need to use the block number from the block because the env's number can be different on // some L2s (e.g. Arbitrum). @@ -527,7 +527,7 @@ async fn create_fork(mut fork: CreateFork) -> eyre::Result<(ForkId, CreatedFork, // Determine the cache path if caching is enabled. let cache_path = if fork.enable_caching { - Config::foundry_block_cache_dir(meta.cfg_env.chain_id, number) + Config::foundry_block_cache_dir(fork.env.evm_env.cfg_env.chain_id, number) } else { None }; diff --git a/crates/evm/core/src/ic.rs b/crates/evm/core/src/ic.rs index 80fef528c9ac5..7fc64791ace06 100644 --- a/crates/evm/core/src/ic.rs +++ b/crates/evm/core/src/ic.rs @@ -1,10 +1,6 @@ use alloy_primitives::map::rustc_hash::FxHashMap; use eyre::Result; -use revm::interpreter::{ - opcode::{PUSH0, PUSH1, PUSH32}, - OpCode, -}; -use revm_inspectors::opcode::immediate_size; +use revm::bytecode::opcode::{OpCode, PUSH0, PUSH1, PUSH32}; use serde::Serialize; /// Maps from program counter to instruction counter. @@ -98,17 +94,17 @@ fn make_map(code: &[u8]) -> FxHashMap { } /// Represents a single instruction consisting of the opcode and its immediate data. -pub struct Instruction<'a> { +pub struct Instruction { /// OpCode, if it could be decoded. pub op: Option, /// Immediate data following the opcode. - pub immediate: &'a [u8], + pub immediate: Box<[u8]>, /// Program counter of the opcode. pub pc: u32, } /// Decodes raw opcode bytes into [`Instruction`]s. -pub fn decode_instructions(code: &[u8]) -> Result>> { +pub fn decode_instructions(code: &[u8]) -> Result> { assert!(code.len() <= u32::MAX as usize, "bytecode is too big"); let mut pc = 0usize; @@ -116,17 +112,72 @@ pub fn decode_instructions(code: &[u8]) -> Result>> { while pc < code.len() { let op = OpCode::new(code[pc]); - pc += 1; - let immediate_size = op.map(|op| immediate_size(op, &code[pc..])).unwrap_or(0) as usize; + let next_pc = pc + 1; + let immediate_size = op.map(|op| op.info().immediate_size()).unwrap_or(0) as usize; + let is_normal_push = op.map(|op| op.is_push()).unwrap_or(false); - if pc + immediate_size > code.len() { + if !is_normal_push && next_pc + immediate_size > code.len() { eyre::bail!("incomplete sequence of bytecode"); } - steps.push(Instruction { op, pc: pc as u32, immediate: &code[pc..pc + immediate_size] }); + // Ensure immediate is padded if needed. + let immediate_end = (next_pc + immediate_size).min(code.len()); + let mut immediate = vec![0u8; immediate_size]; + let immediate_part = &code[next_pc..immediate_end]; + immediate[..immediate_part.len()].copy_from_slice(immediate_part); + + steps.push(Instruction { op, pc: pc as u32, immediate: immediate.into_boxed_slice() }); - pc += immediate_size; + pc = next_pc + immediate_size; } Ok(steps) } + +#[cfg(test)] +pub mod tests { + use super::*; + + #[test] + fn decode_push2_and_stop() -> Result<()> { + // 0x61 0xAA 0xBB = PUSH2 0xAABB + // 0x00 = STOP + let code = vec![0x61, 0xAA, 0xBB, 0x00]; + let insns = decode_instructions(&code)?; + + // PUSH2 then STOP + assert_eq!(insns.len(), 2); + + // PUSH2 at pc = 0 + let i0 = &insns[0]; + assert_eq!(i0.pc, 0); + assert_eq!(i0.op, Some(OpCode::PUSH2)); + assert_eq!(i0.immediate.as_ref(), &[0xAA, 0xBB]); + + // STOP at pc = 3 + let i1 = &insns[1]; + assert_eq!(i1.pc, 3); + assert_eq!(i1.op, Some(OpCode::STOP)); + assert!(i1.immediate.is_empty()); + + Ok(()) + } + + #[test] + fn decode_arithmetic_ops() -> Result<()> { + // 0x01 = ADD, 0x02 = MUL, 0x03 = SUB, 0x04 = DIV + let code = vec![0x01, 0x02, 0x03, 0x04]; + let insns = decode_instructions(&code)?; + + assert_eq!(insns.len(), 4); + + let expected = [(0, OpCode::ADD), (1, OpCode::MUL), (2, OpCode::SUB), (3, OpCode::DIV)]; + for ((pc, want_op), insn) in expected.iter().zip(insns.iter()) { + assert_eq!(insn.pc, *pc); + assert_eq!(insn.op, Some(*want_op)); + assert!(insn.immediate.is_empty()); + } + + Ok(()) + } +} diff --git a/crates/evm/core/src/lib.rs b/crates/evm/core/src/lib.rs index cf678551e21d6..4a9bad9392107 100644 --- a/crates/evm/core/src/lib.rs +++ b/crates/evm/core/src/lib.rs @@ -6,10 +6,11 @@ #![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] use crate::constants::DEFAULT_CREATE2_DEPLOYER; +use alloy_evm::eth::EthEvmContext; use alloy_primitives::Address; use auto_impl::auto_impl; use backend::DatabaseExt; -use revm::{inspectors::NoOpInspector, interpreter::CreateInputs, EvmContext, Inspector}; +use revm::{inspector::NoOpInspector, interpreter::CreateInputs, Inspector}; use revm_inspectors::access_list::AccessListInspector; #[macro_use] @@ -20,14 +21,16 @@ pub mod abi { pub use foundry_evm_abi::*; } -mod ic; - +pub mod env; +pub use env::*; pub mod backend; pub mod buffer; pub mod constants; pub mod decode; +pub mod either_evm; +pub mod evm; pub mod fork; -pub mod opcodes; +pub mod ic; pub mod opts; pub mod precompiles; pub mod state_snapshot; @@ -36,15 +39,15 @@ pub mod utils; /// An extension trait that allows us to add additional hooks to Inspector for later use in /// handlers. #[auto_impl(&mut, Box)] -pub trait InspectorExt: for<'a> Inspector<&'a mut dyn DatabaseExt> { +pub trait InspectorExt: for<'a> Inspector> { /// Determines whether the `DEFAULT_CREATE2_DEPLOYER` should be used for a CREATE2 frame. /// /// If this function returns true, we'll replace CREATE2 frame with a CALL frame to CREATE2 /// factory. fn should_use_create2_factory( &mut self, - _context: &mut EvmContext<&mut dyn DatabaseExt>, - _inputs: &mut CreateInputs, + _context: &mut EthEvmContext<&mut dyn DatabaseExt>, + _inputs: &CreateInputs, ) -> bool { false } diff --git a/crates/evm/core/src/opcodes.rs b/crates/evm/core/src/opcodes.rs deleted file mode 100644 index 3251036c78f7a..0000000000000 --- a/crates/evm/core/src/opcodes.rs +++ /dev/null @@ -1,25 +0,0 @@ -//! Opcode utils - -use revm::interpreter::OpCode; - -/// Returns true if the opcode modifies memory. -/// -/// -#[inline] -pub const fn modifies_memory(opcode: OpCode) -> bool { - matches!( - opcode, - OpCode::EXTCODECOPY | - OpCode::MLOAD | - OpCode::MSTORE | - OpCode::MSTORE8 | - OpCode::MCOPY | - OpCode::CODECOPY | - OpCode::CALLDATACOPY | - OpCode::RETURNDATACOPY | - OpCode::CALL | - OpCode::CALLCODE | - OpCode::DELEGATECALL | - OpCode::STATICCALL - ) -} diff --git a/crates/evm/core/src/opts.rs b/crates/evm/core/src/opts.rs index d135cceb904f9..d02050112716b 100644 --- a/crates/evm/core/src/opts.rs +++ b/crates/evm/core/src/opts.rs @@ -1,11 +1,15 @@ use super::fork::environment; -use crate::{constants::DEFAULT_CREATE2_DEPLOYER, fork::CreateFork}; +use crate::{ + constants::DEFAULT_CREATE2_DEPLOYER, + fork::{configure_env, CreateFork}, + EvmEnv, +}; use alloy_primitives::{Address, B256, U256}; use alloy_provider::{network::AnyRpcBlock, Provider}; use eyre::WrapErr; use foundry_common::{provider::ProviderBuilder, ALCHEMY_FREE_TIER_CUPS}; use foundry_config::{Chain, Config, GasLimit}; -use revm::primitives::{BlockEnv, CfgEnv, TxEnv}; +use revm::context::{BlockEnv, TxEnv}; use serde::{Deserialize, Serialize}; use std::fmt::Write; use url::Url; @@ -106,7 +110,7 @@ impl EvmOpts { /// /// If a `fork_url` is set, it gets configured with settings fetched from the endpoint (chain /// id, ) - pub async fn evm_env(&self) -> eyre::Result { + pub async fn evm_env(&self) -> eyre::Result { if let Some(ref fork_url) = self.fork_url { Ok(self.fork_evm_env(fork_url).await?.0) } else { @@ -116,10 +120,7 @@ impl EvmOpts { /// Returns the `revm::Env` that is configured with settings retrieved from the endpoint. /// And the block that was used to configure the environment. - pub async fn fork_evm_env( - &self, - fork_url: &str, - ) -> eyre::Result<(revm::primitives::Env, AnyRpcBlock)> { + pub async fn fork_evm_env(&self, fork_url: &str) -> eyre::Result<(crate::Env, AnyRpcBlock)> { let provider = ProviderBuilder::new(fork_url) .compute_units_per_second(self.get_compute_units_per_second()) .build()?; @@ -145,31 +146,29 @@ impl EvmOpts { } /// Returns the `revm::Env` configured with only local settings - pub fn local_evm_env(&self) -> revm::primitives::Env { - let mut cfg = CfgEnv::default(); - cfg.chain_id = self.env.chain_id.unwrap_or(foundry_common::DEV_CHAIN_ID); - cfg.limit_contract_code_size = self.env.code_size_limit.or(Some(usize::MAX)); - cfg.memory_limit = self.memory_limit; - // EIP-3607 rejects transactions from senders with deployed code. - // If EIP-3607 is enabled it can cause issues during fuzz/invariant tests if the - // caller is a contract. So we disable the check by default. - cfg.disable_eip3607 = true; - cfg.disable_block_gas_limit = self.disable_block_gas_limit; - - revm::primitives::Env { - block: BlockEnv { - number: U256::from(self.env.block_number), - coinbase: self.env.block_coinbase, - timestamp: U256::from(self.env.block_timestamp), - difficulty: U256::from(self.env.block_difficulty), - prevrandao: Some(self.env.block_prevrandao), - basefee: U256::from(self.env.block_base_fee_per_gas), - gas_limit: U256::from(self.gas_limit()), - ..Default::default() + pub fn local_evm_env(&self) -> crate::Env { + let cfg = configure_env( + self.env.chain_id.unwrap_or(foundry_common::DEV_CHAIN_ID), + self.memory_limit, + self.disable_block_gas_limit, + ); + + crate::Env { + evm_env: EvmEnv { + cfg_env: cfg, + block_env: BlockEnv { + number: self.env.block_number, + beneficiary: self.env.block_coinbase, + timestamp: self.env.block_timestamp, + difficulty: U256::from(self.env.block_difficulty), + prevrandao: Some(self.env.block_prevrandao), + basefee: self.env.block_base_fee_per_gas, + gas_limit: self.gas_limit(), + ..Default::default() + }, }, - cfg, tx: TxEnv { - gas_price: U256::from(self.env.gas_price.unwrap_or_default()), + gas_price: self.env.gas_price.unwrap_or_default().into(), gas_limit: self.gas_limit(), caller: self.sender, ..Default::default() @@ -190,9 +189,9 @@ impl EvmOpts { /// /// for `mainnet` and `--fork-block-number 14435000` on mac the corresponding storage cache will /// be at `~/.foundry/cache/mainnet/14435000/storage.json`. - pub fn get_fork(&self, config: &Config, env: revm::primitives::Env) -> Option { + pub fn get_fork(&self, config: &Config, env: crate::Env) -> Option { let url = self.fork_url.clone()?; - let enable_caching = config.enable_caching(&url, env.cfg.chain_id); + let enable_caching = config.enable_caching(&url, env.evm_env.cfg_env.chain_id); Some(CreateFork { url, enable_caching, env, evm_opts: self.clone() }) } @@ -221,7 +220,7 @@ impl EvmOpts { if self.no_rpc_rate_limit { u64::MAX } else if let Some(cups) = self.compute_units_per_second { - return cups; + cups } else { ALCHEMY_FREE_TIER_CUPS } diff --git a/crates/evm/core/src/utils.rs b/crates/evm/core/src/utils.rs index 7125535694d49..9ff6bcc8aec63 100644 --- a/crates/evm/core/src/utils.rs +++ b/crates/evm/core/src/utils.rs @@ -1,27 +1,21 @@ -pub use crate::ic::*; -use crate::{backend::DatabaseExt, constants::DEFAULT_CREATE2_DEPLOYER_CODEHASH, InspectorExt}; use alloy_consensus::BlockHeader; use alloy_json_abi::{Function, JsonAbi}; -use alloy_network::{AnyTxEnvelope, TransactionResponse}; +use alloy_network::{ + eip2718::{ + EIP1559_TX_TYPE_ID, EIP2930_TX_TYPE_ID, EIP4844_TX_TYPE_ID, EIP7702_TX_TYPE_ID, + LEGACY_TX_TYPE_ID, + }, + AnyTxEnvelope, TransactionResponse, +}; use alloy_primitives::{Address, Selector, TxKind, B256, U256}; use alloy_provider::{network::BlockResponse, Network}; use alloy_rpc_types::{Transaction, TransactionRequest}; use foundry_common::is_impersonated_tx; use foundry_config::NamedChain; -use foundry_fork_db::DatabaseError; -use revm::{ - handler::register::EvmHandler, - interpreter::{ - return_ok, CallInputs, CallOutcome, CallScheme, CallValue, CreateInputs, CreateOutcome, - Gas, InstructionResult, InterpreterResult, - }, - precompile::secp256r1::P256VERIFY, - primitives::{CreateScheme, EVMError, HandlerCfg, SpecId, KECCAK_EMPTY}, - FrameOrResult, FrameResult, -}; -use std::{cell::RefCell, rc::Rc, sync::Arc}; +use revm::primitives::hardfork::SpecId; +pub use revm::state::EvmState as StateChangeset; -pub use revm::primitives::EvmState as StateChangeset; +use crate::EnvMut; /// Depending on the configured chain id and block number this should apply any specific changes /// @@ -30,10 +24,11 @@ pub use revm::primitives::EvmState as StateChangeset; /// /// Should be called with proper chain id (retrieved from provider if not provided). pub fn apply_chain_and_block_specific_env_changes( - env: &mut revm::primitives::Env, + env: EnvMut<'_>, block: &N::BlockResponse, ) { use NamedChain::*; + if let Ok(chain) = NamedChain::try_from(env.cfg.chain_id) { let block_number = block.header().number(); @@ -56,7 +51,7 @@ pub fn apply_chain_and_block_specific_env_changes( env.block.prevrandao = Some(env.block.difficulty.into()); return; } - Moonbeam | Moonbase | Moonriver | MoonbeamDev | Rsk => { + Moonbeam | Moonbase | Moonriver | MoonbeamDev | Rsk | RskTestnet => { if env.block.prevrandao.is_none() { // env.block.prevrandao = Some(B256::random()); @@ -72,7 +67,7 @@ pub fn apply_chain_and_block_specific_env_changes( serde_json::from_value::(l1_block_number).ok() }) { - env.block.number = l1_block_number; + env.block.number = l1_block_number.to(); } } _ => {} @@ -98,7 +93,7 @@ pub fn get_function<'a>( /// Configures the env for the given RPC transaction. /// Accounts for an impersonated transaction by resetting the `env.tx.caller` field to `tx.from`. -pub fn configure_tx_env(env: &mut revm::primitives::Env, tx: &Transaction) { +pub fn configure_tx_env(env: &mut EnvMut<'_>, tx: &Transaction) { let impersonated_from = is_impersonated_tx(&tx.inner).then_some(tx.from()); if let AnyTxEnvelope::Ethereum(tx) = &tx.inner.inner() { configure_tx_req_env(env, &tx.clone().into(), impersonated_from).expect("cannot fail"); @@ -109,7 +104,7 @@ pub fn configure_tx_env(env: &mut revm::primitives::Env, tx: &Transaction, tx: &TransactionRequest, impersonated_from: Option
, ) -> eyre::Result<()> { @@ -127,241 +122,58 @@ pub fn configure_tx_req_env( chain_id, ref blob_versioned_hashes, ref access_list, - transaction_type: _, + transaction_type, ref authorization_list, sidecar: _, } = *tx; + // If no transaction type is provided, we need to infer it from the other fields. + let tx_type = transaction_type.unwrap_or_else(|| { + if authorization_list.is_some() { + EIP7702_TX_TYPE_ID + } else if blob_versioned_hashes.is_some() { + EIP4844_TX_TYPE_ID + } else if max_fee_per_gas.is_some() || max_priority_fee_per_gas.is_some() { + EIP1559_TX_TYPE_ID + } else if access_list.is_some() { + EIP2930_TX_TYPE_ID + } else { + LEGACY_TX_TYPE_ID + } + }); + env.tx.tx_type = tx_type; + // If no `to` field then set create kind: https://eips.ethereum.org/EIPS/eip-2470#deployment-transaction - env.tx.transact_to = to.unwrap_or(TxKind::Create); + env.tx.kind = to.unwrap_or(TxKind::Create); // If the transaction is impersonated, we need to set the caller to the from // address Ref: https://github.com/foundry-rs/foundry/issues/9541 env.tx.caller = impersonated_from.unwrap_or(from.ok_or_else(|| eyre::eyre!("missing `from` field"))?); env.tx.gas_limit = gas.ok_or_else(|| eyre::eyre!("missing `gas` field"))?; - env.tx.nonce = nonce; + env.tx.nonce = nonce.unwrap_or_default(); env.tx.value = value.unwrap_or_default(); env.tx.data = input.input().cloned().unwrap_or_default(); env.tx.chain_id = chain_id; // Type 1, EIP-2930 - env.tx.access_list = access_list.clone().unwrap_or_default().0.into_iter().collect(); + env.tx.access_list = access_list.clone().unwrap_or_default(); // Type 2, EIP-1559 - env.tx.gas_price = U256::from(gas_price.or(max_fee_per_gas).unwrap_or_default()); - env.tx.gas_priority_fee = max_priority_fee_per_gas.map(U256::from); + env.tx.gas_price = gas_price.or(max_fee_per_gas).unwrap_or_default(); + env.tx.gas_priority_fee = max_priority_fee_per_gas; // Type 3, EIP-4844 env.tx.blob_hashes = blob_versioned_hashes.clone().unwrap_or_default(); - env.tx.max_fee_per_blob_gas = max_fee_per_blob_gas.map(U256::from); + env.tx.max_fee_per_blob_gas = max_fee_per_blob_gas.unwrap_or_default(); // Type 4, EIP-7702 - if let Some(authorization_list) = authorization_list { - env.tx.authorization_list = - Some(revm::primitives::AuthorizationList::Signed(authorization_list.clone())); - } + env.tx.set_signed_authorization(authorization_list.clone().unwrap_or_default()); Ok(()) } /// Get the gas used, accounting for refunds pub fn gas_used(spec: SpecId, spent: u64, refunded: u64) -> u64 { - let refund_quotient = if SpecId::enabled(spec, SpecId::LONDON) { 5 } else { 2 }; + let refund_quotient = if SpecId::is_enabled_in(spec, SpecId::LONDON) { 5 } else { 2 }; spent - (refunded).min(spent / refund_quotient) } - -fn get_create2_factory_call_inputs( - salt: U256, - inputs: CreateInputs, - deployer: Address, -) -> CallInputs { - let calldata = [&salt.to_be_bytes::<32>()[..], &inputs.init_code[..]].concat(); - CallInputs { - caller: inputs.caller, - bytecode_address: deployer, - target_address: deployer, - scheme: CallScheme::Call, - value: CallValue::Transfer(inputs.value), - input: calldata.into(), - gas_limit: inputs.gas_limit, - is_static: false, - return_memory_offset: 0..0, - is_eof: false, - } -} - -/// Used for routing certain CREATE2 invocations through CREATE2_DEPLOYER. -/// -/// Overrides create hook with CALL frame if [InspectorExt::should_use_create2_factory] returns -/// true. Keeps track of overridden frames and handles outcome in the overridden insert_call_outcome -/// hook by inserting decoded address directly into interpreter. -/// -/// Should be installed after [revm::inspector_handle_register] and before any other registers. -pub fn create2_handler_register( - handler: &mut EvmHandler<'_, I, &mut dyn DatabaseExt>, -) { - let create2_overrides = Rc::>>::new(RefCell::new(Vec::new())); - - let create2_overrides_inner = create2_overrides.clone(); - let old_handle = handler.execution.create.clone(); - handler.execution.create = - Arc::new(move |ctx, mut inputs| -> Result> { - let CreateScheme::Create2 { salt } = inputs.scheme else { - return old_handle(ctx, inputs); - }; - if !ctx.external.should_use_create2_factory(&mut ctx.evm, &mut inputs) { - return old_handle(ctx, inputs); - } - - let gas_limit = inputs.gas_limit; - - // Get CREATE2 deployer. - let create2_deployer = ctx.external.create2_deployer(); - // Generate call inputs for CREATE2 factory. - let mut call_inputs = get_create2_factory_call_inputs(salt, *inputs, create2_deployer); - - // Call inspector to change input or return outcome. - let outcome = ctx.external.call(&mut ctx.evm, &mut call_inputs); - - // Push data about current override to the stack. - create2_overrides_inner - .borrow_mut() - .push((ctx.evm.journaled_state.depth(), call_inputs.clone())); - - // Sanity check that CREATE2 deployer exists. - let code_hash = ctx.evm.load_account(create2_deployer)?.info.code_hash; - if code_hash == KECCAK_EMPTY { - return Ok(FrameOrResult::Result(FrameResult::Call(CallOutcome { - result: InterpreterResult { - result: InstructionResult::Revert, - output: format!("missing CREATE2 deployer: {create2_deployer}").into(), - gas: Gas::new(gas_limit), - }, - memory_offset: 0..0, - }))) - } else if code_hash != DEFAULT_CREATE2_DEPLOYER_CODEHASH { - return Ok(FrameOrResult::Result(FrameResult::Call(CallOutcome { - result: InterpreterResult { - result: InstructionResult::Revert, - output: "invalid CREATE2 deployer bytecode".into(), - gas: Gas::new(gas_limit), - }, - memory_offset: 0..0, - }))) - } - - // Handle potential inspector override. - if let Some(outcome) = outcome { - return Ok(FrameOrResult::Result(FrameResult::Call(outcome))); - } - - // Create CALL frame for CREATE2 factory invocation. - let mut frame_or_result = ctx.evm.make_call_frame(&call_inputs); - - if let Ok(FrameOrResult::Frame(frame)) = &mut frame_or_result { - ctx.external - .initialize_interp(&mut frame.frame_data_mut().interpreter, &mut ctx.evm) - } - frame_or_result - }); - - let create2_overrides_inner = create2_overrides; - let old_handle = handler.execution.insert_call_outcome.clone(); - handler.execution.insert_call_outcome = - Arc::new(move |ctx, frame, shared_memory, mut outcome| { - // If we are on the depth of the latest override, handle the outcome. - if create2_overrides_inner - .borrow() - .last() - .is_some_and(|(depth, _)| *depth == ctx.evm.journaled_state.depth()) - { - let (_, call_inputs) = create2_overrides_inner.borrow_mut().pop().unwrap(); - outcome = ctx.external.call_end(&mut ctx.evm, &call_inputs, outcome); - - // Decode address from output. - let address = match outcome.instruction_result() { - return_ok!() => Address::try_from(outcome.output().as_ref()) - .map_err(|_| { - outcome.result = InterpreterResult { - result: InstructionResult::Revert, - output: "invalid CREATE2 factory output".into(), - gas: Gas::new(call_inputs.gas_limit), - }; - }) - .ok(), - _ => None, - }; - frame - .frame_data_mut() - .interpreter - .insert_create_outcome(CreateOutcome { address, result: outcome.result }); - - Ok(()) - } else { - old_handle(ctx, frame, shared_memory, outcome) - } - }); -} - -/// Adds Odyssey P256 precompile to the list of loaded precompiles. -pub fn odyssey_handler_register(handler: &mut EvmHandler<'_, EXT, DB>) { - let prev = handler.pre_execution.load_precompiles.clone(); - handler.pre_execution.load_precompiles = Arc::new(move || { - let mut loaded_precompiles = prev(); - - loaded_precompiles.extend([P256VERIFY]); - - loaded_precompiles - }); -} - -/// Creates a new EVM with the given inspector. -pub fn new_evm_with_inspector<'evm, 'i, 'db, I: InspectorExt + ?Sized>( - db: &'db mut dyn DatabaseExt, - env: revm::primitives::EnvWithHandlerCfg, - inspector: &'i mut I, -) -> revm::Evm<'evm, &'i mut I, &'db mut dyn DatabaseExt> { - let revm::primitives::EnvWithHandlerCfg { env, handler_cfg } = env; - - // NOTE: We could use `revm::Evm::builder()` here, but on the current patch it has some - // performance issues. - /* - revm::Evm::builder() - .with_db(db) - .with_env(env) - .with_external_context(inspector) - .with_handler_cfg(handler_cfg) - .append_handler_register(revm::inspector_handle_register) - .append_handler_register(create2_handler_register) - .build() - */ - - let mut handler = revm::Handler::new(handler_cfg); - handler.append_handler_register_plain(revm::inspector_handle_register); - if inspector.is_odyssey() { - handler.append_handler_register_plain(odyssey_handler_register); - } - handler.append_handler_register_plain(create2_handler_register); - - let context = revm::Context::new(revm::EvmContext::new_with_env(db, env), inspector); - - revm::Evm::new(context, handler) -} - -pub fn new_evm_with_existing_context<'a>( - inner: revm::InnerEvmContext<&'a mut dyn DatabaseExt>, - inspector: &'a mut dyn InspectorExt, -) -> revm::Evm<'a, &'a mut dyn InspectorExt, &'a mut dyn DatabaseExt> { - let handler_cfg = HandlerCfg::new(inner.spec_id()); - - let mut handler = revm::Handler::new(handler_cfg); - handler.append_handler_register_plain(revm::inspector_handle_register); - if inspector.is_odyssey() { - handler.append_handler_register_plain(odyssey_handler_register); - } - handler.append_handler_register_plain(create2_handler_register); - - let context = - revm::Context::new(revm::EvmContext { inner, precompiles: Default::default() }, inspector); - revm::Evm::new(context, handler) -} diff --git a/crates/evm/coverage/src/anchors.rs b/crates/evm/coverage/src/anchors.rs index 3d46065518c1b..415ad744b30e8 100644 --- a/crates/evm/coverage/src/anchors.rs +++ b/crates/evm/coverage/src/anchors.rs @@ -3,8 +3,8 @@ use crate::analysis::SourceAnalysis; use alloy_primitives::map::rustc_hash::FxHashSet; use eyre::ensure; use foundry_compilers::artifacts::sourcemap::{SourceElement, SourceMap}; -use foundry_evm_core::utils::IcPcMap; -use revm::interpreter::opcode; +use foundry_evm_core::ic::IcPcMap; +use revm::bytecode::opcode; /// Attempts to find anchors for the given items using the given source map and bytecode. pub fn find_anchors( diff --git a/crates/evm/coverage/src/inspector.rs b/crates/evm/coverage/src/inspector.rs index 6a6c50b093c8f..dfed9589e55fc 100644 --- a/crates/evm/coverage/src/inspector.rs +++ b/crates/evm/coverage/src/inspector.rs @@ -1,6 +1,11 @@ use crate::{HitMap, HitMaps}; use alloy_primitives::B256; -use revm::{interpreter::Interpreter, Database, EvmContext, Inspector}; +use revm::{ + context::ContextTr, + inspector::JournalExt, + interpreter::{interpreter_types::Jumps, Interpreter}, + Inspector, +}; use std::ptr::NonNull; /// Inspector implementation for collecting coverage information. @@ -29,16 +34,19 @@ impl Default for CoverageCollector { } } -impl Inspector for CoverageCollector { - fn initialize_interp(&mut self, interpreter: &mut Interpreter, _context: &mut EvmContext) { +impl Inspector for CoverageCollector +where + CTX: ContextTr, +{ + fn initialize_interp(&mut self, interpreter: &mut Interpreter, _context: &mut CTX) { get_or_insert_contract_hash(interpreter); self.insert_map(interpreter); } #[inline] - fn step(&mut self, interpreter: &mut Interpreter, _context: &mut EvmContext) { + fn step(&mut self, interpreter: &mut Interpreter, _context: &mut CTX) { let map = self.get_or_insert_map(interpreter); - map.hit(interpreter.program_counter() as u32); + map.hit(interpreter.bytecode.pc() as u32); } } @@ -64,14 +72,14 @@ impl CoverageCollector { #[cold] #[inline(never)] - fn insert_map(&mut self, interpreter: &Interpreter) { - let Some(hash) = interpreter.contract.hash else { eof_panic() }; + fn insert_map(&mut self, interpreter: &mut Interpreter) { + let hash = interpreter.bytecode.hash().unwrap_or_else(|| eof_panic()); self.current_hash = hash; // Converts the mutable reference to a `NonNull` pointer. self.current_map = self .maps .entry(hash) - .or_insert_with(|| HitMap::new(interpreter.contract.bytecode.original_bytes())) + .or_insert_with(|| HitMap::new(interpreter.bytecode.original_bytes())) .into(); } } @@ -81,18 +89,11 @@ impl CoverageCollector { /// If the contract hash is zero (contract not yet created but it's going to be created in current /// tx) then the hash is calculated from the bytecode. #[inline] -fn get_or_insert_contract_hash(interpreter: &mut Interpreter) -> &B256 { - let Some(hash) = interpreter.contract.hash.as_mut() else { eof_panic() }; - if hash.is_zero() { - set_contract_hash(hash, &interpreter.contract.bytecode); +fn get_or_insert_contract_hash(interpreter: &mut Interpreter) -> B256 { + if interpreter.bytecode.hash().is_none_or(|h| h.is_zero()) { + interpreter.bytecode.regenerate_hash(); } - hash -} - -#[cold] -#[inline(never)] -fn set_contract_hash(hash: &mut B256, bytecode: &revm::primitives::Bytecode) { - *hash = bytecode.hash_slow(); + interpreter.bytecode.hash().unwrap_or_else(|| eof_panic()) } #[cold] diff --git a/crates/evm/evm/Cargo.toml b/crates/evm/evm/Cargo.toml index 053a534405f7e..06074e305b630 100644 --- a/crates/evm/evm/Cargo.toml +++ b/crates/evm/evm/Cargo.toml @@ -24,6 +24,7 @@ foundry-evm-fuzz.workspace = true foundry-evm-traces.workspace = true alloy-dyn-abi = { workspace = true, features = ["arbitrary", "eip712"] } +alloy-evm.workspace = true alloy-json-abi.workspace = true alloy-primitives = { workspace = true, features = [ "serde", diff --git a/crates/evm/evm/src/executors/builder.rs b/crates/evm/evm/src/executors/builder.rs index c371a6550b879..a60934046efe1 100644 --- a/crates/evm/evm/src/executors/builder.rs +++ b/crates/evm/evm/src/executors/builder.rs @@ -1,6 +1,6 @@ use crate::{executors::Executor, inspectors::InspectorStackBuilder}; -use foundry_evm_core::backend::Backend; -use revm::primitives::{Env, EnvWithHandlerCfg, SpecId}; +use foundry_evm_core::{backend::Backend, Env}; +use revm::primitives::hardfork::SpecId; /// The builder that allows to configure an evm [`Executor`] which a stack of optional /// [`revm::Inspector`]s, such as [`Cheatcodes`]. @@ -27,7 +27,7 @@ impl Default for ExecutorBuilder { Self { stack: InspectorStackBuilder::new(), gas_limit: None, - spec_id: SpecId::LATEST, + spec_id: SpecId::default(), legacy_assertions: false, } } @@ -76,13 +76,18 @@ impl ExecutorBuilder { pub fn build(self, env: Env, db: Backend) -> Executor { let Self { mut stack, gas_limit, spec_id, legacy_assertions } = self; if stack.block.is_none() { - stack.block = Some(env.block.clone()); + stack.block = Some(env.evm_env.block_env.clone()); } if stack.gas_price.is_none() { stack.gas_price = Some(env.tx.gas_price); } - let gas_limit = gas_limit.unwrap_or_else(|| env.block.gas_limit.saturating_to()); - let env = EnvWithHandlerCfg::new_with_spec_id(Box::new(env), spec_id); + let gas_limit = gas_limit.unwrap_or(env.evm_env.block_env.gas_limit); + let env = Env::new_with_spec_id( + env.evm_env.cfg_env.clone(), + env.evm_env.block_env.clone(), + env.tx, + spec_id, + ); Executor::new(db, env, stack.build(), gas_limit, legacy_assertions) } } diff --git a/crates/evm/evm/src/executors/fuzz/mod.rs b/crates/evm/evm/src/executors/fuzz/mod.rs index 3e132c733b1a5..a64b072193587 100644 --- a/crates/evm/evm/src/executors/fuzz/mod.rs +++ b/crates/evm/evm/src/executors/fuzz/mod.rs @@ -207,7 +207,7 @@ impl FuzzedExecutor { } else { result.reason = (!reason.is_empty()).then_some(reason); let args = if let Some(data) = calldata.get(4..) { - func.abi_decode_input(data, false).unwrap_or_default() + func.abi_decode_input(data).unwrap_or_default() } else { vec![] }; diff --git a/crates/evm/evm/src/executors/invariant/mod.rs b/crates/evm/evm/src/executors/invariant/mod.rs index 11c38bf43f29b..4e2914ae79e04 100644 --- a/crates/evm/evm/src/executors/invariant/mod.rs +++ b/crates/evm/evm/src/executors/invariant/mod.rs @@ -2,7 +2,7 @@ use crate::{ executors::{Executor, RawCallResult}, inspectors::Fuzzer, }; -use alloy_primitives::{Address, Bytes, FixedBytes, Selector, U256}; +use alloy_primitives::{map::HashMap, Address, Bytes, FixedBytes, Selector, U256}; use alloy_sol_types::{sol, SolCall}; use eyre::{eyre, ContextCompat, Result}; use foundry_common::contracts::{ContractsByAddress, ContractsByArtifact}; @@ -30,7 +30,7 @@ use proptest::{ test_runner::{TestCaseError, TestRunner}, }; use result::{assert_after_invariant, assert_invariants, can_continue}; -use revm::primitives::HashMap; +use revm::state::Account; use shrink::shrink_sequence; use std::{ cell::RefCell, @@ -598,27 +598,27 @@ impl<'a> InvariantExecutor<'a> { /// /// targetArtifactSelectors > excludeArtifacts > targetArtifacts pub fn select_contract_artifacts(&mut self, invariant_address: Address) -> Result<()> { - let result = self + let targeted_artifact_selectors = self .executor .call_sol_default(invariant_address, &IInvariantTest::targetArtifactSelectorsCall {}); // Insert them into the executor `targeted_abi`. for IInvariantTest::FuzzArtifactSelector { artifact, selectors } in - result.targetedArtifactSelectors + targeted_artifact_selectors { let identifier = self.validate_selected_contract(artifact, &selectors)?; self.artifact_filters.targeted.entry(identifier).or_default().extend(selectors); } - let selected = self + let targeted_artifacts = self .executor .call_sol_default(invariant_address, &IInvariantTest::targetArtifactsCall {}); - let excluded = self + let excluded_artifacts = self .executor .call_sol_default(invariant_address, &IInvariantTest::excludeArtifactsCall {}); // Insert `excludeArtifacts` into the executor `excluded_abi`. - for contract in excluded.excludedArtifacts { + for contract in excluded_artifacts { let identifier = self.validate_selected_contract(contract, &[])?; if !self.artifact_filters.excluded.contains(&identifier) { @@ -648,7 +648,7 @@ impl<'a> InvariantExecutor<'a> { // Insert `targetArtifacts` into the executor `targeted_abi`, if they have not been seen // before. - for contract in selected.targetedArtifacts { + for contract in targeted_artifacts { let identifier = self.validate_selected_contract(contract, &[])?; if !self.artifact_filters.targeted.contains_key(&identifier) && @@ -690,14 +690,10 @@ impl<'a> InvariantExecutor<'a> { &self, to: Address, ) -> Result<(SenderFilters, FuzzRunIdentifiedContracts)> { - let targeted_senders = self - .executor - .call_sol_default(to, &IInvariantTest::targetSendersCall {}) - .targetedSenders; - let mut excluded_senders = self - .executor - .call_sol_default(to, &IInvariantTest::excludeSendersCall {}) - .excludedSenders; + let targeted_senders = + self.executor.call_sol_default(to, &IInvariantTest::targetSendersCall {}); + let mut excluded_senders = + self.executor.call_sol_default(to, &IInvariantTest::excludeSendersCall {}); // Extend with default excluded addresses - https://github.com/foundry-rs/foundry/issues/4163 excluded_senders.extend([ CHEATCODE_ADDRESS, @@ -708,14 +704,8 @@ impl<'a> InvariantExecutor<'a> { excluded_senders.extend(PRECOMPILES); let sender_filters = SenderFilters::new(targeted_senders, excluded_senders); - let selected = self - .executor - .call_sol_default(to, &IInvariantTest::targetContractsCall {}) - .targetedContracts; - let excluded = self - .executor - .call_sol_default(to, &IInvariantTest::excludeContractsCall {}) - .excludedContracts; + let selected = self.executor.call_sol_default(to, &IInvariantTest::targetContractsCall {}); + let excluded = self.executor.call_sol_default(to, &IInvariantTest::excludeContractsCall {}); let contracts = self .setup_contracts @@ -762,8 +752,7 @@ impl<'a> InvariantExecutor<'a> { ) -> Result<()> { let interfaces = self .executor - .call_sol_default(invariant_address, &IInvariantTest::targetInterfacesCall {}) - .targetedInterfaces; + .call_sol_default(invariant_address, &IInvariantTest::targetInterfacesCall {}); // Since `targetInterfaces` returns a tuple array there is no guarantee // that the addresses are unique this map is used to merge functions of @@ -815,14 +804,14 @@ impl<'a> InvariantExecutor<'a> { // Collect contract functions marked as target for fuzzing campaign. let selectors = self.executor.call_sol_default(address, &IInvariantTest::targetSelectorsCall {}); - for IInvariantTest::FuzzSelector { addr, selectors } in selectors.targetedSelectors { + for IInvariantTest::FuzzSelector { addr, selectors } in selectors { self.add_address_with_functions(addr, &selectors, false, targeted_contracts)?; } // Collect contract functions excluded from fuzzing campaign. - let selectors = + let excluded_selectors = self.executor.call_sol_default(address, &IInvariantTest::excludeSelectorsCall {}); - for IInvariantTest::FuzzSelector { addr, selectors } in selectors.excludedSelectors { + for IInvariantTest::FuzzSelector { addr, selectors } in excluded_selectors { self.add_address_with_functions(addr, &selectors, true, targeted_contracts)?; } @@ -865,7 +854,7 @@ impl<'a> InvariantExecutor<'a> { /// randomly generated addresses. fn collect_data( invariant_test: &InvariantTest, - state_changeset: &mut HashMap, + state_changeset: &mut HashMap, tx: &BasicTxDetails, call_result: &RawCallResult, run_depth: u32, diff --git a/crates/evm/evm/src/executors/invariant/replay.rs b/crates/evm/evm/src/executors/invariant/replay.rs index 89d7aa242a035..ab11f3728ae99 100644 --- a/crates/evm/evm/src/executors/invariant/replay.rs +++ b/crates/evm/evm/src/executors/invariant/replay.rs @@ -4,7 +4,7 @@ use super::{ }; use crate::executors::Executor; use alloy_dyn_abi::JsonAbiExt; -use alloy_primitives::{map::HashMap, Log}; +use alloy_primitives::{map::HashMap, Log, U256}; use eyre::Result; use foundry_common::{ContractsByAddress, ContractsByArtifact}; use foundry_evm_coverage::HitMaps; @@ -16,7 +16,6 @@ use foundry_evm_traces::{load_contracts, TraceKind, TraceMode, Traces}; use indicatif::ProgressBar; use parking_lot::RwLock; use proptest::test_runner::TestError; -use revm::primitives::U256; use std::sync::Arc; /// Replays a call sequence for collecting logs and traces. diff --git a/crates/evm/evm/src/executors/mod.rs b/crates/evm/evm/src/executors/mod.rs index 5937912e17601..bfd57a4a0ccee 100644 --- a/crates/evm/evm/src/executors/mod.rs +++ b/crates/evm/evm/src/executors/mod.rs @@ -6,14 +6,17 @@ // `Executor` struct should be accessed using a trait defined in `foundry-evm-core` instead of // the concrete `Executor` type. -use crate::inspectors::{ - cheatcodes::BroadcastableTransactions, Cheatcodes, InspectorData, InspectorStack, +use crate::{ + inspectors::{ + cheatcodes::BroadcastableTransactions, Cheatcodes, InspectorData, InspectorStack, + }, + Env, }; use alloy_dyn_abi::{DynSolValue, FunctionExt, JsonAbiExt}; use alloy_json_abi::Function; use alloy_primitives::{ map::{AddressHashMap, HashMap}, - Address, Bytes, Log, U256, + Address, Bytes, Log, TxKind, U256, }; use alloy_sol_types::{sol, SolCall}; use foundry_evm_core::{ @@ -24,17 +27,20 @@ use foundry_evm_core::{ }, decode::{RevertDecoder, SkipReason}, utils::StateChangeset, - InspectorExt, + EvmEnv, InspectorExt, }; use foundry_evm_coverage::HitMaps; use foundry_evm_traces::{SparsedTraceArena, TraceMode}; use revm::{ - db::{DatabaseCommit, DatabaseRef}, - interpreter::{return_ok, InstructionResult}, - primitives::{ - AuthorizationList, BlockEnv, Bytecode, Env, EnvWithHandlerCfg, ExecutionResult, Output, - ResultAndState, SignedAuthorization, SpecId, TxEnv, TxKind, + bytecode::Bytecode, + context::{BlockEnv, TxEnv}, + context_interface::{ + result::{ExecutionResult, Output, ResultAndState}, + transaction::SignedAuthorization, }, + database::{DatabaseCommit, DatabaseRef}, + interpreter::{return_ok, InstructionResult}, + primitives::hardfork::SpecId, }; use std::{ borrow::Cow, @@ -83,7 +89,7 @@ pub struct Executor { // so the performance difference should be negligible. backend: Backend, /// The EVM environment. - env: EnvWithHandlerCfg, + env: Env, /// The Revm inspector stack. inspector: InspectorStack, /// The gas limit for calls and deployments. @@ -103,7 +109,7 @@ impl Executor { #[inline] pub fn new( mut backend: Backend, - env: EnvWithHandlerCfg, + env: Env, inspector: InspectorStack, gas_limit: u64, legacy_assertions: bool, @@ -112,7 +118,7 @@ impl Executor { // do not fail. backend.insert_account_info( CHEATCODE_ADDRESS, - revm::primitives::AccountInfo { + revm::state::AccountInfo { code: Some(Bytecode::new_raw(Bytes::from_static(&[0]))), // Also set the code hash manually so that it's not computed later. // The code hash value does not matter, as long as it's not zero or `KECCAK_EMPTY`. @@ -125,7 +131,12 @@ impl Executor { } fn clone_with_backend(&self, backend: Backend) -> Self { - let env = EnvWithHandlerCfg::new_with_spec_id(Box::new(self.env().clone()), self.spec_id()); + let env = Env::new_with_spec_id( + self.env.evm_env.cfg_env.clone(), + self.env.evm_env.block_env.clone(), + self.env.tx.clone(), + self.spec_id(), + ); Self::new(backend, env, self.inspector().clone(), self.gas_limit, self.legacy_assertions) } @@ -141,12 +152,12 @@ impl Executor { /// Returns a reference to the EVM environment. pub fn env(&self) -> &Env { - &self.env.env + &self.env } /// Returns a mutable reference to the EVM environment. pub fn env_mut(&mut self) -> &mut Env { - &mut self.env.env + &mut self.env } /// Returns a reference to the EVM inspector. @@ -161,12 +172,12 @@ impl Executor { /// Returns the EVM spec ID. pub fn spec_id(&self) -> SpecId { - self.env.spec_id() + self.env.evm_env.cfg_env.spec } /// Sets the EVM spec ID. pub fn set_spec_id(&mut self, spec_id: SpecId) { - self.env.handler_cfg.spec_id = spec_id; + self.env.evm_env.cfg_env.spec = spec_id; } /// Returns the gas limit for calls and deployments. @@ -238,6 +249,7 @@ impl Executor { let mut account = self.backend().basic_ref(address)?.unwrap_or_default(); account.nonce = nonce; self.backend_mut().insert_account_info(address, account); + self.env_mut().tx.nonce = nonce; Ok(()) } @@ -293,17 +305,17 @@ impl Executor { /// /// # Panics /// - /// Panics if `env.tx.transact_to` is not `TxKind::Create(_)`. + /// Panics if `env.tx.kind` is not `TxKind::Create(_)`. #[instrument(name = "deploy", level = "debug", skip_all)] pub fn deploy_with_env( &mut self, - env: EnvWithHandlerCfg, + env: Env, rd: Option<&RevertDecoder>, ) -> Result { assert!( - matches!(env.tx.transact_to, TxKind::Create), + matches!(env.tx.kind, TxKind::Create), "Expected create transaction, got {:?}", - env.tx.transact_to + env.tx.kind ); trace!(sender=%env.tx.caller, "deploying contract"); @@ -344,9 +356,9 @@ impl Executor { res = res.into_result(rd)?; // record any changes made to the block's environment during setup - self.env_mut().block = res.env.block.clone(); + self.env_mut().evm_env.block_env = res.env.evm_env.block_env.clone(); // and also the chainid, which can be set manually - self.env_mut().cfg.chain_id = res.env.cfg.chain_id; + self.env_mut().evm_env.cfg_env.chain_id = res.env.evm_env.cfg_env.chain_id; let success = self.is_raw_call_success(to, Cow::Borrowed(&res.state_changeset), &res, false); @@ -384,7 +396,7 @@ impl Executor { let calldata = Bytes::from(args.abi_encode()); let mut raw = self.call_raw(from, to, calldata, value)?; raw = raw.into_result(rd)?; - Ok(CallResult { decoded_result: C::abi_decode_returns(&raw.result, false)?, raw }) + Ok(CallResult { decoded_result: C::abi_decode_returns(&raw.result)?, raw }) } /// Performs a call to an account on the current state of the VM. @@ -425,7 +437,8 @@ impl Executor { authorization_list: Vec, ) -> eyre::Result { let mut env = self.build_test_env(from, to.into(), calldata, value); - env.tx.authorization_list = Some(AuthorizationList::Signed(authorization_list)); + env.tx.set_signed_authorization(authorization_list); + env.tx.tx_type = 4; self.call_with_env(env) } @@ -445,7 +458,7 @@ impl Executor { /// /// The state after the call is **not** persisted. #[instrument(name = "call", level = "debug", skip_all)] - pub fn call_with_env(&self, mut env: EnvWithHandlerCfg) -> eyre::Result { + pub fn call_with_env(&self, mut env: Env) -> eyre::Result { let mut inspector = self.inspector().clone(); let mut backend = CowBackend::new_borrowed(self.backend()); let result = backend.inspect(&mut env, &mut inspector)?; @@ -454,7 +467,7 @@ impl Executor { /// Execute the transaction configured in `env.tx`. #[instrument(name = "transact", level = "debug", skip_all)] - pub fn transact_with_env(&mut self, mut env: EnvWithHandlerCfg) -> eyre::Result { + pub fn transact_with_env(&mut self, mut env: Env) -> eyre::Result { let mut inspector = self.inspector().clone(); let backend = self.backend_mut(); let result = backend.inspect(&mut env, &mut inspector)?; @@ -615,7 +628,7 @@ impl Executor { let executor = self.clone_with_backend(backend); let call = executor.call_sol(CALLER, address, &ITest::failedCall {}, U256::ZERO, None); match call { - Ok(CallResult { raw: _, decoded_result: ITest::failedReturn { failed } }) => { + Ok(CallResult { raw: _, decoded_result: failed }) => { trace!(failed, "DSTest::failed()"); !failed } @@ -631,37 +644,36 @@ impl Executor { /// /// If using a backend with cheatcodes, `tx.gas_price` and `block.number` will be overwritten by /// the cheatcode state in between calls. - fn build_test_env( - &self, - caller: Address, - transact_to: TxKind, - data: Bytes, - value: U256, - ) -> EnvWithHandlerCfg { - let env = Env { - cfg: self.env().cfg.clone(), - // We always set the gas price to 0 so we can execute the transaction regardless of - // network conditions - the actual gas price is kept in `self.block` and is applied by - // the cheatcode handler if it is enabled - block: BlockEnv { - basefee: U256::ZERO, - gas_limit: U256::from(self.gas_limit), - ..self.env().block.clone() + fn build_test_env(&self, caller: Address, kind: TxKind, data: Bytes, value: U256) -> Env { + Env { + evm_env: EvmEnv { + cfg_env: { + let mut cfg = self.env().evm_env.cfg_env.clone(); + cfg.spec = self.spec_id(); + cfg + }, + // We always set the gas price to 0 so we can execute the transaction regardless of + // network conditions - the actual gas price is kept in `self.block` and is applied + // by the cheatcode handler if it is enabled + block_env: BlockEnv { + basefee: 0, + gas_limit: self.gas_limit, + ..self.env().evm_env.block_env.clone() + }, }, tx: TxEnv { caller, - transact_to, + kind, data, value, // As above, we set the gas price to 0. - gas_price: U256::ZERO, + gas_price: 0, gas_priority_fee: None, gas_limit: self.gas_limit, + chain_id: Some(self.env().evm_env.cfg_env.chain_id), ..self.env().tx.clone() }, - }; - - EnvWithHandlerCfg::new_with_spec_id(Box::new(env), self.spec_id()) + } } pub fn call_sol_default(&self, to: Address, args: &C) -> C::Return @@ -797,7 +809,7 @@ pub struct RawCallResult { /// The changeset of the state. pub state_changeset: StateChangeset, /// The `revm::Env` after the call - pub env: EnvWithHandlerCfg, + pub env: Env, /// The cheatcode states after execution pub cheatcodes: Option, /// The raw output of the execution @@ -822,7 +834,7 @@ impl Default for RawCallResult { coverage: None, transactions: None, state_changeset: HashMap::default(), - env: EnvWithHandlerCfg::new_with_spec_id(Box::default(), SpecId::LATEST), + env: Env::default(), cheatcodes: Default::default(), out: None, chisel_state: None, @@ -878,7 +890,7 @@ impl RawCallResult { rd: Option<&RevertDecoder>, ) -> Result { self = self.into_result(rd)?; - let mut result = func.abi_decode_output(&self.result, false)?; + let mut result = func.abi_decode_output(&self.result)?; let decoded_result = if result.len() == 1 { result.pop().unwrap() } else { @@ -920,7 +932,7 @@ impl std::ops::DerefMut for CallResult { /// Converts the data aggregated in the `inspector` and `call` to a `RawCallResult` fn convert_executed_result( - env: EnvWithHandlerCfg, + env: Env, inspector: InspectorStack, ResultAndState { result, state: state_changeset }: ResultAndState, has_state_snapshot_failure: bool, @@ -938,10 +950,11 @@ fn convert_executed_result( } }; let gas = revm::interpreter::gas::calculate_initial_tx_gas( - env.spec_id(), + env.evm_env.cfg_env.spec, &env.tx.data, - env.tx.transact_to.is_create(), - &env.tx.access_list, + env.tx.kind.is_create(), + env.tx.access_list.len().try_into()?, + 0, 0, ); diff --git a/crates/evm/evm/src/executors/trace.rs b/crates/evm/evm/src/executors/trace.rs index 026612c88c484..c58ee8ddd6e59 100644 --- a/crates/evm/evm/src/executors/trace.rs +++ b/crates/evm/evm/src/executors/trace.rs @@ -1,10 +1,13 @@ -use crate::executors::{Executor, ExecutorBuilder}; +use crate::{ + executors::{Executor, ExecutorBuilder}, + Env, +}; use alloy_primitives::Address; use foundry_compilers::artifacts::EvmVersion; use foundry_config::{utils::evm_spec_id, Chain, Config}; use foundry_evm_core::{backend::Backend, fork::CreateFork, opts::EvmOpts}; use foundry_evm_traces::TraceMode; -use revm::primitives::{Env, SpecId}; +use revm::primitives::hardfork::SpecId; use std::ops::{Deref, DerefMut}; /// A default executor with tracing enabled @@ -14,7 +17,7 @@ pub struct TracingExecutor { impl TracingExecutor { pub fn new( - env: revm::primitives::Env, + env: Env, fork: Option, version: Option, trace_mode: TraceMode, diff --git a/crates/evm/evm/src/inspectors/chisel_state.rs b/crates/evm/evm/src/inspectors/chisel_state.rs index 023389ed4dfff..67ff8255a261e 100644 --- a/crates/evm/evm/src/inspectors/chisel_state.rs +++ b/crates/evm/evm/src/inspectors/chisel_state.rs @@ -1,7 +1,12 @@ use alloy_primitives::U256; +use foundry_evm_core::backend::DatabaseError; use revm::{ - interpreter::{InstructionResult, Interpreter}, - Database, EvmContext, Inspector, + context::ContextTr, + inspector::JournalExt, + interpreter::{ + interpreter::EthInterpreter, interpreter_types::Jumps, InstructionResult, Interpreter, + }, + Database, Inspector, }; /// An inspector for Chisel @@ -21,16 +26,21 @@ impl ChiselState { } } -impl Inspector for ChiselState { +impl Inspector for ChiselState +where + D: Database, + CTX: ContextTr, + CTX::Journal: JournalExt, +{ #[cold] - fn step_end(&mut self, interp: &mut Interpreter, _context: &mut EvmContext) { + fn step_end(&mut self, interp: &mut Interpreter, _context: &mut CTX) { // If we are at the final pc of the REPL contract execution, set the state. // Subtraction can't overflow because `pc` is always at least 1 in `step_end`. - if self.final_pc == interp.program_counter() - 1 { + if self.final_pc == interp.bytecode.pc() - 1 { self.state = Some(( interp.stack.data().clone(), - interp.shared_memory.context_memory().to_vec(), - interp.instruction_result, + interp.memory.context_memory().to_vec(), + interp.control.instruction_result, )) } } diff --git a/crates/evm/evm/src/inspectors/custom_printer.rs b/crates/evm/evm/src/inspectors/custom_printer.rs new file mode 100644 index 0000000000000..a35958ee550d0 --- /dev/null +++ b/crates/evm/evm/src/inspectors/custom_printer.rs @@ -0,0 +1,114 @@ +//! Custom print inspector, it has step level information of execution. +//! It is a great tool if some debugging is needed. + +use foundry_common::sh_println; +use foundry_evm_core::backend::DatabaseError; +use revm::{ + bytecode::opcode::OpCode, + context::{ContextTr, JournalTr}, + inspector::{inspectors::GasInspector, JournalExt}, + interpreter::{ + interpreter::EthInterpreter, + interpreter_types::{Jumps, LoopControl, MemoryTr}, + CallInputs, CallOutcome, CreateInputs, CreateOutcome, Interpreter, + }, + primitives::{Address, U256}, + Database, Inspector, +}; + +/// Custom print [Inspector], it has step level information of execution. +/// +/// It is a great tool if some debugging is needed. +#[derive(Clone, Debug, Default)] +pub struct CustomPrintTracer { + gas_inspector: GasInspector, +} + +impl Inspector for CustomPrintTracer +where + D: Database, + CTX: ContextTr, + CTX::Journal: JournalExt, +{ + fn initialize_interp(&mut self, interp: &mut Interpreter, _context: &mut CTX) { + self.gas_inspector.initialize_interp(&interp.control.gas); + } + + // get opcode by calling `interp.contract.opcode(interp.program_counter())`. + // all other information can be obtained from interp. + fn step(&mut self, interp: &mut Interpreter, context: &mut CTX) { + let opcode = interp.bytecode.opcode(); + let name = OpCode::name_by_op(opcode); + + let gas_remaining = self.gas_inspector.gas_remaining(); + + let memory_size = interp.memory.size(); + + let _ = sh_println!( + "depth:{}, PC:{}, gas:{:#x}({}), OPCODE: {:?}({:?}) refund:{:#x}({}) Stack:{:?}, Data size:{}", + context.journal().depth(), + interp.bytecode.pc(), + gas_remaining, + gas_remaining, + name, + opcode, + interp.control.gas.refunded(), + interp.control.gas.refunded(), + interp.stack.data(), + memory_size, + ); + + self.gas_inspector.step(&interp.control.gas); + } + + fn step_end(&mut self, interp: &mut Interpreter, _context: &mut CTX) { + self.gas_inspector.step_end(interp.control.gas_mut()); + } + + fn call_end(&mut self, _context: &mut CTX, _inputs: &CallInputs, outcome: &mut CallOutcome) { + self.gas_inspector.call_end(outcome) + } + + fn create_end( + &mut self, + _context: &mut CTX, + _inputs: &CreateInputs, + outcome: &mut CreateOutcome, + ) { + self.gas_inspector.create_end(outcome) + } + + fn call(&mut self, _context: &mut CTX, inputs: &mut CallInputs) -> Option { + let _ = sh_println!( + "SM Address: {:?}, caller:{:?},target:{:?} is_static:{:?}, transfer:{:?}, input_size:{:?}", + inputs.bytecode_address, + inputs.caller, + inputs.target_address, + inputs.is_static, + inputs.value, + inputs.input.len(), + ); + None + } + + fn create(&mut self, _context: &mut CTX, inputs: &mut CreateInputs) -> Option { + let _ = sh_println!( + "CREATE CALL: caller:{:?}, scheme:{:?}, value:{:?}, init_code:{:?}, gas:{:?}", + inputs.caller, + inputs.scheme, + inputs.value, + inputs.init_code, + inputs.gas_limit + ); + None + } + + fn selfdestruct(&mut self, contract: Address, target: Address, value: U256) { + let _ = sh_println!( + "SELFDESTRUCT: contract: {:?}, refund target: {:?}, value {:?}", + contract, + target, + value + ); + } +} diff --git a/crates/evm/evm/src/inspectors/logs.rs b/crates/evm/evm/src/inspectors/logs.rs index 570fb47dbe238..32af7bdbce6c8 100644 --- a/crates/evm/evm/src/inspectors/logs.rs +++ b/crates/evm/evm/src/inspectors/logs.rs @@ -1,12 +1,17 @@ use alloy_primitives::Log; use alloy_sol_types::{SolEvent, SolInterface, SolValue}; use foundry_common::{fmt::ConsoleFmt, ErrorExt}; -use foundry_evm_core::{abi::console, constants::HARDHAT_CONSOLE_ADDRESS, InspectorExt}; +use foundry_evm_core::{ + abi::console, backend::DatabaseError, constants::HARDHAT_CONSOLE_ADDRESS, InspectorExt, +}; use revm::{ + context::ContextTr, + inspector::JournalExt, interpreter::{ - CallInputs, CallOutcome, Gas, InstructionResult, Interpreter, InterpreterResult, + interpreter::EthInterpreter, CallInputs, CallOutcome, Gas, InstructionResult, Interpreter, + InterpreterResult, }, - Database, EvmContext, Inspector, + Database, Inspector, }; /// An inspector that collects logs during execution. @@ -20,8 +25,11 @@ pub struct LogCollector { impl LogCollector { #[cold] - fn do_hardhat_log(&mut self, inputs: &CallInputs) -> Option { - if let Err(err) = self.hardhat_log(&inputs.input) { + fn do_hardhat_log(&mut self, context: &mut CTX, inputs: &CallInputs) -> Option + where + CTX: ContextTr, Journal: JournalExt>, + { + if let Err(err) = self.hardhat_log(&inputs.input.bytes(context)) { let result = InstructionResult::Revert; let output = err.abi_encode_revert(); return Some(CallOutcome { @@ -33,24 +41,25 @@ impl LogCollector { } fn hardhat_log(&mut self, data: &[u8]) -> alloy_sol_types::Result<()> { - let decoded = console::hh::ConsoleCalls::abi_decode(data, false)?; + let decoded = console::hh::ConsoleCalls::abi_decode(data)?; self.logs.push(hh_to_ds(&decoded)); Ok(()) } } -impl Inspector for LogCollector { - fn log(&mut self, _interp: &mut Interpreter, _context: &mut EvmContext, log: &Log) { - self.logs.push(log.clone()); +impl Inspector for LogCollector +where + D: Database, + CTX: ContextTr, + CTX::Journal: JournalExt, +{ + fn log(&mut self, _interp: &mut Interpreter, _context: &mut CTX, log: Log) { + self.logs.push(log); } - fn call( - &mut self, - _context: &mut EvmContext, - inputs: &mut CallInputs, - ) -> Option { + fn call(&mut self, context: &mut CTX, inputs: &mut CallInputs) -> Option { if inputs.target_address == HARDHAT_CONSOLE_ADDRESS { - return self.do_hardhat_log(inputs); + return self.do_hardhat_log(context, inputs); } None } diff --git a/crates/evm/evm/src/inspectors/mod.rs b/crates/evm/evm/src/inspectors/mod.rs index 914bf460e5e44..85ed1354ea11a 100644 --- a/crates/evm/evm/src/inspectors/mod.rs +++ b/crates/evm/evm/src/inspectors/mod.rs @@ -7,6 +7,9 @@ pub use foundry_evm_traces::{StackSnapshotType, TracingInspector, TracingInspect pub use revm_inspectors::access_list::AccessListInspector; +mod custom_printer; +pub use custom_printer::CustomPrintTracer; + mod chisel_state; pub use chisel_state::ChiselState; @@ -18,3 +21,6 @@ pub use script::ScriptExecutionInspector; mod stack; pub use stack::{InspectorData, InspectorStack, InspectorStackBuilder}; + +mod revert_diagnostic; +pub use revert_diagnostic::RevertDiagnostic; diff --git a/crates/evm/evm/src/inspectors/revert_diagnostic.rs b/crates/evm/evm/src/inspectors/revert_diagnostic.rs new file mode 100644 index 0000000000000..c5da1b5b9c1b9 --- /dev/null +++ b/crates/evm/evm/src/inspectors/revert_diagnostic.rs @@ -0,0 +1,228 @@ +use alloy_primitives::{Address, U256}; +use alloy_sol_types::SolValue; +use foundry_evm_core::{ + backend::DatabaseError, + constants::{CHEATCODE_ADDRESS, HARDHAT_CONSOLE_ADDRESS}, +}; +use revm::{ + bytecode::opcode, + context::{ContextTr, JournalTr}, + inspector::JournalExt, + interpreter::{ + interpreter::EthInterpreter, interpreter_types::Jumps, CallInputs, CallOutcome, CallScheme, + InstructionResult, Interpreter, InterpreterAction, InterpreterResult, + }, + Database, Inspector, +}; +use std::fmt; + +const IGNORE: [Address; 2] = [HARDHAT_CONSOLE_ADDRESS, CHEATCODE_ADDRESS]; + +/// Checks if the call scheme corresponds to any sort of delegate call +pub fn is_delegatecall(scheme: CallScheme) -> bool { + matches!(scheme, CallScheme::DelegateCall | CallScheme::ExtDelegateCall | CallScheme::CallCode) +} + +#[derive(Debug, Clone, Copy)] +pub enum DetailedRevertReason { + CallToNonContract(Address), + DelegateCallToNonContract(Address), +} + +impl fmt::Display for DetailedRevertReason { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::CallToNonContract(addr) => { + write!(f, "call to non-contract address {addr}") + } + Self::DelegateCallToNonContract(addr) => write!( + f, + "delegatecall to non-contract address {addr} (usually an unliked library)" + ), + } + } +} + +/// An inspector that tracks call context to enhances revert diagnostics. +/// Useful for understanding reverts that are not linked to custom errors or revert strings. +/// +/// Supported diagnostics: +/// 1. **Non-void call to non-contract address:** the soldity compiler adds some validation to the +/// return data of the call, so despite the call succeeds, as doesn't return data, the +/// validation causes a revert. +/// +/// Identified when: a call with non-empty calldata is made to an address without bytecode, +/// followed by an empty revert at the same depth. +/// +/// 2. **Void call to non-contract address:** in this case the solidity compiler adds some checks +/// before doing the call, so it never takes place. +/// +/// Identified when: extcodesize for the target address returns 0 + empty revert at the same +/// depth. +#[derive(Clone, Debug, Default)] +pub struct RevertDiagnostic { + /// Tracks calls with calldata that target an address without executable code. + pub non_contract_call: Option<(Address, CallScheme, usize)>, + /// Tracks EXTCODESIZE checks that target an address without executable code. + pub non_contract_size_check: Option<(Address, usize)>, + /// Whether the step opcode is EXTCODESIZE or not. + pub is_extcodesize_step: bool, +} + +impl RevertDiagnostic { + /// Returns the effective target address whose code would be executed. + /// For delegate calls, this is the `bytecode_address`. Otherwise, it's the `target_address`. + fn code_target_address(&self, inputs: &mut CallInputs) -> Address { + if is_delegatecall(inputs.scheme) { + inputs.bytecode_address + } else { + inputs.target_address + } + } + + /// Derives the revert reason based on the cached data. Should only be called after a revert. + fn reason(&self) -> Option { + if let Some((addr, scheme, _)) = self.non_contract_call { + let reason = if is_delegatecall(scheme) { + DetailedRevertReason::DelegateCallToNonContract(addr) + } else { + DetailedRevertReason::CallToNonContract(addr) + }; + + return Some(reason); + } + + if let Some((addr, _)) = self.non_contract_size_check { + // unknown schema as the call never took place --> output most generic reason + return Some(DetailedRevertReason::CallToNonContract(addr)); + } + + None + } + + /// Injects the revert diagnostic into the debug traces. Should only be called after a revert. + fn broadcast_diagnostic(&self, interp: &mut Interpreter) { + if let Some(reason) = self.reason() { + interp.control.instruction_result = InstructionResult::Revert; + interp.control.next_action = InterpreterAction::Return { + result: InterpreterResult { + output: reason.to_string().abi_encode().into(), + gas: interp.control.gas, + result: InstructionResult::Revert, + }, + }; + } + } + + /// When a `REVERT` opcode with zero data size occurs: + /// - if `non_contract_call` was set at the current depth, `broadcast_diagnostic` is called. + /// Otherwise, it is cleared. + /// - if `non_contract_size_check` was set at the current depth, `broadcast_diagnostic` is + /// called. Otherwise, it is cleared. + #[cold] + fn handle_revert(&mut self, interp: &mut Interpreter, ctx: &mut CTX) + where + D: Database, + CTX: ContextTr, + CTX::Journal: JournalExt, + { + // REVERT (offset, size) + if let Ok(size) = interp.stack.peek(1) { + if size.is_zero() { + // Check empty revert with same depth as a non-contract call + if let Some((_, _, depth)) = self.non_contract_call { + if ctx.journal_ref().depth() == depth { + self.broadcast_diagnostic(interp); + } else { + self.non_contract_call = None; + } + return; + } + + // Check empty revert with same depth as a non-contract size check + if let Some((_, depth)) = self.non_contract_size_check { + if depth == ctx.journal_ref().depth() { + self.broadcast_diagnostic(interp); + } else { + self.non_contract_size_check = None; + } + } + } + } + } + + /// When an `EXTCODESIZE` opcode occurs: + /// - Optimistically caches the target address and current depth in `non_contract_size_check`, + /// pending later validation. + #[cold] + fn handle_extcodesize(&mut self, interp: &mut Interpreter, ctx: &mut CTX) + where + D: Database, + CTX: ContextTr, + CTX::Journal: JournalExt, + { + // EXTCODESIZE (address) + if let Ok(word) = interp.stack.peek(0) { + let addr = Address::from_word(word.into()); + if IGNORE.contains(&addr) || ctx.journal_ref().precompile_addresses().contains(&addr) { + return; + } + + // Optimistically cache --> validated and cleared (if necessary) at `fn + // step_end()` + self.non_contract_size_check = Some((addr, ctx.journal_ref().depth())); + self.is_extcodesize_step = true; + } + } + + /// Tracks `EXTCODESIZE` output. If the bytecode size is NOT 0, clears the cache. + #[cold] + fn handle_extcodesize_output(&mut self, interp: &mut Interpreter) { + if let Ok(size) = interp.stack.peek(0) { + if size != U256::ZERO { + self.non_contract_size_check = None; + } + } + + self.is_extcodesize_step = false; + } +} + +impl Inspector for RevertDiagnostic +where + D: Database, + CTX: ContextTr, + CTX::Journal: JournalExt, +{ + /// Tracks the first call with non-zero calldata that targets a non-contract address. Excludes + /// precompiles and test addresses. + fn call(&mut self, ctx: &mut CTX, inputs: &mut CallInputs) -> Option { + let target = self.code_target_address(inputs); + + if IGNORE.contains(&target) || ctx.journal_ref().precompile_addresses().contains(&target) { + return None; + } + + if let Ok(state) = ctx.journal().code(target) { + if state.is_empty() && !inputs.input.is_empty() { + self.non_contract_call = Some((target, inputs.scheme, ctx.journal_ref().depth())); + } + } + None + } + + /// Handles `REVERT` and `EXTCODESIZE` opcodes for diagnostics. + fn step(&mut self, interp: &mut Interpreter, ctx: &mut CTX) { + match interp.bytecode.opcode() { + opcode::REVERT => self.handle_revert(interp, ctx), + opcode::EXTCODESIZE => self.handle_extcodesize(interp, ctx), + _ => {} + } + } + + fn step_end(&mut self, interp: &mut Interpreter, _ctx: &mut CTX) { + if self.is_extcodesize_step { + self.handle_extcodesize_output(interp); + } + } +} diff --git a/crates/evm/evm/src/inspectors/script.rs b/crates/evm/evm/src/inspectors/script.rs index 57fa7b209b162..205215f08db08 100644 --- a/crates/evm/evm/src/inspectors/script.rs +++ b/crates/evm/evm/src/inspectors/script.rs @@ -1,8 +1,15 @@ +use alloy_evm::Database; use alloy_primitives::Address; use foundry_common::sh_err; +use foundry_evm_core::backend::DatabaseError; use revm::{ - interpreter::{opcode::ADDRESS, InstructionResult, Interpreter}, - Database, EvmContext, Inspector, + bytecode::opcode::ADDRESS, + context::ContextTr, + inspector::JournalExt, + interpreter::{ + interpreter::EthInterpreter, interpreter_types::Jumps, InstructionResult, Interpreter, + }, + Inspector, }; /// An inspector that enforces certain rules during script execution. @@ -14,21 +21,26 @@ pub struct ScriptExecutionInspector { pub script_address: Address, } -impl Inspector for ScriptExecutionInspector { +impl Inspector for ScriptExecutionInspector +where + D: Database, + CTX: ContextTr, + CTX::Journal: JournalExt, +{ #[inline] - fn step(&mut self, interpreter: &mut Interpreter, _ecx: &mut EvmContext) { + fn step(&mut self, interpreter: &mut Interpreter, _ecx: &mut CTX) { // Check if both target and bytecode address are the same as script contract address // (allow calling external libraries when bytecode address is different). - if interpreter.current_opcode() == ADDRESS && - interpreter.contract.target_address == self.script_address && - interpreter.contract.bytecode_address.unwrap_or_default() == self.script_address + if interpreter.bytecode.opcode() == ADDRESS && + interpreter.input.target_address == self.script_address && + interpreter.input.bytecode_address == Some(self.script_address) { // Log the reason for revert let _ = sh_err!( "Usage of `address(this)` detected in script contract. Script contracts are ephemeral and their addresses should not be relied upon." ); // Set the instruction result to Revert to stop execution - interpreter.instruction_result = InstructionResult::Revert; + interpreter.control.instruction_result = InstructionResult::Revert; } // Note: We don't return anything here as step returns void. // The original check returned InstructionResult::Continue, but that's the default diff --git a/crates/evm/evm/src/inspectors/stack.rs b/crates/evm/evm/src/inspectors/stack.rs index 773730d6cc136..f3c0fa3d13f4b 100644 --- a/crates/evm/evm/src/inspectors/stack.rs +++ b/crates/evm/evm/src/inspectors/stack.rs @@ -1,23 +1,32 @@ use super::{ - Cheatcodes, CheatsConfig, ChiselState, CoverageCollector, Fuzzer, LogCollector, - ScriptExecutionInspector, TracingInspector, + Cheatcodes, CheatsConfig, ChiselState, CoverageCollector, CustomPrintTracer, Fuzzer, + LogCollector, RevertDiagnostic, ScriptExecutionInspector, TracingInspector, +}; +use alloy_evm::{eth::EthEvmContext, Evm}; +use alloy_primitives::{ + map::{AddressHashMap, HashMap}, + Address, Bytes, Log, TxKind, U256, }; -use alloy_primitives::{map::AddressHashMap, Address, Bytes, Log, TxKind, U256}; use foundry_cheatcodes::{CheatcodesExecutor, Wallets}; -use foundry_evm_core::{backend::DatabaseExt, InspectorExt}; +use foundry_evm_core::{ + backend::{DatabaseExt, JournaledState}, + evm::new_evm_with_inspector, + ContextExt, Env, InspectorExt, +}; use foundry_evm_coverage::HitMaps; use foundry_evm_traces::{SparsedTraceArena, TraceMode}; use revm::{ - inspectors::CustomPrintTracer, - interpreter::{ - CallInputs, CallOutcome, CallScheme, CreateInputs, CreateOutcome, EOFCreateInputs, - EOFCreateKind, Gas, InstructionResult, Interpreter, InterpreterResult, + context::{ + result::{ExecutionResult, Output}, + BlockEnv, }, - primitives::{ - Account, AccountStatus, BlockEnv, CreateScheme, Env, EnvWithHandlerCfg, ExecutionResult, - HashMap, Output, TransactTo, + context_interface::CreateScheme, + interpreter::{ + CallInputs, CallOutcome, CallScheme, CreateInputs, CreateOutcome, Gas, InstructionResult, + Interpreter, InterpreterResult, }, - EvmContext, Inspector, JournaledState, + state::{Account, AccountStatus}, + Inspector, }; use std::{ ops::{Deref, DerefMut}, @@ -36,12 +45,12 @@ pub struct InspectorStackBuilder { /// /// Used in the cheatcode handler to overwrite the gas price separately from the gas price /// in the execution environment. - pub gas_price: Option, + pub gas_price: Option, /// The cheatcodes config. pub cheatcodes: Option>, /// The fuzzer inspector and its state, if it exists. pub fuzzer: Option, - /// Whether to enable tracing. + /// Whether to enable tracing and revert diagnostics. pub trace_mode: TraceMode, /// Whether logs should be collected. pub logs: Option, @@ -79,7 +88,7 @@ impl InspectorStackBuilder { /// Set the gas price. #[inline] - pub fn gas_price(mut self, gas_price: U256) -> Self { + pub fn gas_price(mut self, gas_price: u128) -> Self { self.gas_price = Some(gas_price); self } @@ -134,6 +143,7 @@ impl InspectorStackBuilder { } /// Set whether to enable the tracer. + /// Revert diagnostic inspector is activated when `mode != TraceMode::None` #[inline] pub fn trace_mode(mut self, mode: TraceMode) -> Self { if self.trace_mode < mode { @@ -232,7 +242,7 @@ macro_rules! call_inspectors { } )+ }; - (#[ret] [$($inspector:expr),+ $(,)?], |$id:ident $(,)?| $call:expr $(,)?) => { + (#[ret] [$($inspector:expr),+ $(,)?], |$id:ident $(,)?| $call:expr $(,)?) => {{ $( if let Some($id) = $inspector { if let Some(result) = ({ #[inline(always)] #[cold] || $call })() { @@ -240,7 +250,7 @@ macro_rules! call_inspectors { } } )+ - }; + }}; } /// The collected results of [`InspectorStack`]. @@ -295,6 +305,7 @@ pub struct InspectorStackInner { pub enable_isolation: bool, pub odyssey: bool, pub create2_deployer: Address, + pub revert_diag: Option, /// Flag marking if we are in the inner EVM context. pub in_inner_context: bool, @@ -355,7 +366,7 @@ impl InspectorStack { /// Set variables from an environment for the relevant inspectors. #[inline] pub fn set_env(&mut self, env: &Env) { - self.set_block(&env.block); + self.set_block(&env.evm_env.block_env); self.set_gas_price(env.tx.gas_price); } @@ -369,7 +380,7 @@ impl InspectorStack { /// Sets the gas price for the relevant inspectors. #[inline] - pub fn set_gas_price(&mut self, gas_price: U256) { + pub fn set_gas_price(&mut self, gas_price: u128) { if let Some(cheatcodes) = &mut self.cheatcodes { cheatcodes.gas_price = Some(gas_price); } @@ -430,8 +441,15 @@ impl InspectorStack { } /// Set whether to enable the tracer. + /// Revert diagnostic inspector is activated when `mode != TraceMode::None` #[inline] pub fn tracing(&mut self, mode: TraceMode) { + if mode.is_none() { + self.revert_diag = None; + } else { + self.revert_diag = Some(RevertDiagnostic::default()); + } + if let Some(config) = mode.into_config() { *self.tracer.get_or_insert_with(Default::default).config_mut() = config; } else { @@ -496,128 +514,110 @@ impl InspectorStackRefMut<'_> { /// Should be called on the top-level call of inner context (depth == 0 && /// self.in_inner_context) Decreases sender nonce for CALLs to keep backwards compatibility /// Updates tx.origin to the value before entering inner context - fn adjust_evm_data_for_inner_context(&mut self, ecx: &mut EvmContext<&mut dyn DatabaseExt>) { + fn adjust_evm_data_for_inner_context(&mut self, ecx: &mut EthEvmContext<&mut dyn DatabaseExt>) { let inner_context_data = self.inner_context_data.as_ref().expect("should be called in inner context"); - ecx.env.tx.caller = inner_context_data.original_origin; + ecx.tx.caller = inner_context_data.original_origin; } fn do_call_end( &mut self, - ecx: &mut EvmContext<&mut dyn DatabaseExt>, + ecx: &mut EthEvmContext<&mut dyn DatabaseExt>, inputs: &CallInputs, - outcome: CallOutcome, + outcome: &mut CallOutcome, ) -> CallOutcome { let result = outcome.result.result; call_inspectors!( #[ret] - [&mut self.fuzzer, &mut self.tracer, &mut self.cheatcodes, &mut self.printer], + [ + &mut self.fuzzer, + &mut self.tracer, + &mut self.cheatcodes, + &mut self.printer, + &mut self.revert_diag + ], |inspector| { - let new_outcome = inspector.call_end(ecx, inputs, outcome.clone()); + let previous_outcome = outcome.clone(); + inspector.call_end(ecx, inputs, outcome); // If the inspector returns a different status or a revert with a non-empty message, // we assume it wants to tell us something - let different = new_outcome.result.result != result || - (new_outcome.result.result == InstructionResult::Revert && - new_outcome.output() != outcome.output()); - different.then_some(new_outcome) + let different = outcome.result.result != result || + (outcome.result.result == InstructionResult::Revert && + outcome.output() != previous_outcome.output()); + different.then_some(outcome.clone()) }, ); - outcome + outcome.clone() } fn do_create_end( &mut self, - ecx: &mut EvmContext<&mut dyn DatabaseExt>, + ecx: &mut EthEvmContext<&mut dyn DatabaseExt>, call: &CreateInputs, - outcome: CreateOutcome, + outcome: &mut CreateOutcome, ) -> CreateOutcome { let result = outcome.result.result; call_inspectors!( #[ret] [&mut self.tracer, &mut self.cheatcodes, &mut self.printer], |inspector| { - let new_outcome = inspector.create_end(ecx, call, outcome.clone()); + let previous_outcome = outcome.clone(); + inspector.create_end(ecx, call, outcome); // If the inspector returns a different status or a revert with a non-empty message, // we assume it wants to tell us something - let different = new_outcome.result.result != result || - (new_outcome.result.result == InstructionResult::Revert && - new_outcome.output() != outcome.output()); - different.then_some(new_outcome) + let different = outcome.result.result != result || + (outcome.result.result == InstructionResult::Revert && + outcome.output() != previous_outcome.output()); + different.then_some(outcome.clone()) }, ); - outcome - } - - fn do_eofcreate_end( - &mut self, - ecx: &mut EvmContext<&mut dyn DatabaseExt>, - call: &EOFCreateInputs, - outcome: CreateOutcome, - ) -> CreateOutcome { - let result = outcome.result.result; - call_inspectors!( - #[ret] - [&mut self.tracer, &mut self.cheatcodes, &mut self.printer], - |inspector| { - let new_outcome = inspector.eofcreate_end(ecx, call, outcome.clone()); - - // If the inspector returns a different status or a revert with a non-empty message, - // we assume it wants to tell us something - let different = new_outcome.result.result != result || - (new_outcome.result.result == InstructionResult::Revert && - new_outcome.output() != outcome.output()); - different.then_some(new_outcome) - }, - ); - - outcome + outcome.clone() } fn transact_inner( &mut self, - ecx: &mut EvmContext<&mut dyn DatabaseExt>, - transact_to: TransactTo, + ecx: &mut EthEvmContext<&mut dyn DatabaseExt>, + kind: TxKind, caller: Address, input: Bytes, gas_limit: u64, value: U256, ) -> (InterpreterResult, Option
) { - let ecx = &mut ecx.inner; - - let cached_env = ecx.env.clone(); - - ecx.env.block.basefee = U256::ZERO; - ecx.env.tx.caller = caller; - ecx.env.tx.transact_to = transact_to; - ecx.env.tx.data = input; - ecx.env.tx.value = value; + let cached_env = Env::from(ecx.cfg.clone(), ecx.block.clone(), ecx.tx.clone()); + + ecx.block.basefee = 0; + ecx.tx.chain_id = Some(ecx.cfg.chain_id); + ecx.tx.caller = caller; + ecx.tx.kind = kind; + ecx.tx.data = input; + ecx.tx.value = value; // Add 21000 to the gas limit to account for the base cost of transaction. - ecx.env.tx.gas_limit = gas_limit + 21000; + ecx.tx.gas_limit = gas_limit + 21000; + // If we haven't disabled gas limit checks, ensure that transaction gas limit will not // exceed block gas limit. - if !ecx.env.cfg.disable_block_gas_limit { - ecx.env.tx.gas_limit = - std::cmp::min(ecx.env.tx.gas_limit, ecx.env.block.gas_limit.to()); + if !ecx.cfg.disable_block_gas_limit { + ecx.tx.gas_limit = std::cmp::min(ecx.tx.gas_limit, ecx.block.gas_limit); } - ecx.env.tx.gas_price = U256::ZERO; + ecx.tx.gas_price = 0; self.inner_context_data = Some(InnerContextData { original_origin: cached_env.tx.caller }); self.in_inner_context = true; - let env = EnvWithHandlerCfg::new_with_spec_id(ecx.env.clone(), ecx.spec_id()); let res = self.with_stack(|inspector| { - let mut evm = crate::utils::new_evm_with_inspector(&mut ecx.db, env, inspector); + let (db, journal, env) = ecx.as_db_env_and_journal(); + let mut evm = new_evm_with_inspector(db, env.to_owned(), inspector); - evm.context.evm.inner.journaled_state.state = { - let mut state = ecx.journaled_state.state.clone(); + evm.journaled_state.state = { + let mut state = journal.state.clone(); for (addr, acc_mut) in &mut state { // mark all accounts cold, besides preloaded addresses - if !ecx.journaled_state.warm_preloaded_addresses.contains(addr) { + if !journal.warm_preloaded_addresses.contains(addr) { acc_mut.mark_cold(); } @@ -632,21 +632,23 @@ impl InspectorStackRefMut<'_> { }; // set depth to 1 to make sure traces are collected correctly - evm.context.evm.inner.journaled_state.depth = 1; + evm.journaled_state.depth = 1; - let res = evm.transact(); + let res = evm.transact(env.tx.clone()); // need to reset the env in case it was modified via cheatcodes during execution - ecx.env = evm.context.evm.inner.env; + *env.cfg = evm.cfg.clone(); + *env.block = evm.block.clone(); + + *env.tx = cached_env.tx; + env.block.basefee = cached_env.evm_env.block_env.basefee; + res }); self.in_inner_context = false; self.inner_context_data = None; - ecx.env.tx = cached_env.tx; - ecx.env.block.basefee = cached_env.block.basefee; - let mut gas = Gas::new(gas_limit); let Ok(res) = res else { @@ -726,7 +728,7 @@ impl InspectorStackRefMut<'_> { } /// Invoked at the beginning of a new top-level (0 depth) frame. - fn top_level_frame_start(&mut self, ecx: &mut EvmContext<&mut dyn DatabaseExt>) { + fn top_level_frame_start(&mut self, ecx: &mut EthEvmContext<&mut dyn DatabaseExt>) { if self.enable_isolation { // If we're in isolation mode, we need to keep track of the state at the beginning of // the frame to be able to roll back on revert @@ -737,7 +739,7 @@ impl InspectorStackRefMut<'_> { /// Invoked at the end of root frame. fn top_level_frame_end( &mut self, - ecx: &mut EvmContext<&mut dyn DatabaseExt>, + ecx: &mut EthEvmContext<&mut dyn DatabaseExt>, result: InstructionResult, ) { if !result.is_revert() { @@ -759,11 +761,11 @@ impl InspectorStackRefMut<'_> { } } -impl Inspector<&mut dyn DatabaseExt> for InspectorStackRefMut<'_> { +impl Inspector> for InspectorStackRefMut<'_> { fn initialize_interp( &mut self, interpreter: &mut Interpreter, - ecx: &mut EvmContext<&mut dyn DatabaseExt>, + ecx: &mut EthEvmContext<&mut dyn DatabaseExt>, ) { call_inspectors!( [ @@ -777,7 +779,11 @@ impl Inspector<&mut dyn DatabaseExt> for InspectorStackRefMut<'_> { ); } - fn step(&mut self, interpreter: &mut Interpreter, ecx: &mut EvmContext<&mut dyn DatabaseExt>) { + fn step( + &mut self, + interpreter: &mut Interpreter, + ecx: &mut EthEvmContext<&mut dyn DatabaseExt>, + ) { call_inspectors!( [ &mut self.fuzzer, @@ -785,7 +791,8 @@ impl Inspector<&mut dyn DatabaseExt> for InspectorStackRefMut<'_> { &mut self.coverage, &mut self.cheatcodes, &mut self.script_execution_inspector, - &mut self.printer + &mut self.printer, + &mut self.revert_diag ], |inspector| inspector.step(interpreter, ecx), ); @@ -794,10 +801,16 @@ impl Inspector<&mut dyn DatabaseExt> for InspectorStackRefMut<'_> { fn step_end( &mut self, interpreter: &mut Interpreter, - ecx: &mut EvmContext<&mut dyn DatabaseExt>, + ecx: &mut EthEvmContext<&mut dyn DatabaseExt>, ) { call_inspectors!( - [&mut self.tracer, &mut self.cheatcodes, &mut self.chisel_state, &mut self.printer], + [ + &mut self.tracer, + &mut self.cheatcodes, + &mut self.chisel_state, + &mut self.printer, + &mut self.revert_diag + ], |inspector| inspector.step_end(interpreter, ecx), ); } @@ -805,18 +818,18 @@ impl Inspector<&mut dyn DatabaseExt> for InspectorStackRefMut<'_> { fn log( &mut self, interpreter: &mut Interpreter, - ecx: &mut EvmContext<&mut dyn DatabaseExt>, - log: &Log, + ecx: &mut EthEvmContext<&mut dyn DatabaseExt>, + log: Log, ) { call_inspectors!( [&mut self.tracer, &mut self.log_collector, &mut self.cheatcodes, &mut self.printer], - |inspector| inspector.log(interpreter, ecx, log), + |inspector| inspector.log(interpreter, ecx, log.clone()), ); } fn call( &mut self, - ecx: &mut EvmContext<&mut dyn DatabaseExt>, + ecx: &mut EthEvmContext<&mut dyn DatabaseExt>, call: &mut CallInputs, ) -> Option { if self.in_inner_context && ecx.journaled_state.depth == 1 { @@ -830,7 +843,13 @@ impl Inspector<&mut dyn DatabaseExt> for InspectorStackRefMut<'_> { call_inspectors!( #[ret] - [&mut self.fuzzer, &mut self.tracer, &mut self.log_collector, &mut self.printer], + [ + &mut self.fuzzer, + &mut self.tracer, + &mut self.log_collector, + &mut self.printer, + &mut self.revert_diag + ], |inspector| { let mut out = None; if let Some(output) = inspector.call(ecx, call) { @@ -847,10 +866,9 @@ impl Inspector<&mut dyn DatabaseExt> for InspectorStackRefMut<'_> { if let Some(mocks) = cheatcodes.mocked_functions.get(&call.target_address) { // Check if any mock function set for call data or if catch-all mock function set // for selector. - if let Some(target) = mocks - .get(&call.input) - .or_else(|| call.input.get(..4).and_then(|selector| mocks.get(selector))) - { + if let Some(target) = mocks.get(&call.input.bytes(ecx)).or_else(|| { + call.input.bytes(ecx).get(..4).and_then(|selector| mocks.get(selector)) + }) { call.bytecode_address = *target; } } @@ -866,11 +884,12 @@ impl Inspector<&mut dyn DatabaseExt> for InspectorStackRefMut<'_> { match call.scheme { // Isolate CALLs CallScheme::Call | CallScheme::ExtCall => { + let input = call.input.bytes(ecx); let (result, _) = self.transact_inner( ecx, TxKind::Call(call.target_address), call.caller, - call.input.clone(), + input, call.gas_limit, call.value.get(), ); @@ -882,7 +901,7 @@ impl Inspector<&mut dyn DatabaseExt> for InspectorStackRefMut<'_> { // Mark accounts and storage cold before STATICCALLs CallScheme::StaticCall | CallScheme::ExtStaticCall => { let JournaledState { state, warm_preloaded_addresses, .. } = - &mut ecx.journaled_state; + &mut ecx.journaled_state.inner; for (addr, acc_mut) in state { // Do not mark accounts and storage cold accounts with arbitrary storage. if let Some(cheatcodes) = &self.cheatcodes { @@ -910,28 +929,26 @@ impl Inspector<&mut dyn DatabaseExt> for InspectorStackRefMut<'_> { fn call_end( &mut self, - ecx: &mut EvmContext<&mut dyn DatabaseExt>, + ecx: &mut EthEvmContext<&mut dyn DatabaseExt>, inputs: &CallInputs, - outcome: CallOutcome, - ) -> CallOutcome { + outcome: &mut CallOutcome, + ) { // We are processing inner context outputs in the outer context, so need to avoid processing // twice. if self.in_inner_context && ecx.journaled_state.depth == 1 { - return outcome; + return; } - let outcome = self.do_call_end(ecx, inputs, outcome); + self.do_call_end(ecx, inputs, outcome); if ecx.journaled_state.depth == 0 { self.top_level_frame_end(ecx, outcome.result.result); } - - outcome } fn create( &mut self, - ecx: &mut EvmContext<&mut dyn DatabaseExt>, + ecx: &mut EthEvmContext<&mut dyn DatabaseExt>, create: &mut CreateInputs, ) -> Option { if self.in_inner_context && ecx.journaled_state.depth == 1 { @@ -970,102 +987,29 @@ impl Inspector<&mut dyn DatabaseExt> for InspectorStackRefMut<'_> { fn create_end( &mut self, - ecx: &mut EvmContext<&mut dyn DatabaseExt>, + ecx: &mut EthEvmContext<&mut dyn DatabaseExt>, call: &CreateInputs, - outcome: CreateOutcome, - ) -> CreateOutcome { - // We are processing inner context outputs in the outer context, so need to avoid processing - // twice. - if self.in_inner_context && ecx.journaled_state.depth == 1 { - return outcome; - } - - let outcome = self.do_create_end(ecx, call, outcome); - - if ecx.journaled_state.depth == 0 { - self.top_level_frame_end(ecx, outcome.result.result); - } - - outcome - } - - fn eofcreate( - &mut self, - ecx: &mut EvmContext<&mut dyn DatabaseExt>, - create: &mut EOFCreateInputs, - ) -> Option { - if self.in_inner_context && ecx.journaled_state.depth == 1 { - self.adjust_evm_data_for_inner_context(ecx); - return None; - } - - if ecx.journaled_state.depth == 0 { - self.top_level_frame_start(ecx); - } - - call_inspectors!( - #[ret] - [&mut self.tracer, &mut self.coverage, &mut self.cheatcodes], - |inspector| inspector.eofcreate(ecx, create).map(Some), - ); - - if matches!(create.kind, EOFCreateKind::Tx { .. }) && - self.enable_isolation && - !self.in_inner_context && - ecx.journaled_state.depth == 1 - { - let init_code = match &mut create.kind { - EOFCreateKind::Tx { initdata } => initdata.clone(), - EOFCreateKind::Opcode { .. } => unreachable!(), - }; - - let (result, address) = self.transact_inner( - ecx, - TxKind::Create, - create.caller, - init_code, - create.gas_limit, - create.value, - ); - return Some(CreateOutcome { result, address }); - } - - None - } - - fn eofcreate_end( - &mut self, - ecx: &mut EvmContext<&mut dyn DatabaseExt>, - call: &EOFCreateInputs, - outcome: CreateOutcome, - ) -> CreateOutcome { + outcome: &mut CreateOutcome, + ) { // We are processing inner context outputs in the outer context, so need to avoid processing // twice. if self.in_inner_context && ecx.journaled_state.depth == 1 { - return outcome; + return; } - let outcome = self.do_eofcreate_end(ecx, call, outcome); + self.do_create_end(ecx, call, outcome); if ecx.journaled_state.depth == 0 { self.top_level_frame_end(ecx, outcome.result.result); } - - outcome - } - - fn selfdestruct(&mut self, contract: Address, target: Address, value: U256) { - call_inspectors!([&mut self.tracer, &mut self.printer], |inspector| { - Inspector::<&mut dyn DatabaseExt>::selfdestruct(inspector, contract, target, value) - }); } } impl InspectorExt for InspectorStackRefMut<'_> { fn should_use_create2_factory( &mut self, - ecx: &mut EvmContext<&mut dyn DatabaseExt>, - inputs: &mut CreateInputs, + ecx: &mut EthEvmContext<&mut dyn DatabaseExt>, + inputs: &CreateInputs, ) -> bool { call_inspectors!( #[ret] @@ -1091,9 +1035,13 @@ impl InspectorExt for InspectorStackRefMut<'_> { } } -impl Inspector<&mut dyn DatabaseExt> for InspectorStack { +impl Inspector> for InspectorStack { #[inline] - fn step(&mut self, interpreter: &mut Interpreter, ecx: &mut EvmContext<&mut dyn DatabaseExt>) { + fn step( + &mut self, + interpreter: &mut Interpreter, + ecx: &mut EthEvmContext<&mut dyn DatabaseExt>, + ) { self.as_mut().step(interpreter, ecx) } @@ -1101,14 +1049,14 @@ impl Inspector<&mut dyn DatabaseExt> for InspectorStack { fn step_end( &mut self, interpreter: &mut Interpreter, - ecx: &mut EvmContext<&mut dyn DatabaseExt>, + ecx: &mut EthEvmContext<&mut dyn DatabaseExt>, ) { self.as_mut().step_end(interpreter, ecx) } fn call( &mut self, - context: &mut EvmContext<&mut dyn DatabaseExt>, + context: &mut EthEvmContext<&mut dyn DatabaseExt>, inputs: &mut CallInputs, ) -> Option { self.as_mut().call(context, inputs) @@ -1116,16 +1064,16 @@ impl Inspector<&mut dyn DatabaseExt> for InspectorStack { fn call_end( &mut self, - context: &mut EvmContext<&mut dyn DatabaseExt>, + context: &mut EthEvmContext<&mut dyn DatabaseExt>, inputs: &CallInputs, - outcome: CallOutcome, - ) -> CallOutcome { + outcome: &mut CallOutcome, + ) { self.as_mut().call_end(context, inputs, outcome) } fn create( &mut self, - context: &mut EvmContext<&mut dyn DatabaseExt>, + context: &mut EthEvmContext<&mut dyn DatabaseExt>, create: &mut CreateInputs, ) -> Option { self.as_mut().create(context, create) @@ -1133,34 +1081,17 @@ impl Inspector<&mut dyn DatabaseExt> for InspectorStack { fn create_end( &mut self, - context: &mut EvmContext<&mut dyn DatabaseExt>, + context: &mut EthEvmContext<&mut dyn DatabaseExt>, call: &CreateInputs, - outcome: CreateOutcome, - ) -> CreateOutcome { + outcome: &mut CreateOutcome, + ) { self.as_mut().create_end(context, call, outcome) } - fn eofcreate( - &mut self, - context: &mut EvmContext<&mut dyn DatabaseExt>, - create: &mut EOFCreateInputs, - ) -> Option { - self.as_mut().eofcreate(context, create) - } - - fn eofcreate_end( - &mut self, - context: &mut EvmContext<&mut dyn DatabaseExt>, - call: &EOFCreateInputs, - outcome: CreateOutcome, - ) -> CreateOutcome { - self.as_mut().eofcreate_end(context, call, outcome) - } - fn initialize_interp( &mut self, interpreter: &mut Interpreter, - ecx: &mut EvmContext<&mut dyn DatabaseExt>, + ecx: &mut EthEvmContext<&mut dyn DatabaseExt>, ) { self.as_mut().initialize_interp(interpreter, ecx) } @@ -1168,22 +1099,22 @@ impl Inspector<&mut dyn DatabaseExt> for InspectorStack { fn log( &mut self, interpreter: &mut Interpreter, - ecx: &mut EvmContext<&mut dyn DatabaseExt>, - log: &Log, + ecx: &mut EthEvmContext<&mut dyn DatabaseExt>, + log: Log, ) { self.as_mut().log(interpreter, ecx, log) } fn selfdestruct(&mut self, contract: Address, target: Address, value: U256) { - Inspector::<&mut dyn DatabaseExt>::selfdestruct(&mut self.as_mut(), contract, target, value) + self.as_mut().selfdestruct(contract, target, value); } } impl InspectorExt for InspectorStack { fn should_use_create2_factory( &mut self, - ecx: &mut EvmContext<&mut dyn DatabaseExt>, - inputs: &mut CreateInputs, + ecx: &mut EthEvmContext<&mut dyn DatabaseExt>, + inputs: &CreateInputs, ) -> bool { self.as_mut().should_use_create2_factory(ecx, inputs) } diff --git a/crates/evm/evm/src/lib.rs b/crates/evm/evm/src/lib.rs index 15858c0f393aa..92d39026aa677 100644 --- a/crates/evm/evm/src/lib.rs +++ b/crates/evm/evm/src/lib.rs @@ -11,7 +11,9 @@ extern crate tracing; pub mod executors; pub mod inspectors; -pub use foundry_evm_core::{backend, constants, decode, fork, opts, utils, InspectorExt}; +pub use foundry_evm_core::{ + backend, constants, decode, fork, opts, utils, Env, EnvMut, EvmEnv, InspectorExt, +}; pub use foundry_evm_coverage as coverage; pub use foundry_evm_fuzz as fuzz; pub use foundry_evm_traces as traces; diff --git a/crates/evm/fuzz/src/inspector.rs b/crates/evm/fuzz/src/inspector.rs index 052d87dac2dd1..279f0878382ac 100644 --- a/crates/evm/fuzz/src/inspector.rs +++ b/crates/evm/fuzz/src/inspector.rs @@ -1,7 +1,9 @@ use crate::{invariant::RandomCallGenerator, strategies::EvmFuzzState}; use revm::{ - interpreter::{CallInputs, CallOutcome, CallScheme, Interpreter}, - Database, EvmContext, Inspector, + context::{ContextTr, Transaction}, + inspector::JournalExt, + interpreter::{CallInput, CallInputs, CallOutcome, CallScheme, Interpreter}, + Inspector, }; /// An inspector that can fuzz and collect data for that effect. @@ -15,9 +17,12 @@ pub struct Fuzzer { pub fuzz_state: EvmFuzzState, } -impl Inspector for Fuzzer { +impl Inspector for Fuzzer +where + CTX: ContextTr, +{ #[inline] - fn step(&mut self, interp: &mut Interpreter, _context: &mut EvmContext) { + fn step(&mut self, interp: &mut Interpreter, _context: &mut CTX) { // We only collect `stack` and `memory` data before and after calls. if self.collect { self.collect_data(interp); @@ -26,9 +31,9 @@ impl Inspector for Fuzzer { } #[inline] - fn call(&mut self, ecx: &mut EvmContext, inputs: &mut CallInputs) -> Option { + fn call(&mut self, ecx: &mut CTX, inputs: &mut CallInputs) -> Option { // We don't want to override the very first call made to the test contract. - if self.call_generator.is_some() && ecx.env.tx.caller != inputs.caller { + if self.call_generator.is_some() && ecx.tx().caller() != inputs.caller { self.override_call(inputs); } @@ -40,12 +45,7 @@ impl Inspector for Fuzzer { } #[inline] - fn call_end( - &mut self, - _context: &mut EvmContext, - _inputs: &CallInputs, - outcome: CallOutcome, - ) -> CallOutcome { + fn call_end(&mut self, _context: &mut CTX, _inputs: &CallInputs, _outcome: &mut CallOutcome) { if let Some(ref mut call_generator) = self.call_generator { call_generator.used = false; } @@ -53,15 +53,13 @@ impl Inspector for Fuzzer { // We only collect `stack` and `memory` data before and after calls. // this will be turned off on the next `step` self.collect = true; - - outcome } } impl Fuzzer { /// Collects `stack` and `memory` values into the fuzz dictionary. fn collect_data(&mut self, interpreter: &Interpreter) { - self.fuzz_state.collect_values(interpreter.stack().data().iter().copied().map(Into::into)); + self.fuzz_state.collect_values(interpreter.stack.data().iter().copied().map(Into::into)); // TODO: disabled for now since it's flooding the dictionary // for index in 0..interpreter.shared_memory.len() / 32 { @@ -82,7 +80,7 @@ impl Fuzzer { { // There's only a 30% chance that an override happens. if let Some(tx) = call_generator.next(call.caller, call.target_address) { - *call.input = tx.call_details.calldata.0; + call.input = CallInput::Bytes(tx.call_details.calldata.0.into()); call.caller = tx.sender; call.target_address = tx.call_details.target; diff --git a/crates/evm/fuzz/src/lib.rs b/crates/evm/fuzz/src/lib.rs index 3486dac0b5172..bce67dfe320fa 100644 --- a/crates/evm/fuzz/src/lib.rs +++ b/crates/evm/fuzz/src/lib.rs @@ -79,7 +79,7 @@ impl BaseCounterExample { if let Some((name, abi)) = &contracts.get(&addr) { if let Some(func) = abi.functions().find(|f| f.selector() == bytes[..4]) { // skip the function selector when decoding - if let Ok(args) = func.abi_decode_input(&bytes[4..], false) { + if let Ok(args) = func.abi_decode_input(&bytes[4..]) { return Self { sender: Some(sender), addr: Some(addr), diff --git a/crates/evm/fuzz/src/strategies/int.rs b/crates/evm/fuzz/src/strategies/int.rs index 3732de0617325..c13f9414155b1 100644 --- a/crates/evm/fuzz/src/strategies/int.rs +++ b/crates/evm/fuzz/src/strategies/int.rs @@ -1,10 +1,10 @@ use alloy_dyn_abi::{DynSolType, DynSolValue}; use alloy_primitives::{Sign, I256, U256}; use proptest::{ + prelude::Rng, strategy::{NewTree, Strategy, ValueTree}, test_runner::TestRunner, }; -use rand::Rng; /// Value tree for signed ints (up to int256). pub struct IntValueTree { @@ -123,10 +123,10 @@ impl IntStrategy { fn generate_edge_tree(&self, runner: &mut TestRunner) -> NewTree { let rng = runner.rng(); - let offset = I256::from_raw(U256::from(rng.gen_range(0..4))); + let offset = I256::from_raw(U256::from(rng.random_range(0..4))); let umax: U256 = (U256::from(1) << (self.bits - 1)) - U256::from(1); // Choose if we want values around min, -0, +0, or max - let kind = rng.gen_range(0..4); + let kind = rng.random_range(0..4); let start = match kind { 0 => { I256::overflowing_from_sign_and_abs(Sign::Negative, umax + U256::from(1)).0 + offset @@ -146,7 +146,7 @@ impl IntStrategy { } // Generate value tree from fixture. - let fixture = &self.fixtures[runner.rng().gen_range(0..self.fixtures.len())]; + let fixture = &self.fixtures[runner.rng().random_range(0..self.fixtures.len())]; if let Some(int_fixture) = fixture.as_int() { if int_fixture.1 == self.bits { return Ok(IntValueTree::new(int_fixture.0, false)); @@ -162,15 +162,15 @@ impl IntStrategy { let rng = runner.rng(); // generate random number of bits uniformly - let bits = rng.gen_range(0..=self.bits); + let bits = rng.random_range(0..=self.bits); if bits == 0 { return Ok(IntValueTree::new(I256::ZERO, false)) } // init 2 128-bit randoms - let mut higher: u128 = rng.gen_range(0..=u128::MAX); - let mut lower: u128 = rng.gen_range(0..=u128::MAX); + let mut higher: u128 = rng.random_range(0..=u128::MAX); + let mut lower: u128 = rng.random_range(0..=u128::MAX); // cut 2 randoms according to bits size match bits - 1 { @@ -192,7 +192,7 @@ impl IntStrategy { // we have a small bias here, i.e. intN::min will never be generated // but it's ok since it's generated in `fn generate_edge_tree(...)` - let sign = if rng.gen_bool(0.5) { Sign::Positive } else { Sign::Negative }; + let sign = if rng.random::() { Sign::Positive } else { Sign::Negative }; let (start, _) = I256::overflowing_from_sign_and_abs(sign, U256::from_limbs(inner)); Ok(IntValueTree::new(start, false)) @@ -205,7 +205,7 @@ impl Strategy for IntStrategy { fn new_tree(&self, runner: &mut TestRunner) -> NewTree { let total_weight = self.random_weight + self.fixtures_weight + self.edge_weight; - let bias = runner.rng().gen_range(0..total_weight); + let bias = runner.rng().random_range(0..total_weight); // randomly select one of 3 strategies match bias { x if x < self.edge_weight => self.generate_edge_tree(runner), diff --git a/crates/evm/fuzz/src/strategies/invariants.rs b/crates/evm/fuzz/src/strategies/invariants.rs index 37c0b157c3cc2..3222529099d28 100644 --- a/crates/evm/fuzz/src/strategies/invariants.rs +++ b/crates/evm/fuzz/src/strategies/invariants.rs @@ -34,7 +34,7 @@ pub fn override_call_strat( // Choose a random contract if target selected by lazy strategy is not in fuzz run // identified contracts. This can happen when contract is created in `setUp` call // but is not included in targetContracts. - contracts.values().choose(&mut rand::thread_rng()).unwrap() + contracts.values().choose(&mut rand::rng()).unwrap() }); let fuzzed_functions: Vec<_> = contract.abi_fuzzed_functions().cloned().collect(); any::().prop_map(move |index| index.get(&fuzzed_functions).clone()) diff --git a/crates/evm/fuzz/src/strategies/param.rs b/crates/evm/fuzz/src/strategies/param.rs index 43dcdae7b00f3..07732332d90e0 100644 --- a/crates/evm/fuzz/src/strategies/param.rs +++ b/crates/evm/fuzz/src/strategies/param.rs @@ -135,9 +135,7 @@ pub fn fuzz_param_from_state( value() .prop_map(move |value| { let mut fuzzed_addr = Address::from_word(value); - if !deployed_libs.contains(&fuzzed_addr) { - DynSolValue::Address(fuzzed_addr) - } else { + if deployed_libs.contains(&fuzzed_addr) { let mut rng = StdRng::seed_from_u64(0x1337); // use deterministic rng // Do not use addresses of deployed libraries as fuzz input, instead return @@ -151,9 +149,8 @@ pub fn fuzz_param_from_state( break; } } - - DynSolValue::Address(fuzzed_addr) } + DynSolValue::Address(fuzzed_addr) }) .boxed() } @@ -235,7 +232,7 @@ mod tests { }; use foundry_common::abi::get_func; use foundry_config::FuzzDictionaryConfig; - use revm::db::{CacheDB, EmptyDB}; + use revm::database::{CacheDB, EmptyDB}; #[test] fn can_fuzz_array() { diff --git a/crates/evm/fuzz/src/strategies/state.rs b/crates/evm/fuzz/src/strategies/state.rs index c598ada0accab..80c0c029865d2 100644 --- a/crates/evm/fuzz/src/strategies/state.rs +++ b/crates/evm/fuzz/src/strategies/state.rs @@ -10,9 +10,9 @@ use foundry_config::FuzzDictionaryConfig; use foundry_evm_core::utils::StateChangeset; use parking_lot::{lock_api::RwLockReadGuard, RawRwLock, RwLock}; use revm::{ - db::{CacheDB, DatabaseRef, DbAccount}, - interpreter::opcode, - primitives::AccountInfo, + bytecode::opcode, + database::{CacheDB, DatabaseRef, DbAccount}, + state::AccountInfo, }; use std::{collections::BTreeMap, fmt, sync::Arc}; @@ -39,7 +39,7 @@ impl EvmFuzzState { deployed_libs: &[Address], ) -> Self { // Sort accounts to ensure deterministic dictionary generation from the same setUp state. - let mut accs = db.accounts.iter().collect::>(); + let mut accs = db.cache.accounts.iter().collect::>(); accs.sort_by_key(|(address, _)| *address); // Create fuzz dictionary and insert values from db state. @@ -179,7 +179,7 @@ impl FuzzDictionary { if let Some(function) = function { if !function.outputs.is_empty() { // Decode result and collect samples to be used in subsequent fuzz runs. - if let Ok(decoded_result) = function.abi_decode_output(result, false) { + if let Ok(decoded_result) = function.abi_decode_output(result) { self.insert_sample_values(decoded_result, run_depth); } } @@ -195,7 +195,7 @@ impl FuzzDictionary { // Try to decode log with events from contract abi. if let Some(abi) = abi { for event in abi.events() { - if let Ok(decoded_event) = event.decode_log(log, false) { + if let Ok(decoded_event) = event.decode_log(log) { samples.extend(decoded_event.indexed); samples.extend(decoded_event.body); log_decoded = true; diff --git a/crates/evm/fuzz/src/strategies/uint.rs b/crates/evm/fuzz/src/strategies/uint.rs index af133efa00826..3d63ef92d36b4 100644 --- a/crates/evm/fuzz/src/strategies/uint.rs +++ b/crates/evm/fuzz/src/strategies/uint.rs @@ -1,10 +1,10 @@ use alloy_dyn_abi::{DynSolType, DynSolValue}; use alloy_primitives::U256; use proptest::{ + prelude::Rng, strategy::{NewTree, Strategy, ValueTree}, test_runner::TestRunner, }; -use rand::Rng; /// Value tree for unsigned ints (up to uint256). pub struct UintValueTree { @@ -111,8 +111,8 @@ impl UintStrategy { fn generate_edge_tree(&self, runner: &mut TestRunner) -> NewTree { let rng = runner.rng(); // Choose if we want values around 0 or max - let is_min = rng.gen_bool(0.5); - let offset = U256::from(rng.gen_range(0..4)); + let is_min = rng.random::(); + let offset = U256::from(rng.random_range(0..4)); let start = if is_min { offset } else { self.type_max().saturating_sub(offset) }; Ok(UintValueTree::new(start, false)) } @@ -124,7 +124,7 @@ impl UintStrategy { } // Generate value tree from fixture. - let fixture = &self.fixtures[runner.rng().gen_range(0..self.fixtures.len())]; + let fixture = &self.fixtures[runner.rng().random_range(0..self.fixtures.len())]; if let Some(uint_fixture) = fixture.as_uint() { if uint_fixture.1 == self.bits { return Ok(UintValueTree::new(uint_fixture.0, false)); @@ -140,11 +140,11 @@ impl UintStrategy { let rng = runner.rng(); // generate random number of bits uniformly - let bits = rng.gen_range(0..=self.bits); + let bits = rng.random_range(0..=self.bits); // init 2 128-bit randoms - let mut higher: u128 = rng.gen_range(0..=u128::MAX); - let mut lower: u128 = rng.gen_range(0..=u128::MAX); + let mut higher: u128 = rng.random_range(0..=u128::MAX); + let mut lower: u128 = rng.random_range(0..=u128::MAX); // cut 2 randoms according to bits size match bits { @@ -182,7 +182,7 @@ impl Strategy for UintStrategy { type Value = U256; fn new_tree(&self, runner: &mut TestRunner) -> NewTree { let total_weight = self.random_weight + self.fixtures_weight + self.edge_weight; - let bias = runner.rng().gen_range(0..total_weight); + let bias = runner.rng().random_range(0..total_weight); // randomly select one of 3 strategies match bias { x if x < self.edge_weight => self.generate_edge_tree(runner), diff --git a/crates/evm/traces/src/debug/mod.rs b/crates/evm/traces/src/debug/mod.rs index 0e6521a7f5a8c..0e07124597aa3 100644 --- a/crates/evm/traces/src/debug/mod.rs +++ b/crates/evm/traces/src/debug/mod.rs @@ -7,7 +7,7 @@ use alloy_dyn_abi::{ use alloy_primitives::U256; use foundry_common::fmt::format_token; use foundry_compilers::artifacts::sourcemap::{Jump, SourceElement}; -use revm::interpreter::OpCode; +use revm::bytecode::opcode::OpCode; use revm_inspectors::tracing::types::{CallTraceStep, DecodedInternalCall, DecodedTraceStep}; pub use sources::{ArtifactData, ContractSources, SourceData}; diff --git a/crates/evm/traces/src/debug/sources.rs b/crates/evm/traces/src/debug/sources.rs index 5c8d15ef19e65..cfd7056e5a8b6 100644 --- a/crates/evm/traces/src/debug/sources.rs +++ b/crates/evm/traces/src/debug/sources.rs @@ -8,7 +8,7 @@ use foundry_compilers::{ multi::MultiCompilerLanguage, Artifact, Compiler, ProjectCompileOutput, }; -use foundry_evm_core::utils::PcIcMap; +use foundry_evm_core::ic::PcIcMap; use foundry_linking::Linker; use rayon::prelude::*; use solar_parse::{interface::Session, Parser}; diff --git a/crates/evm/traces/src/decoder/mod.rs b/crates/evm/traces/src/decoder/mod.rs index 2e22ca7cdc358..44b9c9cd729b6 100644 --- a/crates/evm/traces/src/decoder/mod.rs +++ b/crates/evm/traces/src/decoder/mod.rs @@ -123,6 +123,9 @@ pub struct CallTraceDecoder { /// Contract addresses that have fallback functions, mapped to function selectors of that /// contract. pub fallback_contracts: HashMap>, + /// Contract addresses that have do NOT have fallback functions, mapped to function selectors + /// of that contract. + pub non_fallback_contracts: HashMap>, /// All known functions. pub functions: HashMap>, @@ -176,6 +179,7 @@ impl CallTraceDecoder { ]), receive_contracts: Default::default(), fallback_contracts: Default::default(), + non_fallback_contracts: Default::default(), functions: console::hh::abi::functions() .into_values() @@ -307,6 +311,9 @@ impl CallTraceDecoder { if abi.fallback.is_some() { self.fallback_contracts .insert(address, abi.functions().map(|f| f.selector()).collect()); + } else { + self.non_fallback_contracts + .insert(address, abi.functions().map(|f| f.selector()).collect()); } } } @@ -364,6 +371,45 @@ impl CallTraceDecoder { &functions } }; + + // Check if unsupported fn selector: calldata dooes NOT point to one of its selectors + + // non-fallback contract + no receive + if let Some(contract_selectors) = self.non_fallback_contracts.get(&trace.address) { + if !contract_selectors.contains(&selector) && + (!cdata.is_empty() || !self.receive_contracts.contains(&trace.address)) + { + let return_data = if !trace.success { + let revert_msg = + self.revert_decoder.decode(&trace.output, Some(trace.status)); + + if trace.output.is_empty() || revert_msg.contains("EvmError: Revert") { + Some(format!( + "unrecognized function selector {} for contract {}, which has no fallback function.", + selector, trace.address + )) + } else { + Some(revert_msg) + } + } else { + None + }; + + if let Some(func) = functions.first() { + return DecodedCallTrace { + label, + call_data: Some(self.decode_function_input(trace, func)), + return_data, + }; + } else { + return DecodedCallTrace { + label, + call_data: self.fallback_call_data(trace), + return_data, + }; + }; + } + } + let [func, ..] = &functions[..] else { return DecodedCallTrace { label, @@ -409,7 +455,7 @@ impl CallTraceDecoder { } if args.is_none() { - if let Ok(v) = func.abi_decode_input(&trace.data[SELECTOR_LEN..], false) { + if let Ok(v) = func.abi_decode_input(&trace.data[SELECTOR_LEN..]) { args = Some(v.iter().map(|value| self.format_value(value)).collect()); } } @@ -445,7 +491,7 @@ impl CallTraceDecoder { } } "sign" | "signP256" => { - let mut decoded = func.abi_decode_input(&data[SELECTOR_LEN..], false).ok()?; + let mut decoded = func.abi_decode_input(&data[SELECTOR_LEN..]).ok()?; // Redact private key and replace in trace // sign(uint256,bytes32) / signP256(uint256,bytes32) / sign(Wallet,bytes32) @@ -458,7 +504,7 @@ impl CallTraceDecoder { Some(decoded.iter().map(format_token).collect()) } "signDelegation" | "signAndAttachDelegation" => { - let mut decoded = func.abi_decode_input(&data[SELECTOR_LEN..], false).ok()?; + let mut decoded = func.abi_decode_input(&data[SELECTOR_LEN..]).ok()?; // Redact private key and replace in trace for // signAndAttachDelegation(address implementation, uint256 privateKey) // signDelegation(address implementation, uint256 privateKey) @@ -482,7 +528,7 @@ impl CallTraceDecoder { "parseJsonBytes32Array" | "writeJson" | // `keyExists` is being deprecated in favor of `keyExistsJson`. It will be removed in future versions. - "keyExists" | + "keyExists" | "keyExistsJson" | "serializeBool" | "serializeUint" | @@ -495,10 +541,10 @@ impl CallTraceDecoder { if self.verbosity >= 5 { None } else { - let mut decoded = func.abi_decode_input(&data[SELECTOR_LEN..], false).ok()?; + let mut decoded = func.abi_decode_input(&data[SELECTOR_LEN..]).ok()?; let token = if func.name.as_str() == "parseJson" || // `keyExists` is being deprecated in favor of `keyExistsJson`. It will be removed in future versions. - func.name.as_str() == "keyExists" || + func.name.as_str() == "keyExists" || func.name.as_str() == "keyExistsJson" { "" @@ -513,7 +559,7 @@ impl CallTraceDecoder { if self.verbosity >= 5 { None } else { - let mut decoded = func.abi_decode_input(&data[SELECTOR_LEN..], false).ok()?; + let mut decoded = func.abi_decode_input(&data[SELECTOR_LEN..]).ok()?; let token = if func.name.as_str() == "parseToml" || func.name.as_str() == "keyExistsToml" { @@ -528,7 +574,7 @@ impl CallTraceDecoder { "createFork" | "createSelectFork" | "rpc" => { - let mut decoded = func.abi_decode_input(&data[SELECTOR_LEN..], false).ok()?; + let mut decoded = func.abi_decode_input(&data[SELECTOR_LEN..]).ok()?; // Redact RPC URL except if referenced by an alias if !decoded.is_empty() && func.inputs[0].ty == "string" { @@ -561,7 +607,7 @@ impl CallTraceDecoder { } if let Some(values) = - funcs.iter().find_map(|func| func.abi_decode_output(&trace.output, false).ok()) + funcs.iter().find_map(|func| func.abi_decode_output(&trace.output).ok()) { // Functions coming from an external database do not have any outputs specified, // and will lead to returning an empty list of values. @@ -628,7 +674,7 @@ impl CallTraceDecoder { } }; for event in events { - if let Ok(decoded) = event.decode_log(log, false) { + if let Ok(decoded) = event.decode_log(log) { let params = reconstruct_params(event, &decoded); return DecodedCallLog { name: Some(event.name.clone()), diff --git a/crates/evm/traces/src/decoder/precompiles.rs b/crates/evm/traces/src/decoder/precompiles.rs index 508bf1e1c4320..245c70e10ec57 100644 --- a/crates/evm/traces/src/decoder/precompiles.rs +++ b/crates/evm/traces/src/decoder/precompiles.rs @@ -106,13 +106,13 @@ pub(super) fn decode(trace: &CallTrace, _chain_id: u64) -> Option alloy_sol_types::Result> { - let mut decoder = abi::Decoder::new(data, false); + let mut decoder = abi::Decoder::new(data); let b_size = decoder.take_offset()?; let e_size = decoder.take_offset()?; let m_size = decoder.take_offset()?; - let b = decoder.take_slice_unchecked(b_size)?; - let e = decoder.take_slice_unchecked(e_size)?; - let m = decoder.take_slice_unchecked(m_size)?; + let b = decoder.take_slice(b_size)?; + let e = decoder.take_slice(e_size)?; + let m = decoder.take_slice(m_size)?; Ok(vec![ b_size.to_string(), e_size.to_string(), @@ -124,7 +124,7 @@ fn decode_modexp(data: &[u8]) -> alloy_sol_types::Result> { } fn decode_ecpairing(data: &[u8]) -> alloy_sol_types::Result> { - let mut decoder = abi::Decoder::new(data, false); + let mut decoder = abi::Decoder::new(data); let mut values = Vec::new(); // input must be either empty or a multiple of 6 32-byte values let mut tmp = <[&B256; 6]>::default(); @@ -138,14 +138,14 @@ fn decode_ecpairing(data: &[u8]) -> alloy_sol_types::Result> { } fn decode_blake2f<'a>(data: &'a [u8]) -> alloy_sol_types::Result> { - let mut decoder = abi::Decoder::new(data, false); - let rounds = u32::from_be_bytes(decoder.take_slice_unchecked(4)?.try_into().unwrap()); + let mut decoder = abi::Decoder::new(data); + let rounds = u32::from_be_bytes(decoder.take_slice(4)?.try_into().unwrap()); let u64_le_list = |x: &'a [u8]| x.chunks_exact(8).map(|x| u64::from_le_bytes(x.try_into().unwrap())); - let h = u64_le_list(decoder.take_slice_unchecked(64)?); - let m = u64_le_list(decoder.take_slice_unchecked(128)?); - let t = u64_le_list(decoder.take_slice_unchecked(16)?); - let f = decoder.take_slice_unchecked(1)?[0]; + let h = u64_le_list(decoder.take_slice(64)?); + let m = u64_le_list(decoder.take_slice(128)?); + let t = u64_le_list(decoder.take_slice(16)?); + let f = decoder.take_slice(1)?[0]; Ok(vec![ rounds.to_string(), iter_to_string(h), @@ -156,12 +156,12 @@ fn decode_blake2f<'a>(data: &'a [u8]) -> alloy_sol_types::Result> { } fn decode_kzg(data: &[u8]) -> alloy_sol_types::Result> { - let mut decoder = abi::Decoder::new(data, false); + let mut decoder = abi::Decoder::new(data); let versioned_hash = decoder.take_word()?; let z = decoder.take_word()?; let y = decoder.take_word()?; - let commitment = decoder.take_slice_unchecked(48)?; - let proof = decoder.take_slice_unchecked(48)?; + let commitment = decoder.take_slice(48)?; + let proof = decoder.take_slice(48)?; Ok(vec![ versioned_hash.to_string(), z.to_string(), @@ -173,7 +173,7 @@ fn decode_kzg(data: &[u8]) -> alloy_sol_types::Result> { fn abi_decode_call(data: &[u8]) -> alloy_sol_types::Result<(&'static str, T)> { // raw because there are no selectors here - Ok((T::SIGNATURE, T::abi_decode_raw(data, false)?)) + Ok((T::SIGNATURE, T::abi_decode_raw(data)?)) } fn iter_to_string, T: std::fmt::Display>(iter: I) -> String { diff --git a/crates/evm/traces/src/identifier/local.rs b/crates/evm/traces/src/identifier/local.rs index dcd4c31bcaf9f..f0bb8cd9d7d76 100644 --- a/crates/evm/traces/src/identifier/local.rs +++ b/crates/evm/traces/src/identifier/local.rs @@ -59,7 +59,7 @@ impl<'a> LocalTraceIdentifier<'a> { // Try to decode ctor args with contract abi. if let Some(constructor) = contract.abi.constructor() { let constructor_args = ¤t_bytecode[bytecode.len()..]; - if constructor.abi_decode_input(constructor_args, false).is_ok() { + if constructor.abi_decode_input(constructor_args).is_ok() { // If we can decode args with current abi then remove args from // code to compare. current_bytecode = ¤t_bytecode[..bytecode.len()] diff --git a/crates/evm/traces/src/lib.rs b/crates/evm/traces/src/lib.rs index 2107644793463..ab024a2c1f530 100644 --- a/crates/evm/traces/src/lib.rs +++ b/crates/evm/traces/src/lib.rs @@ -15,7 +15,7 @@ use foundry_common::{ contracts::{ContractsByAddress, ContractsByArtifact}, shell, }; -use revm::interpreter::OpCode; +use revm::bytecode::opcode::OpCode; use revm_inspectors::tracing::{ types::{DecodedTraceStep, TraceMemberOrder}, OpcodeFilter, diff --git a/crates/fmt-2/src/pp/convenience.rs b/crates/fmt-2/src/pp/convenience.rs index 8cd7cc1ce5b59..7ef8a9acc4cb2 100644 --- a/crates/fmt-2/src/pp/convenience.rs +++ b/crates/fmt-2/src/pp/convenience.rs @@ -54,6 +54,14 @@ impl Printer { self.spaces(SIZE_INFINITY as usize); } + pub fn last_token_is_hardbreak(&self) -> bool { + if let Some(token) = self.last_token() { + return token.is_hardbreak(); + } + + false + } + pub fn is_beginning_of_line(&self) -> bool { match self.last_token() { Some(last_token) => last_token.is_hardbreak(), @@ -61,6 +69,38 @@ impl Printer { } } + pub fn is_bol_or_only_ind(&self) -> bool { + for i in self.buf.index_range().rev() { + let token = &self.buf[i].token; + if token.is_hardbreak() || matches!(token, Token::Begin(_)) { + return true; + } + if Self::token_has_non_whitespace_content(token) { + return false; + } + } + + let last_line = + if let Some(pos) = self.out.rfind('\n') { &self.out[pos + 1..] } else { &self.out[..] }; + + last_line.trim().is_empty() + } + + fn token_has_non_whitespace_content(token: &Token) -> bool { + match token { + Token::String(s) => !s.trim().is_empty(), + Token::Break(bt) => { + if let Some(char) = bt.pre_break { + !char.is_whitespace() + } else { + false + } + } + Token::Begin(_) => false, + Token::End => false, + } + } + pub(crate) fn hardbreak_tok_offset(offset: isize) -> Token { Token::Break(BreakToken { offset, diff --git a/crates/fmt-2/src/state.rs b/crates/fmt-2/src/state.rs index 6d6479044c65f..cb0a69d5ef354 100644 --- a/crates/fmt-2/src/state.rs +++ b/crates/fmt-2/src/state.rs @@ -17,6 +17,18 @@ use solar_parse::{ }; use std::borrow::Cow; +/// Formatting style for comma-separated lists +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum ListFormat { + /// Breaks all elements if any break. + Consistent, + /// Attempts to fit all elements in one line, before breaking consistently. + Compact, + /// If the list contains just one element, it will print unboxed (will not break). + /// Otherwise, will break consistently. + Inline, +} + // TODO(dani): trailing comments should always be passed Some pub(super) struct State<'sess, 'ast> { @@ -78,6 +90,28 @@ impl<'sess> State<'sess, '_> { self.print_comments_inner(pos, true) } + /// Print comments inline without adding line breaks. + /// + /// Only works for trailing and mixed [`CommentStyle`]. + fn print_inline_comments(&mut self, pos: BytePos) -> bool { + let mut printed = false; + while let Some(cmnt) = self.peek_comment() { + if cmnt.pos() >= pos { + break; + } + let cmnt = self.next_comment().unwrap(); + printed = true; + + if matches!(cmnt.style, CommentStyle::Mixed | CommentStyle::Trailing) { + for line in cmnt.lines { + self.word(line); + } + self.space(); + } + } + printed + } + fn print_comments_inner(&mut self, pos: BytePos, skip_ws: bool) -> Option { let mut has_comment = None; while let Some(cmnt) = self.peek_comment() { @@ -129,7 +163,7 @@ impl<'sess> State<'sess, '_> { } } CommentStyle::Trailing => { - if !self.is_beginning_of_line() { + if !self.is_bol_or_only_ind() { self.nbsp(); } if cmnt.lines.len() == 1 { @@ -182,7 +216,7 @@ impl<'sess> State<'sess, '_> { fn print_remaining_comments(&mut self) { // If there aren't any remaining comments, then we need to manually // make sure there is a line break at the end. - if self.peek_comment().is_none() && !self.is_beginning_of_line() { + if self.peek_comment().is_none() && !self.is_bol_or_only_ind() { self.hardbreak(); } while let Some(cmnt) = self.next_comment() { @@ -213,13 +247,30 @@ impl<'sess> State<'sess, '_> { } } - fn print_tuple<'a, T, P, S>(&mut self, values: &'a [T], print: P, get_span: S, compact: bool) - where + fn print_tuple<'a, T, P, S>( + &mut self, + values: &'a [T], + mut print: P, + mut get_span: S, + format: ListFormat, + ) where P: FnMut(&mut Self, &'a T), S: FnMut(&T) -> Option, { + // Format single-item inline lists directly without boxes + if values.len() == 1 && matches!(format, ListFormat::Inline) { + if let Some(span) = get_span(&values[0]) { + self.print_comments(span.lo()); + } + self.word("("); + print(self, &values[0]); + self.word(")"); + return; + } + + // Otherwise, use commasep self.word("("); - self.commasep(values, print, get_span, compact); + self.commasep(values, print, get_span, matches!(format, ListFormat::Compact)); self.word(")"); } @@ -247,13 +298,10 @@ impl<'sess> State<'sess, '_> { return; } + self.s.cbox(self.ind); + self.zerobreak(); if compact { - self.s.cbox(self.ind); - self.zerobreak(); self.s.cbox(0); - } else { - self.s.cbox(self.ind); - self.zerobreak(); } for (i, value) in values.iter().enumerate() { let span = get_span(value); @@ -269,16 +317,19 @@ impl<'sess> State<'sess, '_> { let next_pos = if is_last { None } else { get_span(&values[i + 1]).map(Span::lo) }; self.print_trailing_comment(span, next_pos); } - if !self.is_beginning_of_line() { - if !is_last { - self.space(); - } + if !is_last && !self.is_beginning_of_line() { + self.space(); } } + if compact { - self.end(); + if !self.last_token_is_hardbreak() { + self.end(); + self.zerobreak(); + } + } else { + self.zerobreak(); } - self.zerobreak(); self.s.offset(-self.ind); self.end(); } @@ -576,6 +627,7 @@ impl<'ast> State<'_, 'ast> { virtual_, ref override_, ref returns, + .. } = *header; self.cbox(0); @@ -585,7 +637,7 @@ impl<'ast> State<'_, 'ast> { self.nbsp(); self.print_ident(&name); } - self.print_parameter_list(parameters, false); + self.print_parameter_list(parameters, ListFormat::Consistent); self.end(); // Attributes. @@ -613,7 +665,7 @@ impl<'ast> State<'_, 'ast> { if !returns.is_empty() { self.space(); self.word("returns "); - self.print_parameter_list(returns, false); + self.print_parameter_list(returns, ListFormat::Consistent); } if let Some(body) = body { @@ -656,7 +708,7 @@ impl<'ast> State<'_, 'ast> { let ast::ItemError { name, parameters } = err; self.word("error "); self.print_ident(name); - self.print_parameter_list(parameters, false); + self.print_parameter_list(parameters, ListFormat::Consistent); self.word(";"); } @@ -664,7 +716,7 @@ impl<'ast> State<'_, 'ast> { let ast::ItemEvent { name, parameters, anonymous } = event; self.word("event "); self.print_ident(name); - self.print_parameter_list(parameters, false); + self.print_parameter_list(parameters, ListFormat::Consistent); if *anonymous { self.word(" anonymous"); } @@ -728,9 +780,9 @@ impl<'ast> State<'_, 'ast> { fn print_parameter_list( &mut self, parameters: &'ast [ast::VariableDefinition<'ast>], - compact: bool, + format: ListFormat, ) { - self.print_tuple(parameters, Self::print_var, get_span!(), compact); + self.print_tuple(parameters, Self::print_var, get_span!(), format); } fn print_docs(&mut self, docs: &'ast ast::DocComments<'ast>) { @@ -1017,7 +1069,12 @@ impl<'ast> State<'_, 'ast> { if self.config.override_spacing { self.nbsp(); } - self.print_tuple(paths, |this, path| this.print_path(path), get_span!(()), false); + self.print_tuple( + paths, + |this, path| this.print_path(path), + get_span!(()), + ListFormat::Consistent, + ); } } @@ -1131,11 +1188,16 @@ impl<'ast> State<'_, 'ast> { } }, |e| e.as_deref().map(|e| e.span), - false, + ListFormat::Consistent, ), ast::ExprKind::TypeCall(ty) => { self.word("type"); - self.print_tuple(std::slice::from_ref(ty), Self::print_ty, get_span!(), false); + self.print_tuple( + std::slice::from_ref(ty), + Self::print_ty, + get_span!(), + ListFormat::Consistent, + ); } ast::ExprKind::Type(ty) => self.print_ty(ty), ast::ExprKind::Unary(un_op, expr) => { @@ -1174,7 +1236,12 @@ impl<'ast> State<'_, 'ast> { match kind { ast::CallArgsKind::Unnamed(exprs) => { - self.print_tuple(exprs, |this, e| this.print_expr(e), get_span!(), false); + self.print_tuple( + exprs, + |this, e| this.print_expr(e), + get_span!(), + ListFormat::Consistent, + ); } ast::CallArgsKind::Named(named_args) => { self.word("("); @@ -1220,7 +1287,12 @@ impl<'ast> State<'_, 'ast> { self.nbsp(); } if !flags.is_empty() { - self.print_tuple(flags, Self::print_ast_str_lit, get_span!(), false); + self.print_tuple( + flags, + Self::print_ast_str_lit, + get_span!(), + ListFormat::Consistent, + ); } self.print_yul_block(block, span, false); } @@ -1234,7 +1306,7 @@ impl<'ast> State<'_, 'ast> { } }, |v| v.as_ref().map(|v| v.span), - false, + ListFormat::Consistent, ); self.word(" = "); self.neverbreak(); @@ -1315,40 +1387,45 @@ impl<'ast> State<'_, 'ast> { self.cbox(0); if let Some((first, other)) = clauses.split_first() { // Handle 'try' clause - let ast::TryCatchClause { args, block, .. } = first; + let ast::TryCatchClause { args, block, span: try_span, .. } = first; self.ibox(0); self.word("try "); + self.print_inline_comments(expr.span.lo()); self.print_expr(expr); - self.print_trailing_comment(expr.span, first.args.first().map(|p| p.span.lo())); + self.print_comments_skip_ws( + args.first().map(|p| p.span.lo()).unwrap_or_else(|| expr.span.lo()), + ); if !self.is_beginning_of_line() { self.nbsp(); } if !args.is_empty() { self.word("returns "); - self.print_parameter_list(args, true); + self.print_parameter_list(args, ListFormat::Compact); self.nbsp(); } - self.print_block(block, span); + self.print_block(block, *try_span); + self.print_trailing_comment(*try_span, other.first().map(|c| c.span.lo())); self.end(); // Handle 'catch' clauses let mut should_break = false; - for (pos, ast::TryCatchClause { name, args, block }) in other.iter().delimited() + for (pos, ast::TryCatchClause { name, args, block, span: catch_span }) in + other.iter().delimited() { self.handle_try_catch_indent(&mut should_break, block.is_empty(), pos); + self.ibox(0); + self.print_inline_comments(catch_span.lo()); self.word("catch "); if !args.is_empty() { + self.print_inline_comments(args[0].span.lo()); if let Some(name) = name { - self.cbox(0); self.print_ident(name); } - self.print_parameter_list(args, true); + self.print_parameter_list(args, ListFormat::Inline); self.nbsp(); - if name.is_some() { - self.end(); - } } - self.print_block(block, span); + self.print_block(block, *catch_span); + self.end(); } } self.end(); @@ -1382,7 +1459,12 @@ impl<'ast> State<'_, 'ast> { fn print_if_cond(&mut self, kw: &'static str, cond: &'ast ast::Expr<'ast>) { self.word_nbsp(kw); - self.print_tuple(std::slice::from_ref(cond), Self::print_expr, get_span!(), false); + self.print_tuple( + std::slice::from_ref(cond), + Self::print_expr, + get_span!(), + ListFormat::Consistent, + ); } fn print_emit_revert( @@ -1462,6 +1544,25 @@ impl<'ast> State<'_, 'ast> { self.word("}"); } self.end(); + } + // Special handling for empty blocks, as they could have comments. + else if block.is_empty() { + if let Some(comment) = self.peek_comment() { + if !matches!(comment.style, CommentStyle::Mixed) { + self.word("{}"); + self.print_comments_skip_ws(span.hi()); + } else { + self.s.cbox(self.ind); + self.word("{"); + self.space(); + self.print_comments_skip_ws(span.hi()); + self.zerobreak(); + self.word("}"); + self.end(); + } + } else { + self.word("{}"); + } } else { self.word("{"); self.s.cbox(self.ind); @@ -1564,7 +1665,12 @@ impl<'ast> State<'_, 'ast> { self.ibox(0); self.word("function "); self.print_ident(name); - self.print_tuple(parameters, Self::print_ident, get_span!(), false); + self.print_tuple( + parameters, + Self::print_ident, + get_span!(), + ListFormat::Consistent, + ); self.nbsp(); if !returns.is_empty() { self.word("-> "); @@ -1610,7 +1716,7 @@ impl<'ast> State<'_, 'ast> { fn print_yul_expr_call(&mut self, expr: &'ast yul::ExprCall<'ast>) { let yul::ExprCall { name, arguments } = expr; self.print_ident(name); - self.print_tuple(arguments, Self::print_yul_expr, get_span!(), false); + self.print_tuple(arguments, Self::print_yul_expr, get_span!(), ListFormat::Consistent); } fn handle_try_catch_indent( @@ -1621,19 +1727,27 @@ impl<'ast> State<'_, 'ast> { ) { // Add extra indent if all prev 'catch' stmts are empty if *should_break { - self.nbsp(); - } else { - if empty_block { + if self.is_bol_or_only_ind() { + self.zerobreak(); + } else { + self.nbsp(); + } + } else if empty_block { + if self.is_bol_or_only_ind() { + self.zerobreak(); + } else { self.space(); - self.s.offset(self.ind); + } + self.s.offset(self.ind); + } else { + if pos.is_first { + self.nbsp(); + } else if self.is_bol_or_only_ind() { + self.zerobreak(); } else { - if pos.is_first { - self.nbsp(); - } else { - self.space(); - } - *should_break = true; + self.space(); } + *should_break = true; } } } diff --git a/crates/fmt/src/buffer.rs b/crates/fmt/src/buffer.rs index 031cccd6169f8..18db4fea0459b 100644 --- a/crates/fmt/src/buffer.rs +++ b/crates/fmt/src/buffer.rs @@ -181,7 +181,7 @@ impl FormatBuffer { .take(self.base_indent_len) .take_while(|(_, _, ch)| ch.is_whitespace()) .last() - .map(|(state, idx, _)| (state, idx + 1)) + .map(|(state, idx, ch)| (state, idx + ch.len_utf8())) .unwrap_or((comment_state, 0)); comment_state = new_comment_state; let trimmed_line = &line[line_start..]; diff --git a/crates/fmt/src/formatter.rs b/crates/fmt/src/formatter.rs index 98a2adcef86c5..7b14bd8c6cd43 100644 --- a/crates/fmt/src/formatter.rs +++ b/crates/fmt/src/formatter.rs @@ -132,7 +132,7 @@ impl<'a, W: Write> Formatter<'a, W> { /// Casts the current writer `w` as a `String` reference. Should only be used for debugging. unsafe fn buf_contents(&self) -> &String { - *(&self.buf.w as *const W as *const &mut String) + *(&raw const self.buf.w as *const &mut String) } /// Casts the current `W` writer or the current temp buffer as a `String` reference. @@ -2005,6 +2005,12 @@ impl Visitor for Formatter<'_, W> { ); } + if let Some(layout) = &mut contract.layout { + write_chunk!(fmt, "layout at ")?; + fmt.visit_expr(layout.loc(), layout)?; + write_chunk!(fmt, " ")?; + } + write_chunk!(fmt, "{{")?; fmt.indented(1, |fmt| { @@ -2083,28 +2089,37 @@ impl Visitor for Formatter<'_, W> { Ok(()) } - #[instrument(name = "pragma", skip_all)] fn visit_pragma( &mut self, - loc: Loc, - ident: &mut Option, - string: &mut Option, - ) -> Result<()> { - let (ident, string) = (ident.safe_unwrap(), string.safe_unwrap()); + pragma: &mut PragmaDirective, + ) -> std::result::Result<(), Self::Error> { + let loc = pragma.loc(); return_source_if_disabled!(self, loc, ';'); - let pragma_descriptor = if ident.name == "solidity" { - // There are some issues with parsing Solidity's versions with crates like `semver`: - // 1. Ranges like `>=0.4.21<0.6.0` or `>=0.4.21 <0.6.0` are not parseable at all. - // 2. Versions like `0.8.10` got transformed into `^0.8.10` which is not the same. - // TODO: semver-solidity crate :D - &string.string - } else { - &string.string - }; - - write_chunk!(self, string.loc.end(), "pragma {} {};", &ident.name, pragma_descriptor)?; - + match pragma { + PragmaDirective::Identifier(loc, id1, id2) => { + write_chunk!( + self, + loc.start(), + loc.end(), + "pragma {}{}{};", + id1.as_ref().map(|id| id.name.to_string()).unwrap_or_default(), + if id1.is_some() && id2.is_some() { " " } else { "" }, + id2.as_ref().map(|id| id.name.to_string()).unwrap_or_default(), + )?; + } + PragmaDirective::StringLiteral(_loc, id, lit) => { + write_chunk!(self, "pragma {} ", id.name)?; + let StringLiteral { loc, string, .. } = lit; + write_chunk!(self, loc.start(), loc.end(), "\"{string}\";")?; + } + PragmaDirective::Version(loc, id, version) => { + write_chunk!(self, loc.start(), id.loc().end(), "pragma {}", id.name)?; + let version_loc = loc.with_start(version[0].loc().start()); + self.visit_source(version_loc)?; + self.write_semicolon()?; + } + } Ok(()) } @@ -3306,6 +3321,7 @@ impl Visitor for Formatter<'_, W> { VariableAttribute::Visibility(visibility) => Some(visibility.to_string()), VariableAttribute::Constant(_) => Some("constant".to_string()), VariableAttribute::Immutable(_) => Some("immutable".to_string()), + VariableAttribute::StorageType(_) => None, // Unsupported VariableAttribute::Override(loc, idents) => { write_chunk!(self, loc.start(), "override")?; if !idents.is_empty() && self.config.override_spacing { @@ -3314,6 +3330,7 @@ impl Visitor for Formatter<'_, W> { self.visit_list("", idents, Some(loc.start()), Some(loc.end()), false)?; None } + VariableAttribute::StorageLocation(storage) => Some(storage.to_string()), }; if let Some(token) = token { let loc = attribute.loc(); diff --git a/crates/fmt/src/solang_ext/ast_eq.rs b/crates/fmt/src/solang_ext/ast_eq.rs index 3c796a0d00128..1ba1748d84644 100644 --- a/crates/fmt/src/solang_ext/ast_eq.rs +++ b/crates/fmt/src/solang_ext/ast_eq.rs @@ -387,6 +387,8 @@ derive_ast_eq! { (0 A, 1 B) } derive_ast_eq! { (0 A, 1 B, 2 C) } derive_ast_eq! { (0 A, 1 B, 2 C, 3 D) } derive_ast_eq! { (0 A, 1 B, 2 C, 3 D, 4 E) } +derive_ast_eq! { (0 A, 1 B, 2 C, 3 D, 4 E, 5 F) } +derive_ast_eq! { (0 A, 1 B, 2 C, 3 D, 4 E, 5 F, 6 G) } derive_ast_eq! { bool } derive_ast_eq! { u8 } derive_ast_eq! { u16 } @@ -413,7 +415,7 @@ derive_ast_eq! { struct VariableDeclaration { loc, ty, storage, name } } derive_ast_eq! { struct Using { loc, list, ty, global } } derive_ast_eq! { struct UsingFunction { loc, path, oper } } derive_ast_eq! { struct TypeDefinition { loc, name, ty } } -derive_ast_eq! { struct ContractDefinition { loc, ty, name, base, parts } } +derive_ast_eq! { struct ContractDefinition { loc, ty, name, base, layout, parts } } derive_ast_eq! { struct EventParameter { loc, ty, indexed, name } } derive_ast_eq! { struct ErrorParameter { loc, ty, name } } derive_ast_eq! { struct EventDefinition { loc, name, fields, anonymous } } @@ -421,6 +423,13 @@ derive_ast_eq! { struct ErrorDefinition { loc, keyword, name, fields } } derive_ast_eq! { struct StructDefinition { loc, name, fields } } derive_ast_eq! { struct EnumDefinition { loc, name, values } } derive_ast_eq! { struct Annotation { loc, id, value } } +derive_ast_eq! { enum PragmaDirective { + _ + Identifier(loc, id1, id2), + StringLiteral(loc, id, lit), + Version(loc, id, version), + _ +}} derive_ast_eq! { enum UsingList { Error, _ @@ -480,6 +489,7 @@ derive_ast_eq! { enum StorageLocation { Memory(loc), Storage(loc), Calldata(loc), + Transient(loc), _ }} derive_ast_eq! { enum Type { @@ -615,7 +625,7 @@ derive_ast_eq! { enum YulSwitchOptions { derive_ast_eq! { enum SourceUnitPart { _ ContractDefinition(def), - PragmaDirective(loc, ident, string), + PragmaDirective(pragma), ImportDirective(import), EnumDefinition(def), StructDefinition(def), @@ -679,5 +689,20 @@ derive_ast_eq! { enum VariableAttribute { Constant(loc), Immutable(loc), Override(loc, idents), + StorageType(st), + StorageLocation(st), _ }} + +// Who cares +impl AstEq for StorageType { + fn ast_eq(&self, _other: &Self) -> bool { + true + } +} + +impl AstEq for VersionComparator { + fn ast_eq(&self, _other: &Self) -> bool { + true + } +} diff --git a/crates/fmt/src/solang_ext/loc.rs b/crates/fmt/src/solang_ext/loc.rs index 54bf771c6df90..6261e08c637ee 100644 --- a/crates/fmt/src/solang_ext/loc.rs +++ b/crates/fmt/src/solang_ext/loc.rs @@ -90,6 +90,17 @@ impl CodeLocationExt for pt::ImportPath { } } +impl CodeLocationExt for pt::VersionComparator { + fn loc(&self) -> pt::Loc { + match self { + Self::Plain { loc, .. } | + Self::Operator { loc, .. } | + Self::Or { loc, .. } | + Self::Range { loc, .. } => *loc, + } + } +} + macro_rules! impl_delegate { ($($t:ty),+ $(,)?) => {$( impl CodeLocationExt for $t { @@ -111,6 +122,7 @@ impl_delegate! { pt::ErrorParameter, pt::EventDefinition, pt::EventParameter, + pt::PragmaDirective, // pt::FunctionDefinition, pt::HexLiteral, pt::Identifier, diff --git a/crates/fmt/src/solang_ext/mod.rs b/crates/fmt/src/solang_ext/mod.rs index aa4fe734ee64f..3aa7c526c5ba1 100644 --- a/crates/fmt/src/solang_ext/mod.rs +++ b/crates/fmt/src/solang_ext/mod.rs @@ -11,11 +11,12 @@ pub mod pt { EnumDefinition, ErrorDefinition, ErrorParameter, EventDefinition, EventParameter, Expression, FunctionAttribute, FunctionDefinition, FunctionTy, HexLiteral, Identifier, IdentifierPath, Import, ImportPath, Loc, Mutability, NamedArgument, OptionalCodeLocation, - Parameter, ParameterList, SourceUnit, SourceUnitPart, Statement, StorageLocation, - StringLiteral, StructDefinition, Type, TypeDefinition, UserDefinedOperator, Using, - UsingFunction, UsingList, VariableAttribute, VariableDeclaration, VariableDefinition, - Visibility, YulBlock, YulExpression, YulFor, YulFunctionCall, YulFunctionDefinition, - YulStatement, YulSwitch, YulSwitchOptions, YulTypedIdentifier, + Parameter, ParameterList, PragmaDirective, SourceUnit, SourceUnitPart, Statement, + StorageLocation, StringLiteral, StructDefinition, Type, TypeDefinition, + UserDefinedOperator, Using, UsingFunction, UsingList, VariableAttribute, + VariableDeclaration, VariableDefinition, Visibility, YulBlock, YulExpression, YulFor, + YulFunctionCall, YulFunctionDefinition, YulStatement, YulSwitch, YulSwitchOptions, + YulTypedIdentifier, }; } diff --git a/crates/fmt/src/visit.rs b/crates/fmt/src/visit.rs index db6287b72209d..b0d93ce0e84c6 100644 --- a/crates/fmt/src/visit.rs +++ b/crates/fmt/src/visit.rs @@ -1,6 +1,6 @@ //! Visitor helpers to traverse the [solang Solidity Parse Tree](solang_parser::pt). -use crate::solang_ext::pt::*; +use crate::solang_ext::{pt::*, CodeLocationExt}; /// A trait that is invoked while traversing the Solidity Parse Tree. /// Each method of the [Visitor] trait is a hook that can be potentially overridden. @@ -25,13 +25,8 @@ pub trait Visitor { self.visit_source(annotation.loc) } - fn visit_pragma( - &mut self, - loc: Loc, - _ident: &mut Option, - _str: &mut Option, - ) -> Result<(), Self::Error> { - self.visit_source(loc) + fn visit_pragma(&mut self, pragma: &mut PragmaDirective) -> Result<(), Self::Error> { + self.visit_source(pragma.loc()) } fn visit_import_plain( @@ -331,7 +326,7 @@ pub trait Visitor { _expr: &mut Option<&mut YulExpression>, ) -> Result<(), Self::Error> where - T: Visitable + CodeLocation, + T: Visitable + CodeLocationExt, { self.visit_source(loc) } @@ -459,7 +454,7 @@ impl Visitable for SourceUnitPart { { match self { Self::ContractDefinition(contract) => v.visit_contract(contract), - Self::PragmaDirective(loc, ident, str) => v.visit_pragma(*loc, ident, str), + Self::PragmaDirective(pragma) => v.visit_pragma(pragma), Self::ImportDirective(import) => import.visit(v), Self::EnumDefinition(enumeration) => v.visit_enum(enumeration), Self::StructDefinition(structure) => v.visit_struct(structure), diff --git a/crates/fmt/testdata/NonKeywords/fmt.sol b/crates/fmt/testdata/NonKeywords/fmt.sol new file mode 100644 index 0000000000000..207eed1c96816 --- /dev/null +++ b/crates/fmt/testdata/NonKeywords/fmt.sol @@ -0,0 +1,43 @@ +struct S { + uint256 error; + uint256 layout; + uint256 at; +} +// uint256 transient; + +function f() { + uint256 error = 0; + uint256 layout = 0; + uint256 at = 0; + // uint256 transient = 0; + + error = 0; + // layout = 0; + at = 0; + // transient = 0; + + S memory x = S({ + // format + error: 0, + layout: 0, + at: 0 + }); + // transient: 0 + + x.error = 0; + x.layout = 0; + x.at = 0; + // x.transient = 0; + + assembly { + let error := 0 + let layout := 0 + let at := 0 + // let transient := 0 + + error := 0 + layout := 0 + at := 0 + // transient := 0 + } +} diff --git a/crates/fmt/testdata/NonKeywords/original.sol b/crates/fmt/testdata/NonKeywords/original.sol new file mode 100644 index 0000000000000..2786450ba571e --- /dev/null +++ b/crates/fmt/testdata/NonKeywords/original.sol @@ -0,0 +1,43 @@ +struct S { + uint256 error; + uint256 layout; + uint256 at; + // uint256 transient; +} + +function f() { + uint256 error = 0; + uint256 layout = 0; + uint256 at = 0; + // uint256 transient = 0; + + error = 0; + // layout = 0; + at = 0; + // transient = 0; + + S memory x = S({ + // format + error: 0, + layout: 0, + at: 0 + // transient: 0 + }); + + x.error = 0; + x.layout = 0; + x.at = 0; + // x.transient = 0; + + assembly { + let error := 0 + let layout := 0 + let at := 0 + // let transient := 0 + + error := 0 + layout := 0 + at := 0 + // transient := 0 + } +} diff --git a/crates/fmt/testdata/TryStatement/original.sol b/crates/fmt/testdata/TryStatement/original.sol index 9fc158b20195a..8edd0117eee3f 100644 --- a/crates/fmt/testdata/TryStatement/original.sol +++ b/crates/fmt/testdata/TryStatement/original.sol @@ -39,10 +39,10 @@ contract TryStatement { } try unknown.lookupMultipleValues() returns (uint256, uint256, uint256, uint256, uint256) {} catch Error(string memory) {} catch {} - + try unknown.lookupMultipleValues() returns (uint256, uint256, uint256, uint256, uint256) { unknown.doSomething(); - } + } catch Error(string memory) { unknown.handleError(); } @@ -55,7 +55,7 @@ contract TryStatement { catch /* comment6 */ {} // comment7 - try unknown.empty() { // comment8 + try unknown.empty() { // comment8 unknown.doSomething(); } /* comment9 */ catch /* comment10 */ Error(string memory) { unknown.handleError(); @@ -63,4 +63,4 @@ contract TryStatement { unknown.handleError(); } catch {} } -} \ No newline at end of file +} diff --git a/crates/fmt/testdata/VariableDefinition/fmt.sol b/crates/fmt/testdata/VariableDefinition/fmt.sol index 9ff53c8d55237..85a88e5326de8 100644 --- a/crates/fmt/testdata/VariableDefinition/fmt.sol +++ b/crates/fmt/testdata/VariableDefinition/fmt.sol @@ -1,5 +1,7 @@ // config: line_length = 40 -contract Contract { +contract Contract layout at 69 { + bytes32 transient a; + bytes32 private constant BYTES; bytes32 private diff --git a/crates/fmt/testdata/VariableDefinition/override-spacing.fmt.sol b/crates/fmt/testdata/VariableDefinition/override-spacing.fmt.sol index 5fde30038126e..41ef397f65156 100644 --- a/crates/fmt/testdata/VariableDefinition/override-spacing.fmt.sol +++ b/crates/fmt/testdata/VariableDefinition/override-spacing.fmt.sol @@ -1,6 +1,8 @@ // config: line_length = 40 // config: override_spacing = true -contract Contract { +contract Contract layout at 69 { + bytes32 transient a; + bytes32 private constant BYTES; bytes32 private diff --git a/crates/fmt/tests/formatter.rs b/crates/fmt/tests/formatter.rs index 4aac7e05256bd..2e0a8dc896296 100644 --- a/crates/fmt/tests/formatter.rs +++ b/crates/fmt/tests/formatter.rs @@ -107,8 +107,14 @@ fn test_formatter( assert_eof(expected_source); - let source_parsed = parse(source).unwrap(); - let expected_parsed = parse(expected_source).unwrap(); + let source_parsed = match parse(source) { + Ok(p) => p, + Err(e) => panic!("{e}"), + }; + let expected_parsed = match parse(expected_source) { + Ok(p) => p, + Err(e) => panic!("{e}"), + }; if !test_config.skip_compare_ast_eq && !source_parsed.pt.ast_eq(&expected_parsed.pt) { similar_asserts::assert_eq!( @@ -235,3 +241,4 @@ test_directories! { } test_dir!(SortedImports, TestConfig::skip_compare_ast_eq()); +test_dir!(NonKeywords, TestConfig::skip_compare_ast_eq()); diff --git a/crates/forge/Cargo.toml b/crates/forge/Cargo.toml index 4f8a20e86c9ad..ee5727837862c 100644 --- a/crates/forge/Cargo.toml +++ b/crates/forge/Cargo.toml @@ -17,6 +17,11 @@ workspace = true name = "forge" path = "bin/main.rs" +[[test]] +name = "ui" +path = "tests/ui.rs" +harness = false + [dependencies] # lib foundry-block-explorers = { workspace = true, features = ["foundry-compilers"] } @@ -24,9 +29,8 @@ foundry-common.workspace = true foundry-compilers = { workspace = true, features = ["full"] } foundry-config.workspace = true foundry-evm.workspace = true -foundry-wallets.workspace = true +foundry-evm-core.workspace = true foundry-linking.workspace = true -forge-script-sequence.workspace = true comfy-table.workspace = true eyre.workspace = true @@ -40,6 +44,7 @@ chrono.workspace = true # bin forge-doc.workspace = true forge-fmt.workspace = true +forge-lint.workspace = true forge-verify.workspace = true forge-script.workspace = true forge-sol-macro-gen.workspace = true @@ -57,32 +62,33 @@ alloy-serde.workspace = true alloy-signer.workspace = true alloy-transport.workspace = true +revm.workspace = true + clap = { version = "4", features = ["derive", "env", "unicode", "wrap_help"] } clap_complete = "4" clap_complete_fig = "4" dunce.workspace = true -futures.workspace = true indicatif.workspace = true inferno = { version = "0.12", default-features = false } itertools.workspace = true parking_lot.workspace = true regex = { workspace = true, default-features = false } -reqwest = { workspace = true, features = ["json"] } semver.workspace = true serde_json.workspace = true similar = { version = "2", features = ["inline"] } solang-parser.workspace = true solar-parse.workspace = true +solar-sema.workspace = true strum = { workspace = true, features = ["derive"] } thiserror.workspace = true tokio = { workspace = true, features = ["time"] } -toml = { workspace = true, features = ["preserve_order"] } toml_edit = "0.22" -watchexec = "6.0" -watchexec-events = "5.0" -watchexec-signals = "4.0" +watchexec = "8.0" +watchexec-events = "6.0" +watchexec-signals = "5.0" clearscreen = "4.0" evm-disassembler.workspace = true +path-slash.workspace = true # doc server axum = { workspace = true, features = ["ws"] } @@ -91,19 +97,20 @@ opener = "0.7" # soldeer soldeer-commands.workspace = true -quick-junit = "0.5.0" - -[target.'cfg(unix)'.dependencies] -tikv-jemallocator = { workspace = true, optional = true } +quick-junit = "0.5.1" [dev-dependencies] +alloy-hardforks.workspace = true anvil.workspace = true +forge-script-sequence.workspace = true foundry-test-utils.workspace = true +foundry-wallets.workspace = true +futures.workspace = true +reqwest = { workspace = true, features = ["json"] } mockall = "0.13" globset = "0.4" paste = "1.0" -path-slash = "0.2" similar-asserts.workspace = true svm = { package = "svm-rs", version = "0.5", default-features = false, features = [ "rustls", @@ -115,7 +122,9 @@ alloy-signer-local.workspace = true [features] default = ["jemalloc"] asm-keccak = ["alloy-primitives/asm-keccak"] -jemalloc = ["dep:tikv-jemallocator"] +jemalloc = ["foundry-cli/jemalloc"] +mimalloc = ["foundry-cli/mimalloc"] +tracy-allocator = ["foundry-cli/tracy-allocator"] aws-kms = ["foundry-wallets/aws-kms"] gcp-kms = ["foundry-wallets/gcp-kms"] isolate-by-default = ["foundry-config/isolate-by-default"] diff --git a/crates/forge/assets/workflowTemplate.yml b/crates/forge/assets/workflowTemplate.yml index 34a4a527be6f9..4481ec6a8b2de 100644 --- a/crates/forge/assets/workflowTemplate.yml +++ b/crates/forge/assets/workflowTemplate.yml @@ -10,9 +10,6 @@ env: jobs: check: - strategy: - fail-fast: true - name: Foundry project runs-on: ubuntu-latest steps: diff --git a/crates/forge/bin/main.rs b/crates/forge/bin/main.rs index e198d9a4afc1b..bce1bd085f1fa 100644 --- a/crates/forge/bin/main.rs +++ b/crates/forge/bin/main.rs @@ -3,9 +3,8 @@ use forge::args::run; -#[cfg(all(feature = "jemalloc", unix))] #[global_allocator] -static ALLOC: tikv_jemallocator::Jemalloc = tikv_jemallocator::Jemalloc; +static ALLOC: foundry_cli::utils::Allocator = foundry_cli::utils::new_allocator(); fn main() { if let Err(err) = run() { diff --git a/crates/forge/src/args.rs b/crates/forge/src/args.rs index 92922155c5775..2d411d7c30bc8 100644 --- a/crates/forge/src/args.rs +++ b/crates/forge/src/args.rs @@ -151,5 +151,6 @@ pub fn run_command(args: Forge) -> Result<()> { ForgeSubcommand::Soldeer(cmd) => utils::block_on(cmd.run()), ForgeSubcommand::Eip712(cmd) => cmd.run(), ForgeSubcommand::BindJson(cmd) => cmd.run(), + ForgeSubcommand::Lint(cmd) => cmd.run(), } } diff --git a/crates/forge/src/cmd/bind_json.rs b/crates/forge/src/cmd/bind_json.rs index 7c6bfaa52ad74..fc5ca28bccdf7 100644 --- a/crates/forge/src/cmd/bind_json.rs +++ b/crates/forge/src/cmd/bind_json.rs @@ -1,28 +1,30 @@ use super::eip712::Resolver; use clap::{Parser, ValueHint}; use eyre::Result; -use foundry_cli::{opts::BuildOpts, utils::LoadConfig}; -use foundry_common::{compile::with_compilation_reporter, fs}; +use foundry_cli::{ + opts::{solar_pcx_from_solc_project, BuildOpts}, + utils::LoadConfig, +}; +use foundry_common::{fs, TYPE_BINDING_PREFIX}; use foundry_compilers::{ - artifacts::{ - output_selection::OutputSelection, ContractDefinitionPart, Source, SourceUnit, - SourceUnitPart, Sources, - }, + artifacts::{Source, Sources}, multi::{MultiCompilerLanguage, MultiCompilerParsedSource}, - project::ProjectCompiler, - solc::SolcLanguage, - Graph, Project, + solc::{SolcLanguage, SolcVersionedInput}, + CompilerInput, Graph, Project, }; use foundry_config::Config; use itertools::Itertools; +use path_slash::PathExt; use rayon::prelude::*; +use semver::Version; use solar_parse::{ ast::{self, interface::source_map::FileName, visit::Visit, Arena, FunctionKind, Span, VarMut}, interface::Session, Parser as SolarParser, }; +use solar_sema::thread_local::ThreadLocal; use std::{ - collections::{BTreeMap, BTreeSet}, + collections::{BTreeMap, BTreeSet, HashSet}, fmt::{self, Write}, ops::ControlFlow, path::PathBuf, @@ -31,6 +33,8 @@ use std::{ foundry_config::impl_figment_convert!(BindJsonArgs, build); +const JSON_BINDINGS_PLACEHOLDER: &str = "library JsonBindings {}"; + /// CLI arguments for `forge bind-json`. #[derive(Clone, Debug, Parser)] pub struct BindJsonArgs { @@ -44,7 +48,7 @@ pub struct BindJsonArgs { impl BindJsonArgs { pub fn run(self) -> Result<()> { - self.preprocess()?.compile()?.find_structs()?.resolve_imports_and_aliases().write()?; + self.preprocess()?.find_structs()?.resolve_imports_and_aliases().write()?; Ok(()) } @@ -74,7 +78,7 @@ impl BindJsonArgs { let graph = Graph::::resolve_sources(&project.paths, sources)?; // We only generate bindings for a single Solidity version to avoid conflicts. - let mut sources = graph + let (version, mut sources, _) = graph // resolve graph into mapping language -> version -> sources .into_sources_by_version(&project)? .sources @@ -86,8 +90,7 @@ impl BindJsonArgs { .into_iter() // For now, we are always picking the latest version. .max_by(|(v1, _, _), (v2, _, _)| v1.cmp(v2)) - .unwrap() - .1; + .unwrap(); let sess = Session::builder().with_stderr_emitter().build(); let result = sess.enter_parallel(|| -> solar_parse::interface::Result<()> { @@ -114,9 +117,9 @@ impl BindJsonArgs { eyre::ensure!(result.is_ok(), "failed parsing"); // Insert empty bindings file. - sources.insert(target_path.clone(), Source::new("library JsonBindings {}")); + sources.insert(target_path.clone(), Source::new(JSON_BINDINGS_PLACEHOLDER)); - Ok(PreprocessedState { sources, target_path, project, config }) + Ok(PreprocessedState { version, sources, target_path, project, config }) } } @@ -237,8 +240,8 @@ impl StructToWrite { } } -#[derive(Debug)] struct PreprocessedState { + version: Version, sources: Sources, target_path: PathBuf, project: Project, @@ -246,117 +249,87 @@ struct PreprocessedState { } impl PreprocessedState { - fn compile(self) -> Result { - let Self { sources, target_path, mut project, config } = self; + fn find_structs(self) -> Result { + let mut structs_to_write = Vec::new(); + let Self { version, sources, target_path, config, project } = self; - project.update_output_selection(|selection| { - *selection = OutputSelection::ast_output_selection(); - }); + let settings = config.solc_settings()?; + let include = config.bind_json.include; + let exclude = config.bind_json.exclude; + let root = config.root; - let output = with_compilation_reporter(false, || { - ProjectCompiler::with_sources(&project, sources)?.compile() - })?; + let input = SolcVersionedInput::build(sources, settings, SolcLanguage::Solidity, version); - if output.has_compiler_errors() { - eyre::bail!("{output}"); - } + let mut sess = Session::builder().with_stderr_emitter().build(); + sess.dcx = sess.dcx.set_flags(|flags| flags.track_diagnostics = false); - // Collect ASTs by getting them from sources and converting into strongly typed - // `SourceUnit`s. Also strips root from paths. - let asts = output - .into_output() - .sources - .into_iter() - .filter_map(|(path, mut sources)| Some((path, sources.swap_remove(0).source_file.ast?))) - .map(|(path, ast)| { - Ok(( - path.strip_prefix(project.root()).unwrap_or(&path).to_path_buf(), - serde_json::from_str::(&serde_json::to_string(&ast)?)?, - )) - }) - .collect::>>()?; - - Ok(CompiledState { asts, target_path, config, project }) - } -} + sess.enter_parallel(|| -> Result<()> { + // Set up the parsing context with the project paths, without adding the source files + let mut parsing_context = solar_pcx_from_solc_project(&sess, &project, &input, false); -#[derive(Debug, Clone)] -struct CompiledState { - asts: BTreeMap, - target_path: PathBuf, - config: Config, - project: Project, -} - -impl CompiledState { - fn find_structs(self) -> Result { - let Self { asts, target_path, config, project } = self; - - // construct mapping (file, id) -> (struct definition, optional parent contract name) - let structs = asts - .iter() - .flat_map(|(path, ast)| { - let mut structs = Vec::new(); - // we walk AST directly instead of using visitors because we need to distinguish - // between file-level and contract-level struct definitions - for node in &ast.nodes { - match node { - SourceUnitPart::StructDefinition(def) => { - structs.push((def, None)); - } - SourceUnitPart::ContractDefinition(contract) => { - for node in &contract.nodes { - if let ContractDefinitionPart::StructDefinition(def) = node { - structs.push((def, Some(contract.name.clone()))); - } - } - } - _ => {} + let mut target_files = HashSet::new(); + for (path, source) in &input.input.sources { + if !include.is_empty() { + if !include.iter().any(|matcher| matcher.is_match(path)) { + continue; + } + } else { + // Exclude library files by default + if project.paths.has_library_ancestor(path) { + continue; } } - structs.into_iter().map(|(def, parent)| ((path.as_path(), def.id), (def, parent))) - }) - .collect::>(); - - // Resolver for EIP712 schemas - let resolver = Resolver::new(&asts); - - let mut structs_to_write = Vec::new(); - - let include = config.bind_json.include; - let exclude = config.bind_json.exclude; - - for ((path, id), (def, contract_name)) in structs { - // For some structs there's no schema (e.g. if they contain a mapping), so we just skip - // those. - let Some(schema) = resolver.resolve_struct_eip712(id)? else { continue }; - if !include.is_empty() { - if !include.iter().any(|matcher| matcher.is_match(path)) { + if exclude.iter().any(|matcher| matcher.is_match(path)) { continue; } - } else { - // Exclude library files by default - if project.paths.has_library_ancestor(path) { - continue; + + if let Ok(src_file) = + sess.source_map().new_source_file(path.clone(), source.content.as_str()) + { + target_files.insert(src_file.stable_id); + parsing_context.add_file(src_file); } } - if exclude.iter().any(|matcher| matcher.is_match(path)) { - continue; - } + // Parse and resolve + let hir_arena = ThreadLocal::new(); + if let Ok(Some(gcx)) = parsing_context.parse_and_lower(&hir_arena) { + let hir = &gcx.get().hir; + let resolver = Resolver::new(gcx); + for id in resolver.struct_ids() { + if let Some(schema) = resolver.resolve_struct_eip712(id) { + let def = hir.strukt(id); + let source = hir.source(def.source); + + if !target_files.contains(&source.file.stable_id) { + continue; + } - structs_to_write.push(StructToWrite { - name: def.name.clone(), - contract_name, - path: path.to_path_buf(), - schema, + if let FileName::Real(ref path) = source.file.name { + structs_to_write.push(StructToWrite { + name: def.name.as_str().into(), + contract_name: def + .contract + .map(|id| hir.contract(id).name.as_str().into()), + path: path + .strip_prefix(&root) + .unwrap_or_else(|_| path) + .to_path_buf(), + schema, + + // will be filled later + import_alias: None, + name_in_fns: String::new(), + }); + } + } + } + } + Ok(()) + })?; - // will be filled later - import_alias: None, - name_in_fns: String::new(), - }) - } + eyre::ensure!(sess.dcx.has_errors().is_ok(), "errors occurred"); Ok(StructsState { structs_to_write, target_path }) } @@ -482,7 +455,7 @@ impl ResolvedState { result, "import {{{}}} from \"{}\";", names.iter().join(", "), - path.display() + path.to_slash_lossy() )?; } @@ -514,8 +487,8 @@ library JsonBindings { for struct_to_write in &self.structs_to_write { writeln!( result, - " string constant schema_{} = \"{}\";", - struct_to_write.name_in_fns, struct_to_write.schema + " {}{} = \"{}\";", + TYPE_BINDING_PREFIX, struct_to_write.name_in_fns, struct_to_write.schema )?; } diff --git a/crates/forge/src/cmd/build.rs b/crates/forge/src/cmd/build.rs index e64569206775b..4805865311581 100644 --- a/crates/forge/src/cmd/build.rs +++ b/crates/forge/src/cmd/build.rs @@ -1,10 +1,15 @@ use super::{install, watch::WatchArgs}; use clap::Parser; use eyre::Result; -use foundry_cli::{opts::BuildOpts, utils::LoadConfig}; +use forge_lint::{linter::Linter, sol::SolidityLinter}; +use foundry_cli::{ + opts::BuildOpts, + utils::{cache_local_signatures, LoadConfig}, +}; use foundry_common::{compile::ProjectCompiler, shell}; use foundry_compilers::{ compilers::{multi::MultiCompilerLanguage, Language}, + solc::SolcLanguage, utils::source_files_iter, Project, ProjectCompileOutput, }; @@ -15,6 +20,7 @@ use foundry_config::{ value::{Dict, Map, Value}, Metadata, Profile, Provider, }, + filter::expand_globs, Config, }; use serde::Serialize; @@ -98,8 +104,13 @@ impl BuildArgs { .ignore_eip_3860(self.ignore_eip_3860) .bail(!format_json); + // Runs the SolidityLinter before compilation. + self.lint(&project, &config)?; let output = compiler.compile(&project)?; + // Cache project selectors. + cache_local_signatures(&output)?; + if format_json && !self.names && !self.sizes { sh_println!("{}", serde_json::to_string_pretty(&output.output())?)?; } @@ -107,6 +118,51 @@ impl BuildArgs { Ok(output) } + fn lint(&self, project: &Project, config: &Config) -> Result<()> { + let format_json = shell::is_json(); + if project.compiler.solc.is_some() && config.lint.lint_on_build && !shell::is_quiet() { + let linter = SolidityLinter::new(config.project_paths()) + .with_json_emitter(format_json) + .with_description(!format_json) + .with_severity(if config.lint.severity.is_empty() { + None + } else { + Some(config.lint.severity.clone()) + }) + .without_lints(if config.lint.exclude_lints.is_empty() { + None + } else { + Some( + config + .lint + .exclude_lints + .iter() + .filter_map(|s| forge_lint::sol::SolLint::try_from(s.as_str()).ok()) + .collect(), + ) + }); + + // Expand ignore globs and canonicalize from the get go + let ignored = expand_globs(&config.root, config.lint.ignore.iter())? + .iter() + .flat_map(foundry_common::fs::canonicalize_path) + .collect::>(); + + let curr_dir = std::env::current_dir()?; + let input_files = config + .project_paths::() + .input_files_iter() + .filter(|p| !(ignored.contains(p) || ignored.contains(&curr_dir.join(p)))) + .collect::>(); + + if !input_files.is_empty() { + linter.lint(&input_files); + } + } + + Ok(()) + } + /// Returns the `Project` for the current workspace /// /// This loads the `foundry_config::Config` for the current workspace (see diff --git a/crates/forge/src/cmd/clone.rs b/crates/forge/src/cmd/clone.rs index aaf157637c9e7..b1a28b60c70f5 100644 --- a/crates/forge/src/cmd/clone.rs +++ b/crates/forge/src/cmd/clone.rs @@ -101,8 +101,10 @@ impl CloneArgs { // step 0. get the chain and api key from the config let config = etherscan.load_config()?; let chain = config.chain.unwrap_or_default(); + let etherscan_api_version = config.get_etherscan_api_version(Some(chain)); let etherscan_api_key = config.get_etherscan_api_key(Some(chain)).unwrap_or_default(); - let client = Client::new(chain, etherscan_api_key.clone())?; + let client = + Client::new_with_api_version(chain, etherscan_api_key.clone(), etherscan_api_version)?; // step 1. get the metadata from client sh_println!("Downloading the source code of {address} from Etherscan...")?; @@ -554,10 +556,8 @@ fn dump_sources(meta: &Metadata, root: &PathBuf, no_reorg: bool) -> Result 1 { - Some( - EvmVersion::default() - .normalize_version_solc(version) - .unwrap_or_default(), - ) + let evm = EvmVersion::default() + .normalize_version_solc(version) + .unwrap_or_default(); + + // Vyper does not yet support Prague, so we normalize it to Cancun. + if language.is_vyper() { + Some(normalize_evm_version_vyper(evm)) + } else { + Some(evm) + } } else { None }; diff --git a/crates/forge/src/cmd/coverage.rs b/crates/forge/src/cmd/coverage.rs index 2b656c0be493f..729f96e5a9838 100644 --- a/crates/forge/src/cmd/coverage.rs +++ b/crates/forge/src/cmd/coverage.rs @@ -6,7 +6,6 @@ use crate::{ BytecodeReporter, ContractId, CoverageReport, CoverageReporter, CoverageSummaryReporter, DebugReporter, ItemAnchor, LcovReporter, }, - utils::IcPcMap, MultiContractRunnerBuilder, }; use alloy_primitives::{map::HashMap, Address, Bytes, U256}; @@ -23,6 +22,7 @@ use foundry_compilers::{ }; use foundry_config::Config; use foundry_evm::opts::EvmOpts; +use foundry_evm_core::ic::IcPcMap; use rayon::prelude::*; use semver::{Version, VersionReq}; use std::{ @@ -75,6 +75,10 @@ pub struct CoverageArgs { #[arg(long)] include_libs: bool, + /// Whether to exclude tests from the coverage report. + #[arg(long)] + exclude_tests: bool, + /// The coverage reporters to use. Constructed from the other fields. #[arg(skip)] reporters: Vec>, @@ -194,8 +198,10 @@ impl CoverageArgs { for (path, source_file, version) in output.output().sources.sources_with_version() { report.add_source(version.clone(), source_file.id as usize, path.clone()); - // Filter out dependencies. - if !self.include_libs && project_paths.has_library_ancestor(path) { + // Filter out libs dependencies and tests. + if (!self.include_libs && project_paths.has_library_ancestor(path)) || + (self.exclude_tests && project_paths.is_test(path)) + { continue; } diff --git a/crates/forge/src/cmd/create.rs b/crates/forge/src/cmd/create.rs index 744c38d71d977..6768b43829836 100644 --- a/crates/forge/src/cmd/create.rs +++ b/crates/forge/src/cmd/create.rs @@ -184,7 +184,7 @@ impl CreateArgs { let deployer = signer.address(); let provider = ProviderBuilder::<_, _, AnyNetwork>::default() .wallet(EthereumWallet::new(signer)) - .on_provider(provider); + .connect_provider(provider); self.deploy( abi, bin, @@ -228,6 +228,7 @@ impl CreateArgs { num_of_optimizations: None, etherscan: EtherscanOpts { key: self.eth.etherscan.key.clone(), + api_version: self.eth.etherscan.api_version, chain: Some(chain.into()), }, rpc: Default::default(), @@ -416,7 +417,11 @@ impl CreateArgs { constructor_args, constructor_args_path: None, num_of_optimizations, - etherscan: EtherscanOpts { key: self.eth.etherscan.key(), chain: Some(chain.into()) }, + etherscan: EtherscanOpts { + key: self.eth.etherscan.key(), + api_version: self.eth.etherscan.api_version, + chain: Some(chain.into()), + }, rpc: Default::default(), flatten: false, force: false, diff --git a/crates/forge/src/cmd/eip712.rs b/crates/forge/src/cmd/eip712.rs index 3c85840673d6a..4c7196ac5ddd5 100644 --- a/crates/forge/src/cmd/eip712.rs +++ b/crates/forge/src/cmd/eip712.rs @@ -1,13 +1,20 @@ +use alloy_primitives::{keccak256, B256}; use clap::{Parser, ValueHint}; -use eyre::{Ok, OptionExt, Result}; -use foundry_cli::{opts::BuildOpts, utils::LoadConfig}; -use foundry_common::compile::ProjectCompiler; -use foundry_compilers::artifacts::{ - output_selection::OutputSelection, - visitor::{Visitor, Walk}, - ContractDefinition, EnumDefinition, SourceUnit, StructDefinition, TypeDescriptions, TypeName, +use eyre::Result; +use foundry_cli::opts::{solar_pcx_from_build_opts, BuildOpts}; +use serde::Serialize; +use solar_parse::interface::Session; +use solar_sema::{ + hir::StructId, + thread_local::ThreadLocal, + ty::{Ty, TyKind}, + GcxWrapper, Hir, +}; +use std::{ + collections::BTreeMap, + fmt::{Display, Formatter, Result as FmtResult, Write}, + path::{Path, PathBuf}, }; -use std::{collections::BTreeMap, fmt::Write, path::PathBuf}; foundry_config::impl_figment_convert!(Eip712Args, build); @@ -18,238 +25,220 @@ pub struct Eip712Args { #[arg(value_hint = ValueHint::FilePath, value_name = "PATH")] pub target_path: PathBuf, + /// Output in JSON format. + #[arg(long, help = "Output in JSON format")] + pub json: bool, + #[command(flatten)] build: BuildOpts, } +#[derive(Debug, Serialize)] +struct Eip712Output { + path: String, + #[serde(rename = "type")] + typ: String, + hash: B256, +} + +impl Display for Eip712Output { + fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { + writeln!(f, "{}:", self.path)?; + writeln!(f, " - type: {}", self.typ)?; + writeln!(f, " - hash: {}", self.hash) + } +} + impl Eip712Args { pub fn run(self) -> Result<()> { - let config = self.load_config()?; - let mut project = config.ephemeral_project()?; - let target_path = dunce::canonicalize(self.target_path)?; - project.update_output_selection(|selection| { - *selection = OutputSelection::ast_output_selection(); - }); - - let output = ProjectCompiler::new().files([target_path.clone()]).compile(&project)?; - - // Collect ASTs by getting them from sources and converting into strongly typed - // `SourceUnit`s. - let asts = output - .into_output() - .sources - .into_iter() - .filter_map(|(path, mut sources)| Some((path, sources.swap_remove(0).source_file.ast?))) - .map(|(path, ast)| { - Ok((path, serde_json::from_str::(&serde_json::to_string(&ast)?)?)) - }) - .collect::>>()?; - - let resolver = Resolver::new(&asts); - - let target_ast = asts - .get(&target_path) - .ok_or_else(|| eyre::eyre!("Could not find AST for target file {target_path:?}"))?; - - let structs_in_target = { - let mut collector = StructCollector::default(); - target_ast.walk(&mut collector); - collector.0 - }; - - for id in structs_in_target.keys() { - if let Some(resolved) = resolver.resolve_struct_eip712(*id)? { - sh_println!("{resolved}\n")?; + let mut sess = Session::builder().with_stderr_emitter().build(); + sess.dcx = sess.dcx.set_flags(|flags| flags.track_diagnostics = false); + + sess.enter_parallel(|| -> Result<()> { + // Set up the parsing context with the project paths and sources. + let parsing_context = + solar_pcx_from_build_opts(&sess, self.build, Some(vec![self.target_path]))?; + + // Parse and resolve + let hir_arena = ThreadLocal::new(); + let Ok(Some(gcx)) = parsing_context.parse_and_lower(&hir_arena) else { + return Err(eyre::eyre!("failed parsing")); + }; + let resolver = Resolver::new(gcx); + + let outputs = resolver + .struct_ids() + .filter_map(|id| { + let resolved = resolver.resolve_struct_eip712(id)?; + Some(Eip712Output { + path: resolver.get_struct_path(id), + hash: keccak256(resolved.as_bytes()), + typ: resolved, + }) + }) + .collect::>(); + + if self.json { + sh_println!("{json}", json = serde_json::to_string_pretty(&outputs)?)?; + } else { + for output in &outputs { + sh_println!("{output}")?; + } } - } - Ok(()) - } -} + Ok(()) + })?; -/// AST [Visitor] used for collecting struct definitions. -#[derive(Debug, Clone, Default)] -pub struct StructCollector(pub BTreeMap); + eyre::ensure!(sess.dcx.has_errors().is_ok(), "errors occurred"); -impl Visitor for StructCollector { - fn visit_struct_definition(&mut self, def: &StructDefinition) { - self.0.insert(def.id, def.clone()); + Ok(()) } } -/// Collects mapping from AST id of type definition to representation of this type for EIP-712 -/// encoding. +/// Generates the EIP-712 `encodeType` string for a given struct. /// -/// For now, maps contract definitions to `address` and enums to `uint8`. -#[derive(Debug, Clone, Default)] -struct SimpleCustomTypesCollector(BTreeMap); +/// Requires a reference to the source HIR. +pub struct Resolver<'hir> { + gcx: GcxWrapper<'hir>, +} -impl Visitor for SimpleCustomTypesCollector { - fn visit_contract_definition(&mut self, def: &ContractDefinition) { - self.0.insert(def.id, "address".to_string()); +impl<'hir> Resolver<'hir> { + /// Constructs a new [`Resolver`] for the supplied [`Hir`] instance. + pub fn new(gcx: GcxWrapper<'hir>) -> Self { + Self { gcx } } - fn visit_enum_definition(&mut self, def: &EnumDefinition) { - self.0.insert(def.id, "uint8".to_string()); + #[inline] + fn hir(&self) -> &'hir Hir<'hir> { + &self.gcx.get().hir } -} - -pub struct Resolver { - simple_types: BTreeMap, - structs: BTreeMap, -} - -impl Resolver { - pub fn new(asts: &BTreeMap) -> Self { - let simple_types = { - let mut collector = SimpleCustomTypesCollector::default(); - asts.values().for_each(|ast| ast.walk(&mut collector)); - - collector.0 - }; - let structs = { - let mut collector = StructCollector::default(); - asts.values().for_each(|ast| ast.walk(&mut collector)); - collector.0 - }; + /// Returns the [`StructId`]s of every user-defined struct in source order. + pub fn struct_ids(&self) -> impl Iterator { + self.hir().strukt_ids() + } - Self { simple_types, structs } + /// Returns the path for a struct, with the format: `file.sol > MyContract > MyStruct` + pub fn get_struct_path(&self, id: StructId) -> String { + let strukt = self.hir().strukt(id).name.as_str(); + match self.hir().strukt(id).contract { + Some(cid) => { + let full_name = self.gcx.get().contract_fully_qualified_name(cid).to_string(); + let relevant = Path::new(&full_name) + .file_name() + .and_then(|s| s.to_str()) + .unwrap_or(&full_name); + + if let Some((file, contract)) = relevant.rsplit_once(':') { + format!("{file} > {contract} > {strukt}") + } else { + format!("{relevant} > {strukt}") + } + } + None => strukt.to_string(), + } } - /// Converts a given struct definition into EIP-712 `encodeType` representation. + /// Converts a given struct into its EIP-712 `encodeType` representation. /// - /// Returns `None` if struct contains any fields that are not supported by EIP-712 (e.g. - /// mappings or function pointers). - pub fn resolve_struct_eip712(&self, id: usize) -> Result> { + /// Returns `None` if the struct, or any of its fields, contains constructs + /// not supported by EIP-712 (mappings, function types, errors, etc). + pub fn resolve_struct_eip712(&self, id: StructId) -> Option { let mut subtypes = BTreeMap::new(); - subtypes.insert(self.structs[&id].name.clone(), id); + subtypes.insert(self.hir().strukt(id).name.as_str().into(), id); self.resolve_eip712_inner(id, &mut subtypes, true, None) } fn resolve_eip712_inner( &self, - id: usize, - subtypes: &mut BTreeMap, + id: StructId, + subtypes: &mut BTreeMap, append_subtypes: bool, rename: Option<&str>, - ) -> Result> { - let def = &self.structs[&id]; - let mut result = format!("{}(", rename.unwrap_or(&def.name)); - - for (idx, member) in def.members.iter().enumerate() { - let Some(ty) = self.resolve_type( - member.type_name.as_ref().ok_or_eyre("missing type name")?, - subtypes, - )? - else { - return Ok(None) - }; + ) -> Option { + let def = self.hir().strukt(id); + let mut result = format!("{}(", rename.unwrap_or(def.name.as_str())); + + for (idx, field_id) in def.fields.iter().enumerate() { + let field = self.hir().variable(*field_id); + let ty = self.resolve_type(self.gcx.get().type_of_hir_ty(&field.ty), subtypes)?; - write!(result, "{ty} {name}", name = member.name)?; + write!(result, "{ty} {name}", name = field.name?.as_str()).ok()?; - if idx < def.members.len() - 1 { + if idx < def.fields.len() - 1 { result.push(','); } } result.push(')'); - if !append_subtypes { - return Ok(Some(result)) - } + if append_subtypes { + for (subtype_name, subtype_id) in + subtypes.iter().map(|(name, id)| (name.clone(), *id)).collect::>() + { + if subtype_id == id { + continue + } + let encoded_subtype = + self.resolve_eip712_inner(subtype_id, subtypes, false, Some(&subtype_name))?; - for (subtype_name, subtype_id) in - subtypes.iter().map(|(name, id)| (name.clone(), *id)).collect::>() - { - if subtype_id == id { - continue + result.push_str(&encoded_subtype); } - let Some(encoded_subtype) = - self.resolve_eip712_inner(subtype_id, subtypes, false, Some(&subtype_name))? - else { - return Ok(None) - }; - result.push_str(&encoded_subtype); } - Ok(Some(result)) + Some(result) } - /// Converts given [TypeName] into a type which can be converted to - /// [`alloy_dyn_abi::DynSolType`]. - /// - /// Returns `None` if the type is not supported for EIP712 encoding. - pub fn resolve_type( + fn resolve_type( &self, - type_name: &TypeName, - subtypes: &mut BTreeMap, - ) -> Result> { - match type_name { - TypeName::FunctionTypeName(_) | TypeName::Mapping(_) => Ok(None), - TypeName::ElementaryTypeName(ty) => Ok(Some(ty.name.clone())), - TypeName::ArrayTypeName(ty) => { - let Some(inner) = self.resolve_type(&ty.base_type, subtypes)? else { - return Ok(None) + ty: Ty<'hir>, + subtypes: &mut BTreeMap, + ) -> Option { + let ty = ty.peel_refs(); + match ty.kind { + TyKind::Elementary(elem_ty) => Some(elem_ty.to_abi_str().to_string()), + TyKind::Array(element_ty, size) => { + let inner_type = self.resolve_type(element_ty, subtypes)?; + let size = size.to_string(); + Some(format!("{inner_type}[{size}]")) + } + TyKind::DynArray(element_ty) => { + let inner_type = self.resolve_type(element_ty, subtypes)?; + Some(format!("{inner_type}[]")) + } + TyKind::Udvt(ty, _) => self.resolve_type(ty, subtypes), + TyKind::Struct(id) => { + let def = self.hir().strukt(id); + let name = match subtypes.iter().find(|(_, cached_id)| id == **cached_id) { + Some((name, _)) => name.to_string(), + None => { + // Otherwise, assign new name + let mut i = 0; + let mut name = def.name.as_str().into(); + while subtypes.contains_key(&name) { + i += 1; + name = format!("{}_{i}", def.name.as_str()); + } + + subtypes.insert(name.clone(), id); + + // Recursively resolve fields to populate subtypes + for &field_id in def.fields { + let field_ty = self.gcx.get().type_of_item(field_id.into()); + self.resolve_type(field_ty, subtypes)?; + } + name + } }; - let len = parse_array_length(&ty.type_descriptions)?; - Ok(Some(format!("{inner}[{}]", len.unwrap_or("")))) - } - TypeName::UserDefinedTypeName(ty) => { - if let Some(name) = self.simple_types.get(&(ty.referenced_declaration as usize)) { - Ok(Some(name.clone())) - } else if let Some(def) = self.structs.get(&(ty.referenced_declaration as usize)) { - let name = - // If we've already seen struct with this ID, just use assigned name. - if let Some((name, _)) = subtypes.iter().find(|(_, id)| **id == def.id) { - name.clone() - } else { - // Otherwise, assign new name. - let mut i = 0; - let mut name = def.name.clone(); - while subtypes.contains_key(&name) { - i += 1; - name = format!("{}_{i}", def.name); - } - - subtypes.insert(name.clone(), def.id); - - // iterate over members to check if they are resolvable and to populate subtypes - for member in &def.members { - if self.resolve_type( - member.type_name.as_ref().ok_or_eyre("missing type name")?, - subtypes, - )? - .is_none() - { - return Ok(None) - } - } - name - }; - - return Ok(Some(name)) - } else { - return Ok(None) - } + Some(name) } + // For now, map enums to `uint8` + TyKind::Enum(_) => Some("uint8".to_string()), + // For now, map contracts to `address` + TyKind::Contract(_) => Some("address".to_string()), + // EIP-712 doesn't support tuples (should use structs), functions, mappings, nor errors + _ => None, } } } - -fn parse_array_length(type_description: &TypeDescriptions) -> Result> { - let type_string = - type_description.type_string.as_ref().ok_or_eyre("missing typeString for array type")?; - let Some(inside_brackets) = - type_string.rsplit_once("[").and_then(|(_, right)| right.split("]").next()) - else { - eyre::bail!("failed to parse array type string: {type_string}") - }; - - if inside_brackets.is_empty() { - Ok(None) - } else { - Ok(Some(inside_brackets)) - } -} diff --git a/crates/forge/src/cmd/fmt.rs b/crates/forge/src/cmd/fmt.rs index 44828d82760b9..948d64139e95c 100644 --- a/crates/forge/src/cmd/fmt.rs +++ b/crates/forge/src/cmd/fmt.rs @@ -123,7 +123,8 @@ impl FmtArgs { solang_parser::parse(&output, 0).map_err(|diags| { eyre::eyre!( "Failed to construct valid Solidity code for {name}. Leaving source unchanged.\n\ - Debug info: {diags:?}" + Debug info: {diags:?}\n\ + Formatted output:\n\n{output}" ) })?; diff --git a/crates/forge/src/cmd/inspect.rs b/crates/forge/src/cmd/inspect.rs index 32daac06f6172..af0b10a66b5f0 100644 --- a/crates/forge/src/cmd/inspect.rs +++ b/crates/forge/src/cmd/inspect.rs @@ -1,22 +1,22 @@ -use crate::revm::primitives::Eof; use alloy_json_abi::{EventParam, InternalType, JsonAbi, Param}; -use alloy_primitives::{hex, keccak256, Address}; +use alloy_primitives::{hex, keccak256}; use clap::Parser; use comfy_table::{modifiers::UTF8_ROUND_CORNERS, Cell, Table}; -use eyre::{Context, Result}; +use eyre::{eyre, Result}; use foundry_cli::opts::{BuildOpts, CompilerOpts}; use foundry_common::{ compile::{PathOrContractInfo, ProjectCompiler}, - find_matching_contract_artifact, find_target_path, - fmt::pretty_eof, - shell, + find_matching_contract_artifact, find_target_path, shell, }; -use foundry_compilers::artifacts::{ - output_selection::{ - BytecodeOutputSelection, ContractOutputSelection, DeployedBytecodeOutputSelection, - EvmOutputSelection, EwasmOutputSelection, +use foundry_compilers::{ + artifacts::{ + output_selection::{ + BytecodeOutputSelection, ContractOutputSelection, DeployedBytecodeOutputSelection, + EvmOutputSelection, EwasmOutputSelection, + }, + StorageLayout, }, - CompactBytecode, StorageLayout, + solc::SolcLanguage, }; use regex::Regex; use serde_json::{Map, Value}; @@ -50,8 +50,8 @@ impl InspectArgs { // Map field to ContractOutputSelection let mut cos = build.compiler.extra_output; - if !field.is_default() && !cos.iter().any(|selected| field == *selected) { - cos.push(field.into()); + if !field.can_skip_field() && !cos.iter().any(|selected| field == *selected) { + cos.push(field.try_into()?); } // Run Optimized? @@ -61,6 +61,9 @@ impl InspectArgs { build.compiler.optimize }; + // Get the solc version if specified + let solc_version = build.use_solc.clone(); + // Build modified Args let modified_build_args = BuildOpts { compiler: CompilerOpts { extra_output: cos, optimize: optimized, ..build.compiler }, @@ -132,11 +135,17 @@ impl InspectArgs { let out = artifact.abi.as_ref().map_or(Map::new(), parse_events); print_errors_events(&out, false)?; } - ContractArtifactField::Eof => { - print_eof(artifact.deployed_bytecode.and_then(|b| b.bytecode))?; - } - ContractArtifactField::EofInit => { - print_eof(artifact.bytecode)?; + ContractArtifactField::StandardJson => { + let standard_json = if let Some(version) = solc_version { + let version = version.parse()?; + let mut standard_json = + project.standard_json_input(&target_path)?.normalize_evm_version(&version); + standard_json.settings.sanitize(&version, SolcLanguage::Solidity); + standard_json + } else { + project.standard_json_input(&target_path)? + }; + print_json(&standard_json)?; } }; @@ -362,8 +371,7 @@ pub enum ContractArtifactField { Ewasm, Errors, Events, - Eof, - EofInit, + StandardJson, } macro_rules! impl_value_enum { @@ -451,37 +459,39 @@ impl_value_enum! { Ewasm => "ewasm" | "e-wasm", Errors => "errors" | "er", Events => "events" | "ev", - Eof => "eof" | "eof-container" | "eof-deployed", - EofInit => "eof-init" | "eof-initcode" | "eof-initcontainer", + StandardJson => "standardJson" | "standard-json" | "standard_json", } } -impl From for ContractOutputSelection { - fn from(field: ContractArtifactField) -> Self { +impl TryFrom for ContractOutputSelection { + type Error = eyre::Error; + + fn try_from(field: ContractArtifactField) -> Result { type Caf = ContractArtifactField; match field { - Caf::Abi => Self::Abi, - Caf::Bytecode => Self::Evm(EvmOutputSelection::ByteCode(BytecodeOutputSelection::All)), - Caf::DeployedBytecode => Self::Evm(EvmOutputSelection::DeployedByteCode( - DeployedBytecodeOutputSelection::All, - )), - Caf::Assembly | Caf::AssemblyOptimized => Self::Evm(EvmOutputSelection::Assembly), - Caf::LegacyAssembly => Self::Evm(EvmOutputSelection::LegacyAssembly), - Caf::MethodIdentifiers => Self::Evm(EvmOutputSelection::MethodIdentifiers), - Caf::GasEstimates => Self::Evm(EvmOutputSelection::GasEstimates), - Caf::StorageLayout => Self::StorageLayout, - Caf::DevDoc => Self::DevDoc, - Caf::Ir => Self::Ir, - Caf::IrOptimized => Self::IrOptimized, - Caf::Metadata => Self::Metadata, - Caf::UserDoc => Self::UserDoc, - Caf::Ewasm => Self::Ewasm(EwasmOutputSelection::All), - Caf::Errors => Self::Abi, - Caf::Events => Self::Abi, - Caf::Eof => Self::Evm(EvmOutputSelection::DeployedByteCode( + Caf::Abi => Ok(Self::Abi), + Caf::Bytecode => { + Ok(Self::Evm(EvmOutputSelection::ByteCode(BytecodeOutputSelection::All))) + } + Caf::DeployedBytecode => Ok(Self::Evm(EvmOutputSelection::DeployedByteCode( DeployedBytecodeOutputSelection::All, - )), - Caf::EofInit => Self::Evm(EvmOutputSelection::ByteCode(BytecodeOutputSelection::All)), + ))), + Caf::Assembly | Caf::AssemblyOptimized => Ok(Self::Evm(EvmOutputSelection::Assembly)), + Caf::LegacyAssembly => Ok(Self::Evm(EvmOutputSelection::LegacyAssembly)), + Caf::MethodIdentifiers => Ok(Self::Evm(EvmOutputSelection::MethodIdentifiers)), + Caf::GasEstimates => Ok(Self::Evm(EvmOutputSelection::GasEstimates)), + Caf::StorageLayout => Ok(Self::StorageLayout), + Caf::DevDoc => Ok(Self::DevDoc), + Caf::Ir => Ok(Self::Ir), + Caf::IrOptimized => Ok(Self::IrOptimized), + Caf::Metadata => Ok(Self::Metadata), + Caf::UserDoc => Ok(Self::UserDoc), + Caf::Ewasm => Ok(Self::Ewasm(EwasmOutputSelection::All)), + Caf::Errors => Ok(Self::Abi), + Caf::Events => Ok(Self::Abi), + Caf::StandardJson => { + Err(eyre!("StandardJson is not supported for ContractOutputSelection")) + } } } } @@ -506,9 +516,7 @@ impl PartialEq for ContractArtifactField { (Self::IrOptimized, Cos::IrOptimized) | (Self::Metadata, Cos::Metadata) | (Self::UserDoc, Cos::UserDoc) | - (Self::Ewasm, Cos::Ewasm(_)) | - (Self::Eof, Cos::Evm(Eos::DeployedByteCode(_))) | - (Self::EofInit, Cos::Evm(Eos::ByteCode(_))) + (Self::Ewasm, Cos::Ewasm(_)) ) } } @@ -520,9 +528,9 @@ impl fmt::Display for ContractArtifactField { } impl ContractArtifactField { - /// Returns true if this field is generated by default. - pub const fn is_default(&self) -> bool { - matches!(self, Self::Bytecode | Self::DeployedBytecode) + /// Returns true if this field does not need to be passed to the compiler. + pub const fn can_skip_field(&self) -> bool { + matches!(self, Self::Bytecode | Self::DeployedBytecode | Self::StandardJson) } } @@ -568,30 +576,6 @@ fn get_json_str(obj: &impl serde::Serialize, key: Option<&str>) -> Result) -> Result<()> { - let Some(mut bytecode) = bytecode else { eyre::bail!("No bytecode") }; - - // Replace link references with zero address. - if bytecode.object.is_unlinked() { - for (file, references) in bytecode.link_references.clone() { - for (name, _) in references { - bytecode.link(&file, &name, Address::ZERO); - } - } - } - - let Some(bytecode) = bytecode.object.into_bytes() else { - eyre::bail!("Failed to link bytecode"); - }; - - let eof = Eof::decode(bytecode).wrap_err("Failed to decode EOF")?; - - sh_println!("{}", pretty_eof(&eof)?)?; - - Ok(()) -} - #[cfg(test)] mod tests { use super::*; @@ -599,14 +583,22 @@ mod tests { #[test] fn contract_output_selection() { for &field in ContractArtifactField::ALL { - let selection: ContractOutputSelection = field.into(); - assert_eq!(field, selection); - - let s = field.as_str(); - assert_eq!(s, field.to_string()); - assert_eq!(s.parse::().unwrap(), field); - for alias in field.aliases() { - assert_eq!(alias.parse::().unwrap(), field); + if field == ContractArtifactField::StandardJson { + let selection: Result = field.try_into(); + assert!(selection + .unwrap_err() + .to_string() + .eq("StandardJson is not supported for ContractOutputSelection")); + } else { + let selection: ContractOutputSelection = field.try_into().unwrap(); + assert_eq!(field, selection); + + let s = field.as_str(); + assert_eq!(s, field.to_string()); + assert_eq!(s.parse::().unwrap(), field); + for alias in field.aliases() { + assert_eq!(alias.parse::().unwrap(), field); + } } } } diff --git a/crates/forge/src/cmd/lint.rs b/crates/forge/src/cmd/lint.rs new file mode 100644 index 0000000000000..9de86faf276ec --- /dev/null +++ b/crates/forge/src/cmd/lint.rs @@ -0,0 +1,120 @@ +use clap::{Parser, ValueHint}; +use eyre::{eyre, Result}; +use forge_lint::{ + linter::Linter, + sol::{SolLint, SolLintError, SolidityLinter}, +}; +use foundry_cli::utils::{FoundryPathExt, LoadConfig}; +use foundry_compilers::{solc::SolcLanguage, utils::SOLC_EXTENSIONS}; +use foundry_config::{filter::expand_globs, impl_figment_convert_basic, lint::Severity}; +use std::path::PathBuf; + +/// CLI arguments for `forge lint`. +#[derive(Clone, Debug, Parser)] +pub struct LintArgs { + /// Path to the file to be checked. Overrides the `ignore` project config. + #[arg(value_hint = ValueHint::FilePath, value_name = "PATH", num_args(1..))] + paths: Vec, + + /// The project's root path. + /// + /// By default root of the Git repository, if in one, + /// or the current working directory. + #[arg(long, value_hint = ValueHint::DirPath, value_name = "PATH")] + root: Option, + + /// Specifies which lints to run based on severity. Overrides the `severity` project config. + /// + /// Supported values: `high`, `med`, `low`, `info`, `gas`. + #[arg(long, value_name = "SEVERITY", num_args(1..))] + severity: Option>, + + /// Specifies which lints to run based on their ID (e.g., "incorrect-shift"). Overrides the + /// `exclude_lints` project config. + #[arg(long = "only-lint", value_name = "LINT_ID", num_args(1..))] + lint: Option>, + + /// Activates the linter's JSON formatter (rustc-compatible). + #[arg(long)] + json: bool, +} + +impl_figment_convert_basic!(LintArgs); + +impl LintArgs { + pub fn run(self) -> Result<()> { + let config = self.load_config()?; + let project = config.project()?; + let path_config = config.project_paths(); + + // Expand ignore globs and canonicalize from the get go + let ignored = expand_globs(&config.root, config.lint.ignore.iter())? + .iter() + .flat_map(foundry_common::fs::canonicalize_path) + .collect::>(); + + let cwd = std::env::current_dir()?; + let input = match &self.paths[..] { + [] => { + // Retrieve the project paths, and filter out the ignored ones. + let project_paths = config + .project_paths::() + .input_files_iter() + .filter(|p| !(ignored.contains(p) || ignored.contains(&cwd.join(p)))) + .collect(); + project_paths + } + paths => { + // Override default excluded paths and only lint the input files. + let mut inputs = Vec::with_capacity(paths.len()); + for path in paths { + if path.is_dir() { + inputs + .extend(foundry_compilers::utils::source_files(path, SOLC_EXTENSIONS)); + } else if path.is_sol() { + inputs.push(path.to_path_buf()); + } else { + warn!("Cannot process path {}", path.display()); + } + } + inputs + } + }; + + if input.is_empty() { + sh_println!("Nothing to lint")?; + return Ok(()); + } + + let parse_lints = |lints: &[String]| -> Result, SolLintError> { + lints.iter().map(|s| SolLint::try_from(s.as_str())).collect() + }; + + // Override default lint config with user-defined lints + let (include, exclude) = match &self.lint { + Some(cli_lints) => (Some(parse_lints(cli_lints)?), None), + None => (None, Some(parse_lints(&config.lint.exclude_lints)?)), + }; + + // Override default severity config with user-defined severity + let severity = match self.severity { + Some(target) => target, + None => config.lint.severity, + }; + + if project.compiler.solc.is_none() { + return Err(eyre!("Linting not supported for this language")); + } + + let linter = SolidityLinter::new(path_config) + .with_json_emitter(self.json) + .with_description(true) + .with_lints(include) + .without_lints(exclude) + .with_severity(if severity.is_empty() { None } else { Some(severity) }); + + linter.lint(&input); + + Ok(()) + } +} diff --git a/crates/forge/src/cmd/mod.rs b/crates/forge/src/cmd/mod.rs index d633564f69cf4..0a0945bab99e9 100644 --- a/crates/forge/src/cmd/mod.rs +++ b/crates/forge/src/cmd/mod.rs @@ -23,6 +23,7 @@ pub mod generate; pub mod init; pub mod inspect; pub mod install; +pub mod lint; pub mod remappings; pub mod remove; pub mod selectors; diff --git a/crates/forge/src/cmd/selectors.rs b/crates/forge/src/cmd/selectors.rs index 5e858c9da9c58..e6ecdcbac7962 100644 --- a/crates/forge/src/cmd/selectors.rs +++ b/crates/forge/src/cmd/selectors.rs @@ -11,7 +11,6 @@ use foundry_common::{ selectors::{import_selectors, SelectorImportData}, }; use foundry_compilers::{artifacts::output_selection::ContractOutputSelection, info::ContractInfo}; -use foundry_config::Config; use std::fs::canonicalize; /// CLI arguments for `forge selectors`. @@ -95,7 +94,7 @@ impl SelectorsSubcommands { // compile the project to get the artifacts/abis let project = build_args.project()?; let outcome = ProjectCompiler::new().quiet(true).compile(&project)?; - cache_local_signatures(&outcome, &Config::foundry_cache_dir().unwrap())? + cache_local_signatures(&outcome)?; } Self::Upload { contract, all, project_paths } => { let build_args = BuildOpts { diff --git a/crates/forge/src/cmd/test/mod.rs b/crates/forge/src/cmd/test/mod.rs index a1f3e64bb574f..9e8a4b2d26643 100644 --- a/crates/forge/src/cmd/test/mod.rs +++ b/crates/forge/src/cmd/test/mod.rs @@ -16,6 +16,7 @@ use alloy_primitives::U256; use chrono::Utc; use clap::{Parser, ValueHint}; use eyre::{bail, Context, OptionExt, Result}; +use foundry_block_explorers::EtherscanApiVersion; use foundry_cli::{ opts::{BuildOpts, GlobalArgs}, utils::{self, LoadConfig}, @@ -146,6 +147,10 @@ pub struct TestArgs { #[arg(long, env = "ETHERSCAN_API_KEY", value_name = "KEY")] etherscan_api_key: Option, + /// The Etherscan API version. + #[arg(long, env = "ETHERSCAN_API_VERSION", value_name = "VERSION")] + etherscan_api_version: Option, + /// List tests instead of running them. #[arg(long, short, conflicts_with_all = ["show_progress", "decode_internal", "summary"], help_heading = "Display options")] list: bool, @@ -873,6 +878,10 @@ impl Provider for TestArgs { dict.insert("etherscan_api_key".to_string(), etherscan_api_key.to_string().into()); } + if let Some(api_version) = &self.etherscan_api_version { + dict.insert("etherscan_api_version".to_string(), api_version.to_string().into()); + } + if self.show_progress { dict.insert("show_progress".to_string(), true.into()); } diff --git a/crates/forge/src/cmd/watch.rs b/crates/forge/src/cmd/watch.rs index 077604d2d8000..3378d9b148cb0 100644 --- a/crates/forge/src/cmd/watch.rs +++ b/crates/forge/src/cmd/watch.rs @@ -24,7 +24,10 @@ use watchexec::{ paths::summarise_events_to_env, Watchexec, }; -use watchexec_events::{Event, Priority, ProcessEnd}; +use watchexec_events::{ + filekind::{AccessKind, FileEventKind}, + Event, Priority, ProcessEnd, Tag, +}; use watchexec_signals::Signal; use yansi::{Color, Paint}; @@ -179,6 +182,35 @@ impl WatchArgs { return action; } + if cfg!(target_os = "linux") { + // Reading a file now triggers `Access(Open)` events on Linux due to: + // https://github.com/notify-rs/notify/pull/612 + // This causes an infinite rebuild loop: the build reads a file, + // which triggers a notification, which restarts the build, and so on. + // To prevent this, we ignore `Access(Open)` events during event processing. + let mut has_file_events = false; + let mut has_synthetic_events = false; + 'outer: for e in action.events.iter() { + if e.is_empty() { + has_synthetic_events = true; + break; + } else { + for tag in &e.tags { + if let Tag::FileEventKind(kind) = tag { + if !matches!(kind, FileEventKind::Access(AccessKind::Open(_))) { + has_file_events = true; + break 'outer; + } + } + } + } + } + if !has_file_events && !has_synthetic_events { + debug!("no filesystem events (other than Access(Open)) or synthetic events, skip without doing more"); + return action; + } + } + job.run({ let job = job.clone(); move |context| { diff --git a/crates/forge/src/lib.rs b/crates/forge/src/lib.rs index 4c7d135767719..fc2f71466bda4 100644 --- a/crates/forge/src/lib.rs +++ b/crates/forge/src/lib.rs @@ -1,5 +1,6 @@ //! Forge is a fast and flexible Ethereum testing framework. +#![cfg_attr(not(test), warn(unused_crate_dependencies))] #![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] #[macro_use] diff --git a/crates/forge/src/multi_runner.rs b/crates/forge/src/multi_runner.rs index 03e889c8c5544..7b10328d5908e 100644 --- a/crates/forge/src/multi_runner.rs +++ b/crates/forge/src/multi_runner.rs @@ -21,12 +21,12 @@ use foundry_evm::{ fork::CreateFork, inspectors::CheatsConfig, opts::EvmOpts, - revm, traces::{InternalTraceMode, TraceMode}, + Env, }; use foundry_linking::{LinkOutput, Linker}; use rayon::prelude::*; -use revm::primitives::SpecId; +use revm::primitives::hardfork::SpecId; use std::{ borrow::Borrow, collections::BTreeMap, @@ -249,10 +249,11 @@ impl MultiContractRunner { debug!("start executing all tests in contract"); + let executor = self.tcfg.executor(self.known_contracts.clone(), artifact_id, db.clone()); let runner = ContractRunner::new( &identifier, contract, - self.tcfg.executor(self.known_contracts.clone(), artifact_id, db.clone()), + executor, progress, tokio_handle, span, @@ -279,7 +280,7 @@ pub struct TestRunnerConfig { /// EVM configuration. pub evm_opts: EvmOpts, /// EVM environment. - pub env: revm::primitives::Env, + pub env: Env, /// EVM version. pub spec_id: SpecId, /// The address which will be used to deploy the initial contracts and send all transactions. @@ -472,7 +473,7 @@ impl MultiContractRunnerBuilder { self, root: &Path, output: &ProjectCompileOutput, - env: revm::primitives::Env, + env: Env, evm_opts: EvmOpts, ) -> Result { let contracts = output diff --git a/crates/forge/src/opts.rs b/crates/forge/src/opts.rs index 0867589a39c62..a50de6c7be59e 100644 --- a/crates/forge/src/opts.rs +++ b/crates/forge/src/opts.rs @@ -1,7 +1,7 @@ use crate::cmd::{ bind::BindArgs, bind_json, build::BuildArgs, cache::CacheArgs, clone::CloneArgs, compiler::CompilerArgs, config, coverage, create::CreateArgs, doc::DocArgs, eip712, flatten, - fmt::FmtArgs, geiger, generate, init::InitArgs, inspect, install::InstallArgs, + fmt::FmtArgs, geiger, generate, init::InitArgs, inspect, install::InstallArgs, lint::LintArgs, remappings::RemappingArgs, remove::RemoveArgs, selectors::SelectorsSubcommands, snapshot, soldeer, test, tree, update, }; @@ -132,6 +132,10 @@ pub enum ForgeSubcommand { /// Format Solidity source files. Fmt(FmtArgs), + /// Lint Solidity source files + #[command(visible_alias = "l")] + Lint(LintArgs), + /// Get specialized information about a smart contract. #[command(visible_alias = "in")] Inspect(inspect::InspectArgs), diff --git a/crates/forge/src/runner.rs b/crates/forge/src/runner.rs index eb8f87221bfed..5384df363729f 100644 --- a/crates/forge/src/runner.rs +++ b/crates/forge/src/runner.rs @@ -7,7 +7,7 @@ use crate::{ result::{SuiteResult, TestResult, TestSetup}, MultiContractRunner, TestFilter, }; -use alloy_dyn_abi::DynSolValue; +use alloy_dyn_abi::{DynSolValue, JsonAbiExt}; use alloy_json_abi::Function; use alloy_primitives::{address, map::HashMap, Address, Bytes, U256}; use eyre::Result; @@ -31,6 +31,7 @@ use foundry_evm::{ }, traces::{load_contracts, TraceKind, TraceMode}, }; +use itertools::Itertools; use proptest::test_runner::{ FailurePersistence, FileFailurePersistence, RngAlgorithm, TestError, TestRng, TestRunner, }; @@ -512,6 +513,7 @@ impl<'a> FunctionRunner<'a> { match kind { TestFunctionKind::UnitTest { .. } => self.run_unit_test(func), TestFunctionKind::FuzzTest { .. } => self.run_fuzz_test(func), + TestFunctionKind::TableTest => self.run_table_test(func), TestFunctionKind::InvariantTest => { let test_bytecode = &self.cr.contract.bytecode; self.run_invariant_test( @@ -566,6 +568,116 @@ impl<'a> FunctionRunner<'a> { self.result } + /// Runs a table test. + /// The parameters dataset (table) is created from defined parameter fixtures, therefore each + /// test table parameter should have the same number of fixtures defined. + /// E.g. for table test + /// - `table_test(uint256 amount, bool swap)` fixtures are defined as + /// - `uint256[] public fixtureAmount = [2, 5]` + /// - `bool[] public fixtureSwap = [true, false]` The `table_test` is then called with the pair + /// of args `(2, true)` and `(5, false)`. + fn run_table_test(mut self, func: &Function) -> TestResult { + // Prepare unit test execution. + if self.prepare_test(func).is_err() { + return self.result; + } + + // Extract and validate fixtures for the first table test parameter. + let Some(first_param) = func.inputs.first() else { + self.result.single_fail(Some("Table test should have at least one parameter".into())); + return self.result; + }; + + let Some(first_param_fixtures) = + &self.setup.fuzz_fixtures.param_fixtures(first_param.name()) + else { + self.result.single_fail(Some("Table test should have fixtures defined".into())); + return self.result; + }; + + if first_param_fixtures.is_empty() { + self.result.single_fail(Some("Table test should have at least one fixture".into())); + return self.result; + } + + let fixtures_len = first_param_fixtures.len(); + let mut table_fixtures = vec![&first_param_fixtures[..]]; + + // Collect fixtures for remaining parameters. + for param in &func.inputs[1..] { + let param_name = param.name(); + let Some(fixtures) = &self.setup.fuzz_fixtures.param_fixtures(param.name()) else { + self.result.single_fail(Some(format!("No fixture defined for param {param_name}"))); + return self.result; + }; + + if fixtures.len() != fixtures_len { + self.result.single_fail(Some(format!( + "{} fixtures defined for {param_name} (expected {})", + fixtures.len(), + fixtures_len + ))); + return self.result; + } + + table_fixtures.push(&fixtures[..]); + } + + let progress = + start_fuzz_progress(self.cr.progress, self.cr.name, &func.name, fixtures_len as u32); + + for i in 0..fixtures_len { + // Increment progress bar. + if let Some(progress) = progress.as_ref() { + progress.inc(1); + } + + let args = table_fixtures.iter().map(|row| row[i].clone()).collect_vec(); + let (mut raw_call_result, reason) = match self.executor.call( + self.sender, + self.address, + func, + &args, + U256::ZERO, + Some(self.revert_decoder()), + ) { + Ok(res) => (res.raw, None), + Err(EvmError::Execution(err)) => (err.raw, Some(err.reason)), + Err(EvmError::Skip(reason)) => { + self.result.single_skip(reason); + return self.result; + } + Err(err) => { + self.result.single_fail(Some(err.to_string())); + return self.result; + } + }; + + let is_success = + self.executor.is_raw_call_mut_success(self.address, &mut raw_call_result, false); + // Record counterexample if test fails. + if !is_success { + self.result.counterexample = + Some(CounterExample::Single(BaseCounterExample::from_fuzz_call( + Bytes::from(func.abi_encode_input(&args).unwrap()), + args, + raw_call_result.traces.clone(), + ))); + self.result.single_result(false, reason, raw_call_result); + return self.result; + } + + // If it's the last iteration and all other runs succeeded, then use last call result + // for logs and traces. + if i == fixtures_len - 1 { + self.result.single_result(true, None, raw_call_result); + return self.result; + } + } + + self.result + } + fn run_invariant_test( mut self, func: &Function, @@ -826,14 +938,10 @@ impl<'a> FunctionRunner<'a> { if self.cr.contract.abi.functions().filter(|func| func.name.is_before_test_setup()).count() == 1 { - for calldata in self - .executor - .call_sol_default( - address, - &ITest::beforeTestSetupCall { testSelector: func.selector() }, - ) - .beforeTestCalldata - { + for calldata in self.executor.call_sol_default( + address, + &ITest::beforeTestSetupCall { testSelector: func.selector() }, + ) { // Apply before test configured calldata. match self.executor.to_mut().transact_raw( self.tcfg.sender, diff --git a/crates/forge/tests/cli/cmd.rs b/crates/forge/tests/cli/cmd.rs index f845981bda592..5ab6f22ad8a26 100644 --- a/crates/forge/tests/cli/cmd.rs +++ b/crates/forge/tests/cli/cmd.rs @@ -7,7 +7,7 @@ use foundry_config::{ }; use foundry_test_utils::{ foundry_compilers::PathStyle, - rpc::next_mainnet_etherscan_api_key, + rpc::next_etherscan_api_key, snapbox::IntoData, util::{pretty_err, read_string, OutputExt, TestCommand}, }; @@ -36,7 +36,7 @@ Options: -j, --threads Number of threads to use. Specifying 0 defaults to the number of logical cores - [aliases: jobs] + [aliases: --jobs] -V, --version Print version @@ -619,7 +619,7 @@ forgetest!(can_clone, |prj, cmd| { cmd.args([ "clone", "--etherscan-api-key", - next_mainnet_etherscan_api_key().as_str(), + next_etherscan_api_key().as_str(), "0x044b75f554b886A065b9567891e45c79542d7357", ]) .arg(prj.root()) @@ -648,7 +648,7 @@ forgetest!(can_clone_quiet, |prj, cmd| { cmd.args([ "clone", "--etherscan-api-key", - next_mainnet_etherscan_api_key().as_str(), + next_etherscan_api_key().as_str(), "--quiet", "0xDb53f47aC61FE54F456A4eb3E09832D08Dd7BEec", ]) @@ -666,7 +666,7 @@ forgetest!(can_clone_no_remappings_txt, |prj, cmd| { cmd.args([ "clone", "--etherscan-api-key", - next_mainnet_etherscan_api_key().as_str(), + next_etherscan_api_key().as_str(), "--no-remappings-txt", "0x33e690aEa97E4Ef25F0d140F1bf044d663091DAf", ]) @@ -701,7 +701,7 @@ forgetest!(can_clone_keep_directory_structure, |prj, cmd| { .args([ "clone", "--etherscan-api-key", - next_mainnet_etherscan_api_key().as_str(), + next_etherscan_api_key().as_str(), "--keep-directory-structure", "0x33e690aEa97E4Ef25F0d140F1bf044d663091DAf", ]) @@ -739,7 +739,7 @@ forgetest!(can_clone_with_node_modules, |prj, cmd| { cmd.args([ "clone", "--etherscan-api-key", - next_mainnet_etherscan_api_key().as_str(), + next_etherscan_api_key().as_str(), "0xA3E217869460bEf59A1CfD0637e2875F9331e823", ]) .arg(prj.root()) @@ -1208,6 +1208,11 @@ Compiler run successful! forgetest!(can_build_after_failure, |prj, cmd| { prj.insert_ds_test(); + // Disable linting during build to avoid linting output interfering with test assertions + prj.update_config(|config| { + config.lint.lint_on_build = false; + }); + prj.add_source( "ATest.t.sol", r#" @@ -3569,6 +3574,42 @@ forgetest!(test_inspect_contract_with_same_name, |prj, cmd| { ╰-------------------------------+----------╯ +"#]]); +}); + +// +forgetest!(inspect_multiple_contracts_with_different_paths, |prj, cmd| { + prj.add_source( + "Source.sol", + r#" + contract Source { + function foo() public {} + } + "#, + ) + .unwrap(); + + prj.add_source( + "another/Source.sol", + r#" + contract Source { + function bar() public {} + } + "#, + ) + .unwrap(); + + cmd.args(["inspect", "src/another/Source.sol:Source", "methodIdentifiers"]) + .assert_success() + .stdout_eq(str![[r#" + +╭--------+------------╮ +| Method | Identifier | ++=====================+ +| bar() | febb0f7e | +╰--------+------------╯ + + "#]]); }); @@ -3596,6 +3637,53 @@ forgetest!(inspect_custom_counter_method_identifiers, |prj, cmd| { ╰----------------------------+------------╯ +"#]]); +}); + +forgetest_init!(can_inspect_standard_json, |prj, cmd| { + cmd.args(["inspect", "src/Counter.sol:Counter", "standard-json"]).assert_success().stdout_eq(str![[r#" +{ + "language": "Solidity", + "sources": { + "src/Counter.sol": { + "content": "// SPDX-License-Identifier: UNLICENSED/npragma solidity ^0.8.13;/n/ncontract Counter {/n uint256 public number;/n/n function setNumber(uint256 newNumber) public {/n number = newNumber;/n }/n/n function increment() public {/n number++;/n }/n}/n" + } + }, + "settings": { + "remappings": [ + "forge-std/=lib/forge-std/src/" + ], + "optimizer": { + "enabled": false, + "runs": 200 + }, + "metadata": { + "useLiteralContent": false, + "bytecodeHash": "ipfs", + "appendCBOR": true + }, + "outputSelection": { + "*": { + "*": [ + "abi", + "evm.bytecode.object", + "evm.bytecode.sourceMap", + "evm.bytecode.linkReferences", + "evm.deployedBytecode.object", + "evm.deployedBytecode.sourceMap", + "evm.deployedBytecode.linkReferences", + "evm.deployedBytecode.immutableReferences", + "evm.methodIdentifiers", + "metadata" + ] + } + }, + "evmVersion": "prague", + "viaIR": false, + "libraries": {} + } +} + "#]]); }); @@ -3786,3 +3874,22 @@ Generating bindings for 1 contracts Bindings have been generated to [..]"# ]]); }); + +// forge bind e2e +forgetest_init!(can_bind_e2e, |prj, cmd| { + cmd.args(["bind"]).assert_success().stdout_eq(str![[r#"No files changed, compilation skipped +Generating bindings for 2 contracts +Bindings have been generated to [..]"#]]); + + let bindings_path = prj.root().join("out/bindings"); + + assert!(bindings_path.exists(), "Bindings directory should exist"); + let out = Command::new("cargo") + .arg("build") + .current_dir(&bindings_path) + .output() + .expect("Failed to run cargo build"); + // RUn `cargo build` + + assert!(out.status.success(), "Cargo build should succeed"); +}); diff --git a/crates/forge/tests/cli/compiler.rs b/crates/forge/tests/cli/compiler.rs index 0b58221f2a1d0..f58d5c3ef1511 100644 --- a/crates/forge/tests/cli/compiler.rs +++ b/crates/forge/tests/cli/compiler.rs @@ -18,14 +18,14 @@ contract ContractB {} const CONTRACT_C: &str = r#" // SPDX-license-identifier: MIT -pragma solidity 0.8.27; +pragma solidity 0.8.30; contract ContractC {} "#; const CONTRACT_D: &str = r#" // SPDX-license-identifier: MIT -pragma solidity 0.8.27; +pragma solidity 0.8.30; contract ContractD {} "#; @@ -111,7 +111,7 @@ forgetest!(can_list_resolved_compiler_versions_verbose, |prj, cmd| { cmd.args(["compiler", "resolve", "-v"]).assert_success().stdout_eq(str![[r#" Solidity: -0.8.27: +0.8.30: ├── src/ContractC.sol └── src/ContractD.sol @@ -128,7 +128,7 @@ forgetest!(can_list_resolved_compiler_versions_verbose_json, |prj, cmd| { { "Solidity": [ { - "version": "0.8.27", + "version": "0.8.30", "paths": [ "src/ContractC.sol", "src/ContractD.sol" @@ -153,7 +153,7 @@ forgetest!(can_list_resolved_multiple_compiler_versions, |prj, cmd| { Solidity: - 0.8.4 - 0.8.11 -- 0.8.27 +- 0.8.30 Vyper: - 0.4.0 @@ -198,7 +198,7 @@ forgetest!(can_list_resolved_multiple_compiler_versions_skipped_json, |prj, cmd| { "Solidity": [ { - "version": "0.8.27", + "version": "0.8.30", "paths": [ "src/ContractD.sol" ] @@ -236,7 +236,7 @@ Solidity: 0.8.11 (<= london): └── src/ContractB.sol -0.8.27 (<= [..]): +0.8.30 (<= prague): ├── src/ContractC.sol └── src/ContractD.sol @@ -277,7 +277,7 @@ forgetest!(can_list_resolved_multiple_compiler_versions_verbose_json, |prj, cmd| ] }, { - "version": "0.8.27", + "version": "0.8.30", "evm_version": "[..]", "paths": [ "src/ContractC.sol", diff --git a/crates/forge/tests/cli/config.rs b/crates/forge/tests/cli/config.rs index 799365fcd1b03..3cea4fca0d31a 100644 --- a/crates/forge/tests/cli/config.rs +++ b/crates/forge/tests/cli/config.rs @@ -119,6 +119,7 @@ forgetest!(can_extract_config_values, |prj, cmd| { eth_rpc_timeout: None, eth_rpc_headers: None, etherscan_api_key: None, + etherscan_api_version: None, etherscan: Default::default(), verbosity: 4, remappings: vec![Remapping::from_str("forge-std/=lib/forge-std/").unwrap().into()], @@ -147,6 +148,7 @@ forgetest!(can_extract_config_values, |prj, cmd| { build_info: false, build_info_path: None, fmt: Default::default(), + lint: Default::default(), doc: Default::default(), bind_json: Default::default(), fs_permissions: Default::default(), @@ -163,12 +165,10 @@ forgetest!(can_extract_config_values, |prj, cmd| { assertions_revert: true, legacy_assertions: false, extra_args: vec![], - eof_version: None, odyssey: false, transaction_timeout: 120, additional_compiler_profiles: Default::default(), compilation_restrictions: Default::default(), - eof: false, script_execution_protection: true, _non_exhaustive: (), }; @@ -983,7 +983,7 @@ allow_paths = [] include_paths = [] skip = [] force = false -evm_version = "cancun" +evm_version = "prague" gas_reports = ["*"] gas_reports_ignore = [] gas_reports_include_tests = false @@ -1039,7 +1039,6 @@ assertions_revert = true legacy_assertions = false odyssey = false transaction_timeout = 120 -eof = false additional_compiler_profiles = [] compilation_restrictions = [] script_execution_protection = true @@ -1068,6 +1067,12 @@ ignore = [] contract_new_lines = false sort_imports = false +[lint] +severity = [] +exclude_lints = [] +ignore = [] +lint_on_build = true + [doc] out = "docs" title = "" @@ -1102,7 +1107,7 @@ shrink_run_limit = 5000 max_assume_rejects = 65536 gas_report_samples = 256 failure_persist_dir = "cache/invariant" -show_metrics = false +show_metrics = true show_solidity = false [labels] @@ -1142,7 +1147,7 @@ exclude = [] "include_paths": [], "skip": [], "force": false, - "evm_version": "cancun", + "evm_version": "prague", "gas_reports": [ "*" ], @@ -1161,6 +1166,7 @@ exclude = [] "eth_rpc_timeout": null, "eth_rpc_headers": null, "etherscan_api_key": null, + "etherscan_api_version": null, "ignored_error_codes": [ "license", "code-size", @@ -1208,7 +1214,7 @@ exclude = [] "max_assume_rejects": 65536, "gas_report_samples": 256, "failure_persist_dir": "cache/invariant", - "show_metrics": false, + "show_metrics": true, "timeout": null, "show_solidity": false }, @@ -1267,6 +1273,12 @@ exclude = [] "contract_new_lines": false, "sort_imports": false }, + "lint": { + "severity": [], + "exclude_lints": [], + "ignore": [], + "lint_on_build": true + }, "doc": { "out": "docs", "title": "", @@ -1298,7 +1310,6 @@ exclude = [] "legacy_assertions": false, "odyssey": false, "transaction_timeout": 120, - "eof": false, "additional_compiler_profiles": [], "compilation_restrictions": [], "script_execution_protection": true @@ -1703,7 +1714,7 @@ contract Counter { let v1_profile = SettingsOverrides { name: "v1".to_string(), via_ir: Some(true), - evm_version: Some(EvmVersion::Cancun), + evm_version: Some(EvmVersion::Prague), optimizer: None, optimizer_runs: Some(44444444), bytecode_hash: None, @@ -1789,19 +1800,19 @@ contract Counter { let (via_ir, evm_version, enabled, runs) = artifact_settings("Counter.sol/Counter.json"); assert_eq!(None, via_ir); - assert_eq!("\"cancun\"", evm_version.unwrap().to_string()); + assert_eq!("\"prague\"", evm_version.unwrap().to_string()); assert_eq!("false", enabled.unwrap().to_string()); assert_eq!("200", runs.unwrap().to_string()); let (via_ir, evm_version, enabled, runs) = artifact_settings("v1/Counter.sol/Counter.json"); assert_eq!("true", via_ir.unwrap().to_string()); - assert_eq!("\"cancun\"", evm_version.unwrap().to_string()); + assert_eq!("\"prague\"", evm_version.unwrap().to_string()); assert_eq!("true", enabled.unwrap().to_string()); assert_eq!("44444444", runs.unwrap().to_string()); let (via_ir, evm_version, enabled, runs) = artifact_settings("v2/Counter.sol/Counter.json"); assert_eq!("true", via_ir.unwrap().to_string()); - assert_eq!("\"cancun\"", evm_version.unwrap().to_string()); + assert_eq!("\"prague\"", evm_version.unwrap().to_string()); assert_eq!("true", enabled.unwrap().to_string()); assert_eq!("111", runs.unwrap().to_string()); diff --git a/crates/forge/tests/cli/eip712.rs b/crates/forge/tests/cli/eip712.rs index 9ec944631d9db..d7df5fa333579 100644 --- a/crates/forge/tests/cli/eip712.rs +++ b/crates/forge/tests/cli/eip712.rs @@ -1,3 +1,5 @@ +use foundry_config::fs_permissions::PathPermission; + forgetest!(test_eip712, |prj, cmd| { let path = prj .add_source( @@ -55,28 +57,711 @@ library Structs2 { cmd.forge_fuse().args(["eip712", path.to_string_lossy().as_ref()]).assert_success().stdout_eq( str![[r#" -[COMPILING_FILES] with [SOLC_VERSION] -[SOLC_VERSION] [ELAPSED] -No files changed, compilation skipped -Foo(Bar bar)Art(uint256 id)Bar(Art art) +Structs.sol > Structs > Foo: + - type: Foo(Bar bar)Art(uint256 id)Bar(Art art) + - hash: 0x6d9b732373bd999fde4072274c752e03f7437067dd75521eb406d8edf1d30f7d -Bar(Art art)Art(uint256 id) +Structs.sol > Structs > Bar: + - type: Bar(Art art)Art(uint256 id) + - hash: 0xadeb03f4f98fb57c05c9a79d8dd2348220e9bd9fd332ec2fbd92479e5695a596 -Art(uint256 id) +Structs.sol > Structs > Art: + - type: Art(uint256 id) + - hash: 0xbfeb9da97f9dbc2403e9d5ec3853f36414cae141d772601f24e0097d159d302b -Complex(Foo foo2,Foo_1[] foos,Rec[][] recs)Art(uint256 id)Bar(Art art)Foo(uint256 id)Foo_1(Bar bar)Rec(Rec[] rec) +Structs.sol > Structs > Complex: + - type: Complex(Foo foo2,Foo_1[] foos,Rec[][] recs)Art(uint256 id)Bar(Art art)Foo(uint256 id)Foo_1(Bar bar)Rec(Rec[] rec) + - hash: 0xfb0a234a82efcade7c031ebb4c58afd7f5f242ca67ed06f4050c60044dcee425 -Rec(Rec[] rec) +Structs.sol > Structs > Rec: + - type: Rec(Rec[] rec) + - hash: 0x5f060eb740f5aee93a910587a100458c724479d189f6dd67ac39048bf312102e -Foo(uint256 id) +Structs.sol > Structs2 > Foo: + - type: Foo(uint256 id) + - hash: 0xb93d8bb2877cd5cc51979d9fe85339ab570714a6fd974225e2a763851092497e -Rec(Bar[] bar)Bar(Rec rec) +Structs.sol > Structs2 > Rec: + - type: Rec(Bar[] bar)Bar(Rec rec) + - hash: 0xe9dded72c72648f27772620cb4e10b773ce31a3ea26ef980c0b39d1834242cda -Bar(Rec rec)Rec(Bar[] bar) +Structs.sol > Structs2 > Bar: + - type: Bar(Rec rec)Rec(Bar[] bar) + - hash: 0x164eba932ecde04ec75feba228664d08f29c88d6a67e531757e023e6063c3b2c -FooBar(Foo[] foos,Bar[] bars,Foo_1 foo,Bar_1 bar,Rec[] recs,Rec_1 rec)Art(uint256 id)Bar(Rec rec)Bar_1(Art art)Foo(uint256 id)Foo_1(Bar_1 bar)Rec(Bar[] bar)Rec_1(Rec_1[] rec) +Structs.sol > Structs2 > FooBar: + - type: FooBar(Foo[] foos,Bar[] bars,Foo_1 foo,Bar_1 bar,Rec[] recs,Rec_1 rec)Art(uint256 id)Bar(Rec rec)Bar_1(Art art)Foo(uint256 id)Foo_1(Bar_1 bar)Rec(Bar[] bar)Rec_1(Rec_1[] rec) + - hash: 0xce88f333fe5b5d4901ceb2569922ffe741cda3afc383a63d34ed2c3d565e42d8 "#]], ); + + cmd.forge_fuse().args(["eip712", path.to_string_lossy().as_ref(), "--json"]).assert_success().stdout_eq( + str![[r#" +[ + { + "path": "Structs.sol > Structs > Foo", + "type": "Foo(Bar bar)Art(uint256 id)Bar(Art art)", + "hash": "0x6d9b732373bd999fde4072274c752e03f7437067dd75521eb406d8edf1d30f7d" + }, + { + "path": "Structs.sol > Structs > Bar", + "type": "Bar(Art art)Art(uint256 id)", + "hash": "0xadeb03f4f98fb57c05c9a79d8dd2348220e9bd9fd332ec2fbd92479e5695a596" + }, + { + "path": "Structs.sol > Structs > Art", + "type": "Art(uint256 id)", + "hash": "0xbfeb9da97f9dbc2403e9d5ec3853f36414cae141d772601f24e0097d159d302b" + }, + { + "path": "Structs.sol > Structs > Complex", + "type": "Complex(Foo foo2,Foo_1[] foos,Rec[][] recs)Art(uint256 id)Bar(Art art)Foo(uint256 id)Foo_1(Bar bar)Rec(Rec[] rec)", + "hash": "0xfb0a234a82efcade7c031ebb4c58afd7f5f242ca67ed06f4050c60044dcee425" + }, + { + "path": "Structs.sol > Structs > Rec", + "type": "Rec(Rec[] rec)", + "hash": "0x5f060eb740f5aee93a910587a100458c724479d189f6dd67ac39048bf312102e" + }, + { + "path": "Structs.sol > Structs2 > Foo", + "type": "Foo(uint256 id)", + "hash": "0xb93d8bb2877cd5cc51979d9fe85339ab570714a6fd974225e2a763851092497e" + }, + { + "path": "Structs.sol > Structs2 > Rec", + "type": "Rec(Bar[] bar)Bar(Rec rec)", + "hash": "0xe9dded72c72648f27772620cb4e10b773ce31a3ea26ef980c0b39d1834242cda" + }, + { + "path": "Structs.sol > Structs2 > Bar", + "type": "Bar(Rec rec)Rec(Bar[] bar)", + "hash": "0x164eba932ecde04ec75feba228664d08f29c88d6a67e531757e023e6063c3b2c" + }, + { + "path": "Structs.sol > Structs2 > FooBar", + "type": "FooBar(Foo[] foos,Bar[] bars,Foo_1 foo,Bar_1 bar,Rec[] recs,Rec_1 rec)Art(uint256 id)Bar(Rec rec)Bar_1(Art art)Foo(uint256 id)Foo_1(Bar_1 bar)Rec(Bar[] bar)Rec_1(Rec_1[] rec)", + "hash": "0xce88f333fe5b5d4901ceb2569922ffe741cda3afc383a63d34ed2c3d565e42d8" + } +] + +"#]], + ); +}); + +forgetest!(test_eip712_free_standing_structs, |prj, cmd| { + let path = prj + .add_source( + "FreeStandingStructs.sol", + r#" +// free-standing struct (outside a contract and lib) +struct FreeStanding { + uint256 id; + string name; +} + +contract InsideContract { + struct ContractStruct { + uint256 value; + } +} + +library InsideLibrary { + struct LibraryStruct { + bytes32 hash; + } +} +"#, + ) + .unwrap(); + + cmd.forge_fuse().args(["eip712", path.to_string_lossy().as_ref()]).assert_success().stdout_eq( + str![[r#" +FreeStanding: + - type: FreeStanding(uint256 id,string name) + - hash: 0xfb3c934b2382873277133498bde6eb3914ab323e3bef8b373ebcd423969bf1a2 + +FreeStandingStructs.sol > InsideContract > ContractStruct: + - type: ContractStruct(uint256 value) + - hash: 0xfb63263e7cf823ff50385a991cb1bd5c1ff46b58011119984d52f8736331e3fe + +FreeStandingStructs.sol > InsideLibrary > LibraryStruct: + - type: LibraryStruct(bytes32 hash) + - hash: 0x81d6d25f4d37549244d76a68f23ecdcbf3ae81e5a361ed6c492b6a2e126a2843 + + +"#]], + ); +}); + +forgetest!(test_eip712_cheatcode_simple, |prj, cmd| { + prj.insert_ds_test(); + prj.insert_vm(); + prj.insert_console(); + + prj.add_source( + "Eip712", + r#" +contract Eip712Structs { + struct EIP712Domain { + string name; + string version; + uint256 chainId; + address verifyingContract; + } +} + "#, + ) + .unwrap(); + + prj.add_source("Eip712Cheat.sol", r#" +import "./test.sol"; +import "./Vm.sol"; +import "./console.sol"; + +string constant CANONICAL = "EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"; + +contract Eip712Test is DSTest { + Vm constant vm = Vm(HEVM_ADDRESS); + + function testEip712HashType() public { + bytes32 canonicalHash = keccak256(bytes(CANONICAL)); + console.logBytes32(canonicalHash); + + // Can figure out the canonical type from a messy string representation of the type, + // with an invalid order and extra whitespaces + bytes32 fromTypeDef = vm.eip712HashType( + "EIP712Domain(string name, string version, uint256 chainId, address verifyingContract)" + ); + assertEq(fromTypeDef, canonicalHash); + + // Can figure out the canonical type from the previously generated bindings + bytes32 fromTypeName = vm.eip712HashType("EIP712Domain"); + assertEq(fromTypeName, canonicalHash); + } +} +"#, + ) + .unwrap(); + + cmd.forge_fuse().args(["bind-json"]).assert_success(); + + let bindings = prj.root().join("utils").join("JsonBindings.sol"); + assert!(bindings.exists(), "'JsonBindings.sol' was not generated at {bindings:?}"); + + prj.update_config(|config| config.fs_permissions.add(PathPermission::read(bindings))); + cmd.forge_fuse().args(["test", "--mc", "Eip712Test", "-vv"]).assert_success().stdout_eq(str![ + [r#" +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +Compiler run successful! + +Ran 1 test for src/Eip712Cheat.sol:Eip712Test +[PASS] testEip712HashType() ([GAS]) +Logs: + 0x8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f + +Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED] + +Ran 1 test suite [ELAPSED]: 1 tests passed, 0 failed, 0 skipped (1 total tests) + +"#] + ]); +}); + +forgetest!(test_eip712_cheatcode_nested, |prj, cmd| { + prj.insert_ds_test(); + prj.insert_vm(); + prj.insert_console(); + + prj.add_source( + "Eip712", + r#" +contract Eip712Structs { + struct Transaction { + Person from; + Person to; + Asset tx; + } + struct Person { + address wallet; + string name; + } + struct Asset { + address token; + uint256 amount; + } +} + "#, + ) + .unwrap(); + + prj.add_source("Eip712Cheat.sol", r#" +import "./test.sol"; +import "./Vm.sol"; + +string constant CANONICAL = "Transaction(Person from,Person to,Asset tx)Asset(address token,uint256 amount)Person(address wallet,string name)"; + +contract Eip712Test is DSTest { + Vm constant vm = Vm(HEVM_ADDRESS); + + function testEip712HashType_byDefinition() public { + bytes32 canonicalHash = keccak256(bytes(CANONICAL)); + + // Can figure out the canonical type from a messy string representation of the type, + // with an invalid order and extra whitespaces + bytes32 fromTypeDef = vm.eip712HashType( + "Person(address wallet, string name) Asset(address token, uint256 amount) Transaction(Person from, Person to, Asset tx)" + ); + assertEq(fromTypeDef, canonicalHash); + } + + function testEip712HashType_byTypeName() public { + bytes32 canonicalHash = keccak256(bytes(CANONICAL)); + + // Can figure out the canonical type from the previously generated bindings + bytes32 fromTypeName = vm.eip712HashType("Transaction"); + assertEq(fromTypeName, canonicalHash); + } + + function testReverts_Eip712HashType_invalidName() public { + // Reverts if the input type is not found in the bindings + vm._expectCheatcodeRevert(); + bytes32 fromTypeName = vm.eip712HashType("InvalidTypeName"); + } + + function testEip712HashType_byCustomPathAndTypeName() public { + bytes32 canonicalHash = keccak256(bytes(CANONICAL)); + + // Can figure out the canonical type from the previously generated bindings + bytes32 fromTypeName = vm.eip712HashType("utils/CustomJsonBindings.sol", "Transaction"); + assertEq(fromTypeName, canonicalHash); + } +} +"#, + ) + .unwrap(); + + // cheatcode by type definition can run without bindings + cmd.forge_fuse() + .args(["test", "--mc", "Eip712Test", "--match-test", "testEip712HashType_byDefinition"]) + .assert_success(); + + let bindings = prj.root().join("utils").join("JsonBindings.sol"); + prj.update_config(|config| config.fs_permissions.add(PathPermission::read(&bindings))); + + // cheatcode by type name fails if bindings haven't been generated + cmd.forge_fuse() + .args(["test", "--mc", "Eip712Test", "--match-test", "testEip712HashType_byTypeName"]) + .assert_failure() + .stdout_eq(str![[r#" +... +Ran 1 test for src/Eip712Cheat.sol:Eip712Test +[FAIL: vm.eip712HashType: failed to read from [..] testEip712HashType_byTypeName() ([GAS]) +Suite result: FAILED. 0 passed; 1 failed; 0 skipped; [ELAPSED] + +Ran 1 test suite [ELAPSED]: 0 tests passed, 1 failed, 0 skipped (1 total tests) + +Failing tests: +Encountered 1 failing test in src/Eip712Cheat.sol:Eip712Test +[FAIL: vm.eip712HashType: failed to read from [..] testEip712HashType_byTypeName() ([GAS]) + +Encountered a total of 1 failing tests, 0 tests succeeded + +"#]]); + + cmd.forge_fuse().args(["bind-json"]).assert_success(); + assert!(bindings.exists(), "'JsonBindings.sol' was not generated at {bindings:?}"); + + // with generated bindings, cheatcode by type name works + cmd.forge_fuse() + .args(["test", "--mc", "Eip712Test", "--match-test", "testEip712HashType_byTypeName"]) + .assert_success(); + + // even with generated bindings, cheatcode by type name fails if name is not present + cmd.forge_fuse() + .args([ + "test", + "--mc", + "Eip712Test", + "--match-test", + "testReverts_Eip712HashType_invalidName", + ]) + .assert_success(); + + let bindings_2 = prj.root().join("utils").join("CustomJsonBindings.sol"); + prj.update_config(|config| { + config.fs_permissions.add(PathPermission::read(&bindings_2)); + }); + + // cheatcode by custom path and type name fails if bindings haven't been generated for that path + cmd.forge_fuse() + .args(["test", "--mc", "Eip712Test", "--match-test", "testEip712HashType_byCustomPathAndTypeName"]) + .assert_failure() + .stdout_eq(str![[r#" +... +Ran 1 test for src/Eip712Cheat.sol:Eip712Test +[FAIL: vm.eip712HashType: failed to read from [..] testEip712HashType_byCustomPathAndTypeName() ([GAS]) +Suite result: FAILED. 0 passed; 1 failed; 0 skipped; [ELAPSED] + +Ran 1 test suite [ELAPSED]: 0 tests passed, 1 failed, 0 skipped (1 total tests) + +Failing tests: +Encountered 1 failing test in src/Eip712Cheat.sol:Eip712Test +[FAIL: vm.eip712HashType: failed to read from [..] testEip712HashType_byCustomPathAndTypeName() ([GAS]) + +Encountered a total of 1 failing tests, 0 tests succeeded + +"#]]); + + cmd.forge_fuse().args(["bind-json", "utils/CustomJsonBindings.sol"]).assert_success(); + assert!(bindings_2.exists(), "'CustomJsonBindings.sol' was not generated at {bindings_2:?}"); + + // with generated bindings, cheatcode by custom path and type name works + cmd.forge_fuse() + .args([ + "test", + "--mc", + "Eip712Test", + "--match-test", + "testEip712HashType_byCustomPathAndTypeName", + ]) + .assert_success(); +}); + +forgetest!(test_eip712_hash_struct_simple, |prj, cmd| { + prj.insert_ds_test(); + prj.insert_vm(); + prj.insert_console(); + + prj.add_source( + "Eip712HashStructDomainTest.sol", + r#" +import "./Vm.sol"; +import "./test.sol"; +import "./console.sol"; + +struct EIP712Domain { + string name; + string version; + uint256 chainId; + address verifyingContract; +} + +string constant _EIP712_DOMAIN_TYPE_DEF = "EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"; +bytes32 constant _EIP712_DOMAIN_TYPE_HASH = keccak256(bytes(_EIP712_DOMAIN_TYPE_DEF)); + +contract Eip712HashStructDomainTest is DSTest { + Vm constant vm = Vm(address(uint160(uint256(keccak256("hevm cheat code"))))); + + function testHashEIP712Domain() public { + EIP712Domain memory domain = EIP712Domain({ + name: "Foo", + version: "Bar", + chainId: 1, + verifyingContract: 0xdEADBEeF00000000000000000000000000000000 + }); + + // simulate user-computed domain hash + bytes memory encodedData = abi.encode( + keccak256(bytes(domain.name)), + keccak256(bytes(domain.version)), + bytes32(domain.chainId), + bytes32(uint256(uint160(domain.verifyingContract))) + ); + bytes32 userStructHash = keccak256(abi.encodePacked(_EIP712_DOMAIN_TYPE_HASH, encodedData)); + + // cheatcode-computed domain hash + bytes32 cheatStructHash = vm.eip712HashStruct(_EIP712_DOMAIN_TYPE_DEF, abi.encode(domain)); + console.log("EIP712Domain struct hash from cheatcode:"); + console.logBytes32(cheatStructHash); + + assertEq(cheatStructHash, userStructHash, "EIP712Domain struct hash mismatch"); + } +} +"#, + ) + .unwrap(); + + cmd.forge_fuse().args(["test", "--mc", "Eip712HashStructDomainTest", "-vvvv"]).assert_success(); +}); + +forgetest!(test_eip712_hash_struct_complex, |prj, cmd| { + prj.insert_ds_test(); + prj.insert_vm(); + prj.insert_console(); + + prj.add_source( + "Eip712Permit.sol", + r#" +struct PermitDetails { + address token; + uint160 amount; + uint48 expiration; + uint48 nonce; +} + +bytes32 constant _PERMIT_DETAILS_TYPEHASH = keccak256( + "PermitDetails(address token,uint160 amount,uint48 expiration,uint48 nonce)" +); + +struct PermitSingle { + PermitDetails details; + address spender; + uint256 sigDeadline; +} + +bytes32 constant _PERMIT_SINGLE_TYPEHASH = keccak256( + "PermitSingle(PermitDetails details,address spender,uint256 sigDeadline)PermitDetails(address token,uint160 amount,uint48 expiration,uint48 nonce)" +); + +// borrowed from https://github.com/Uniswap/permit2/blob/main/src/libraries/PermitHash.sol +library PermitHash { + function hash(PermitSingle memory permitSingle) internal pure returns (bytes32) { + bytes32 permitHash = _hashDetails(permitSingle.details); + return + keccak256(abi.encode(_PERMIT_SINGLE_TYPEHASH, permitHash, permitSingle.spender, permitSingle.sigDeadline)); + } + + function _hashDetails(PermitDetails memory details) internal pure returns (bytes32) { + return keccak256(abi.encode(_PERMIT_DETAILS_TYPEHASH, details)); + } +} +"#, + ) + .unwrap(); + + prj.add_source( + "Eip712Transaction.sol", + r#" +struct Asset { + address token; + uint256 amount; +} + +bytes32 constant _ASSET_TYPEHASH = keccak256( + "Asset(address token,uint256 amount)" +); + +struct Person { + address wallet; + string name; +} + +bytes32 constant _PERSON_TYPEHASH = keccak256( + "Person(address wallet,string name)" +); + +struct Transaction { + Person from; + Person to; + Asset tx; +} + +bytes32 constant _TRANSACTION_TYPEHASH = keccak256( + "Transaction(Person from,Person to,Asset tx)Asset(address token,uint256 amount)Person(address wallet,string name)" +); + + +library TransactionHash { + function hash(Transaction memory t) internal pure returns (bytes32) { + bytes32 fromHash = _hashPerson(t.from); + bytes32 toHash = _hashPerson(t.to); + bytes32 assetHash = _hashAsset(t.tx); + return + keccak256(abi.encode(_TRANSACTION_TYPEHASH, fromHash, toHash, assetHash)); + } + + function _hashPerson(Person memory person) internal pure returns (bytes32) { + return keccak256( + abi.encode(_PERSON_TYPEHASH, person.wallet, keccak256(bytes(person.name))) + ); + + } + + function _hashAsset(Asset memory asset) internal pure returns (bytes32) { + return keccak256(abi.encode(_ASSET_TYPEHASH, asset)); + } +} + "#, + ) + .unwrap(); + + let bindings = prj.root().join("utils").join("JsonBindings.sol"); + prj.update_config(|config| config.fs_permissions.add(PathPermission::read(&bindings))); + cmd.forge_fuse().args(["bind-json"]).assert_success(); + + prj.add_source( + "Eip712HashStructTest.sol", + r#" +import "./Vm.sol"; +import "./test.sol"; +import "./console.sol"; +import "./Eip712Permit.sol"; +import "./Eip712Transaction.sol"; + +contract Eip712HashStructTest is DSTest { + Vm constant vm = Vm(HEVM_ADDRESS); + + function testHashPermitSingle_withTypeName() public { + PermitDetails memory details = PermitDetails({ + token: 0x1111111111111111111111111111111111111111, + amount: 1000 ether, + expiration: 12345, + nonce: 1 + }); + + // user-computed permit (using uniswap hash library) + bytes32 userStructHash = PermitHash._hashDetails(details); + + // cheatcode-computed permit + bytes32 cheatStructHash = vm.eip712HashStruct("PermitDetails", abi.encode(details)); + + assertEq(cheatStructHash, userStructHash, "details struct hash mismatch"); + + PermitSingle memory permit = PermitSingle({ + details: details, + spender: 0x2222222222222222222222222222222222222222, + sigDeadline: 12345 + }); + + // user-computed permit (using uniswap hash library) + userStructHash = PermitHash.hash(permit); + + // cheatcode-computed permit + cheatStructHash = vm.eip712HashStruct("PermitSingle", abi.encode(permit)); + console.log("PermitSingle struct hash from cheatcode:"); + console.logBytes32(cheatStructHash); + + assertEq(cheatStructHash, userStructHash, "permit struct hash mismatch"); + } + + function testHashPermitSingle_withTypeDefinion() public { + PermitDetails memory details = PermitDetails({ + token: 0x1111111111111111111111111111111111111111, + amount: 1000 ether, + expiration: 12345, + nonce: 1 + }); + + // user-computed permit (using uniswap hash library) + bytes32 userStructHash = PermitHash._hashDetails(details); + + // cheatcode-computed permit + bytes32 cheatStructHash = vm.eip712HashStruct("PermitDetails(address token, uint160 amount, uint48 expiration, uint48 nonce)", abi.encode(details)); + + assertEq(cheatStructHash, userStructHash, "details struct hash mismatch"); + + PermitSingle memory permit = PermitSingle({ + details: details, + spender: 0x2222222222222222222222222222222222222222, + sigDeadline: 12345 + }); + + // user-computed permit (using uniswap hash library) + userStructHash = PermitHash.hash(permit); + + // cheatcode-computed permit (previously encoding) + cheatStructHash = vm.eip712HashStruct("PermitDetails(address token, uint160 amount, uint48 expiration, uint48 nonce) PermitSingle(PermitDetails details,address spender,uint256 sigDeadline)", abi.encode(permit)); + console.log("PermitSingle struct hash from cheatcode:"); + console.logBytes32(cheatStructHash); + + assertEq(cheatStructHash, userStructHash, "permit struct hash mismatch"); + } + + function testHashTransaction_withTypeName() public { + Asset memory asset = Asset ({ token: 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2, amount: 100 ether }); + + bytes32 user = TransactionHash._hashAsset(asset); + bytes32 cheat = vm.eip712HashStruct("Asset", abi.encode(asset)); + assertEq(user, cheat, "asset struct hash mismatch"); + + Person memory from = Person ({ wallet: 0x0000000000000000000000000000000000000001, name: "alice" }); + Person memory to = Person ({ wallet: 0x0000000000000000000000000000000000000002, name: "bob" }); + + user = TransactionHash._hashPerson(from); + cheat = vm.eip712HashStruct("Person", abi.encode(from)); + assertEq(user, cheat, "person struct hash mismatch"); + + Transaction memory t = Transaction ({ from: from, to: to, tx: asset }); + + user = TransactionHash.hash(t); + cheat = vm.eip712HashStruct("Transaction", abi.encode(t)); + assertEq(user, cheat, "transaction struct hash mismatch"); + } + + function testHashTransaction_withTypeDefinition() public { + Asset memory asset = Asset ({ token: 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2, amount: 100 ether }); + + bytes32 user = TransactionHash._hashAsset(asset); + bytes32 cheat = vm.eip712HashStruct("Asset(address token, uint256 amount)", abi.encode(asset)); + assertEq(user, cheat, "asset struct hash mismatch"); + + Person memory from = Person ({ wallet: 0x0000000000000000000000000000000000000001, name: "alice" }); + Person memory to = Person ({ wallet: 0x0000000000000000000000000000000000000002, name: "bob" }); + + user = TransactionHash._hashPerson(from); + cheat = vm.eip712HashStruct("Person(address wallet, string name)", abi.encode(from)); + assertEq(user, cheat, "person struct hash mismatch"); + + Transaction memory t = Transaction ({ from: from, to: to, tx: asset }); + + user = TransactionHash.hash(t); + cheat = vm.eip712HashStruct("Person(address wallet, string name) Asset(address token, uint256 amount) Transaction(Person from, Person to, Asset tx)", abi.encode(t)); + assertEq(user, cheat, "transaction struct hash mismatch"); + } +} +"#, + ) + .unwrap(); + + cmd.forge_fuse() + .args(["test", "--mc", "Eip712HashStructTest", "-vv"]) + .assert_success() + .stdout_eq(str![[r#" +... +[PASS] testHashPermitSingle_withTypeDefinion() ([GAS]) +Logs: + PermitSingle struct hash from cheatcode: + 0x3ed744fdcea02b6b9ad45a9db6e648bf6f18c221909f9ee425191f2a02f9e4a8 + +[PASS] testHashPermitSingle_withTypeName() ([GAS]) +Logs: + PermitSingle struct hash from cheatcode: + 0x3ed744fdcea02b6b9ad45a9db6e648bf6f18c221909f9ee425191f2a02f9e4a8 +... +"#]]); +}); + +forgetest!(test_eip712_hash_typed_data, |prj, cmd| { + prj.insert_ds_test(); + prj.insert_vm(); + prj.insert_console(); + + prj.add_source( + "Eip712HashTypedData.sol", + r#" +import "./Vm.sol"; +import "./test.sol"; +import "./console.sol"; +contract Eip712HashTypedDataTest is DSTest { + Vm constant vm = Vm(address(uint160(uint256(keccak256("hevm cheat code"))))); + + function testHashEIP712Message() public { + string memory jsonData = + '{"types":{"EIP712Domain":[{"name":"name","type":"string"},{"name":"version","type":"string"},{"name":"chainId","type":"uint256"},{"name":"verifyingContract","type":"address"},{"name":"salt","type":"bytes32"}]},"primaryType":"EIP712Domain","domain":{"name":"example.metamask.io","version":"1","chainId":1,"verifyingContract":"0x0000000000000000000000000000000000000000"},"message":{}}'; + + // since this cheatcode simply exposes an alloy fn, the test has been borrowed from: + // + bytes32 expectedHash = hex"122d1c8ef94b76dad44dcb03fa772361e20855c63311a15d5afe02d1b38f6077"; + assertEq(vm.eip712HashTypedData(jsonData), expectedHash, "EIP712Domain struct hash mismatch"); + } +} +"#, + ) + .unwrap(); + + cmd.forge_fuse().args(["test", "--mc", "Eip712HashTypedDataTest"]).assert_success(); }); diff --git a/crates/forge/tests/cli/eof.rs b/crates/forge/tests/cli/eof.rs deleted file mode 100644 index 445980304547d..0000000000000 --- a/crates/forge/tests/cli/eof.rs +++ /dev/null @@ -1,66 +0,0 @@ -use foundry_compilers::artifacts::ConfigurableContractArtifact; - -// Ensure we can build and decode EOF bytecode. -forgetest_init!(test_build_with_eof, |prj, cmd| { - cmd.forge_fuse() - .args(["build", "src/Counter.sol", "--eof", "--use", "0.8.29"]) - .assert_success() - .stdout_eq(str![[r#" -[COMPILING_FILES] with [SOLC_VERSION] -[SOLC_VERSION] [ELAPSED] -Compiler run successful! - -"#]]); - - // get artifact bytecode - let artifact_path = prj.paths().artifacts.join("Counter.sol/Counter.json"); - let artifact: ConfigurableContractArtifact = - foundry_compilers::utils::read_json_file(&artifact_path).unwrap(); - assert!(artifact.metadata.is_some()); - let bytecode = format!("{}", artifact.bytecode.unwrap().object.into_bytes().unwrap()); - - cmd.cast_fuse() - .args(["decode-eof", bytecode.as_str()]) - .assert_success().stdout_eq(str![[r#" -Header: -╭------------------------+-------╮ -| type_size | 4 | -|------------------------+-------| -| num_code_sections | 1 | -|------------------------+-------| -| code_sizes | [17] | -|------------------------+-------| -| num_container_sections | 1 | -|------------------------+-------| -| container_sizes | [257] | -|------------------------+-------| -| data_size | 0 | -╰------------------------+-------╯ - -Code sections: -╭---+--------+---------+------------------+--------------------------------------╮ -| | Inputs | Outputs | Max stack height | Code | -+================================================================================+ -| 0 | 0 | 128 | 2 | 0x608060405234e100055f6080ee005f80fd | -╰---+--------+---------+------------------+--------------------------------------╯ - -Container sections: -╭---+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------╮ -| 0 | 0xef000101000402000100ab04004300008000056080806040526004361015e100035f80fd5f3560e01c9081633fb5c1cb14e1006d81638381f58a14e100475063d09de08a14e100045fe0ffd534e100325f600319360112e100255f545f198114e10009600190015f555f80f3634e487b7160e01b5f52601160045260245ffd5f80fd5f80fd34e100155f600319360112e100086020905f548152f35f80fd5f80fd34e100166020600319360112e100086004355f555f80f35f80fd5f80fda364697066735822122030195051c5939201983e86f52c88296e7ba03945a054b4e413a7b16cafb76bd96c6578706572696d656e74616cf564736f6c634300081d0041 | -╰---+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------╯ - -"#]]); -}); - -// Ensure compiler fails if doesn't support EOFs but eof flag used. -forgetest_init!(test_unsupported_compiler, |prj, cmd| { - cmd.forge_fuse() - .args(["build", "src/Counter.sol", "--eof", "--use", "0.8.27"]) - .assert_failure() - .stderr_eq(str![[r#" -... -Error: Compiler run failed: -... - -"#]]); -}); diff --git a/crates/forge/tests/cli/lint.rs b/crates/forge/tests/cli/lint.rs new file mode 100644 index 0000000000000..80427a3e3f4ca --- /dev/null +++ b/crates/forge/tests/cli/lint.rs @@ -0,0 +1,392 @@ +use forge_lint::{linter::Lint, sol::med::REGISTERED_LINTS}; +use foundry_config::{LintSeverity, LinterConfig}; + +const CONTRACT: &str = r#" +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +struct _PascalCaseInfo { uint256 a; } +uint256 constant screaming_snake_case_info = 0; + +contract ContractWithLints { + uint256 VARIABLE_MIXED_CASE_INFO; + + function incorrectShiftHigh() public { + uint256 localValue = 50; + uint256 result = 8 >> localValue; + } + function divideBeforeMultiplyMedium() public { + (1 / 2) * 3; + } + function unoptimizedHashGas(uint256 a, uint256 b) public view { + keccak256(abi.encodePacked(a, b)); + } + function FUNCTION_MIXED_CASE_INFO() public {} +} + "#; + +const OTHER_CONTRACT: &str = r#" + // SPDX-License-Identifier: MIT + pragma solidity ^0.8.0; + + contract ContractWithLints { + uint256 VARIABLE_MIXED_CASE_INFO; + } + "#; + +forgetest!(can_use_config, |prj, cmd| { + prj.wipe_contracts(); + prj.add_source("ContractWithLints", CONTRACT).unwrap(); + prj.add_source("OtherContractWithLints", OTHER_CONTRACT).unwrap(); + + // Check config for `severity` and `exclude` + prj.update_config(|config| { + config.lint = LinterConfig { + severity: vec![LintSeverity::High, LintSeverity::Med], + exclude_lints: vec!["incorrect-shift".into()], + ignore: vec![], + lint_on_build: true, + }; + }); + cmd.arg("lint").assert_success().stderr_eq(str![[r#" +warning[divide-before-multiply]: multiplication should occur before division to avoid loss of precision + [FILE]:16:9 + | +16 | (1 / 2) * 3; + | ----------- + | + = help: https://book.getfoundry.sh/reference/forge/forge-lint#divide-before-multiply + + +"#]]); +}); + +forgetest!(can_use_config_ignore, |prj, cmd| { + prj.wipe_contracts(); + prj.add_source("ContractWithLints", CONTRACT).unwrap(); + prj.add_source("OtherContract", OTHER_CONTRACT).unwrap(); + + // Check config for `ignore` + prj.update_config(|config| { + config.lint = LinterConfig { + severity: vec![], + exclude_lints: vec![], + ignore: vec!["src/ContractWithLints.sol".into()], + lint_on_build: true, + }; + }); + cmd.arg("lint").assert_success().stderr_eq(str![[r#" +note[mixed-case-variable]: mutable variables should use mixedCase + [FILE]:6:17 + | +6 | uint256 VARIABLE_MIXED_CASE_INFO; + | ------------------------ + | + = help: https://book.getfoundry.sh/reference/forge/forge-lint#mixed-case-variable + + +"#]]); + + // Check config again, ignoring all files + prj.update_config(|config| { + config.lint = LinterConfig { + severity: vec![], + exclude_lints: vec![], + ignore: vec!["src/ContractWithLints.sol".into(), "src/OtherContract.sol".into()], + lint_on_build: true, + }; + }); + cmd.arg("lint").assert_success().stderr_eq(str![[""]]); +}); + +forgetest!(can_override_config_severity, |prj, cmd| { + prj.wipe_contracts(); + prj.add_source("ContractWithLints", CONTRACT).unwrap(); + prj.add_source("OtherContractWithLints", OTHER_CONTRACT).unwrap(); + + // Override severity + prj.update_config(|config| { + config.lint = LinterConfig { + severity: vec![LintSeverity::High, LintSeverity::Med], + exclude_lints: vec![], + ignore: vec!["src/ContractWithLints.sol".into()], + lint_on_build: true, + }; + }); + cmd.arg("lint").args(["--severity", "info"]).assert_success().stderr_eq(str![[r#" +note[mixed-case-variable]: mutable variables should use mixedCase + [FILE]:6:17 + | +6 | uint256 VARIABLE_MIXED_CASE_INFO; + | ------------------------ + | + = help: https://book.getfoundry.sh/reference/forge/forge-lint#mixed-case-variable + + +"#]]); +}); + +forgetest!(can_override_config_path, |prj, cmd| { + prj.wipe_contracts(); + prj.add_source("ContractWithLints", CONTRACT).unwrap(); + prj.add_source("OtherContractWithLints", OTHER_CONTRACT).unwrap(); + + // Override excluded files + prj.update_config(|config| { + config.lint = LinterConfig { + severity: vec![LintSeverity::High, LintSeverity::Med], + exclude_lints: vec!["incorrect-shift".into()], + ignore: vec!["src/ContractWithLints.sol".into()], + lint_on_build: true, + }; + }); + cmd.arg("lint").arg("src/ContractWithLints.sol").assert_success().stderr_eq(str![[r#" +warning[divide-before-multiply]: multiplication should occur before division to avoid loss of precision + [FILE]:16:9 + | +16 | (1 / 2) * 3; + | ----------- + | + = help: https://book.getfoundry.sh/reference/forge/forge-lint#divide-before-multiply + + +"#]]); +}); + +forgetest!(can_override_config_lint, |prj, cmd| { + prj.wipe_contracts(); + prj.add_source("ContractWithLints", CONTRACT).unwrap(); + prj.add_source("OtherContractWithLints", OTHER_CONTRACT).unwrap(); + + // Override excluded lints + prj.update_config(|config| { + config.lint = LinterConfig { + severity: vec![LintSeverity::High, LintSeverity::Med], + exclude_lints: vec!["incorrect-shift".into()], + ignore: vec![], + lint_on_build: true, + }; + }); + cmd.arg("lint").args(["--only-lint", "incorrect-shift"]).assert_success().stderr_eq(str![[ + r#" +warning[incorrect-shift]: the order of args in a shift operation is incorrect + [FILE]:13:26 + | +13 | uint256 result = 8 >> localValue; + | --------------- + | + = help: https://book.getfoundry.sh/reference/forge/forge-lint#incorrect-shift + + +"# + ]]); +}); + +forgetest!(build_runs_linter_by_default, |prj, cmd| { + prj.wipe_contracts(); + prj.add_source("ContractWithLints", CONTRACT).unwrap(); + + // Configure linter to show only medium severity lints + prj.update_config(|config| { + config.lint = LinterConfig { + severity: vec![LintSeverity::Med], + exclude_lints: vec!["incorrect-shift".into()], + ignore: vec![], + lint_on_build: true, + }; + }); + + // Run forge build and expect linting output before compilation + cmd.arg("build").assert_success().stderr_eq(str![[r#" +warning[divide-before-multiply]: multiplication should occur before division to avoid loss of precision + [FILE]:16:9 + | +16 | (1 / 2) * 3; + | ----------- + | + = help: https://book.getfoundry.sh/reference/forge/forge-lint#divide-before-multiply + + +"#]]).stdout_eq(str![[r#" +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +Compiler run successful with warnings: +Warning (2072): Unused local variable. + [FILE]:13:9: + | +13 | uint256 result = 8 >> localValue; + | ^^^^^^^^^^^^^^ + +Warning (6133): Statement has no effect. + [FILE]:16:9: + | +16 | (1 / 2) * 3; + | ^^^^^^^^^^^ + +Warning (2018): Function state mutability can be restricted to pure + [FILE]:11:5: + | +11 | function incorrectShiftHigh() public { + | ^ (Relevant source part starts here and spans across multiple lines). + +Warning (2018): Function state mutability can be restricted to pure + [FILE]:15:5: + | +15 | function divideBeforeMultiplyMedium() public { + | ^ (Relevant source part starts here and spans across multiple lines). + +Warning (2018): Function state mutability can be restricted to pure + [FILE]:18:5: + | +18 | function unoptimizedHashGas(uint256 a, uint256 b) public view { + | ^ (Relevant source part starts here and spans across multiple lines). + + +"#]]); +}); + +forgetest!(build_respects_quiet_flag_for_linting, |prj, cmd| { + prj.wipe_contracts(); + prj.add_source("ContractWithLints", CONTRACT).unwrap(); + + // Configure linter to show medium severity lints + prj.update_config(|config| { + config.lint = LinterConfig { + severity: vec![LintSeverity::Med], + exclude_lints: vec!["incorrect-shift".into()], + ignore: vec![], + lint_on_build: true, + }; + }); + + // Run forge build with --quiet flag - should not show linting output + cmd.arg("build").arg("--quiet").assert_success().stderr_eq(str![[""]]).stdout_eq(str![[""]]); +}); + +forgetest!(build_with_json_uses_json_linter_output, |prj, cmd| { + prj.wipe_contracts(); + prj.add_source("ContractWithLints", CONTRACT).unwrap(); + + // Configure linter to show medium severity lints + prj.update_config(|config| { + config.lint = LinterConfig { + severity: vec![LintSeverity::Med], + exclude_lints: vec!["incorrect-shift".into()], + ignore: vec![], + lint_on_build: true, + }; + }); + + // Run forge build with --json flag - should use JSON formatter for linting + let output = cmd.arg("build").arg("--json").assert_success(); + + // Should contain JSON linting output + let stderr = String::from_utf8_lossy(&output.get_output().stderr); + assert!(stderr.contains("\"code\"")); + assert!(stderr.contains("divide-before-multiply")); + + // Should also contain JSON compilation output + let stdout = String::from_utf8_lossy(&output.get_output().stdout); + assert!(stdout.contains("\"errors\"")); + assert!(stdout.contains("\"sources\"")); +}); + +forgetest!(build_respects_lint_on_build_false, |prj, cmd| { + prj.wipe_contracts(); + prj.add_source("ContractWithLints", CONTRACT).unwrap(); + + // Configure linter with medium severity lints but disable lint_on_build + prj.update_config(|config| { + config.lint = LinterConfig { + severity: vec![LintSeverity::Med], + exclude_lints: vec!["incorrect-shift".into()], + ignore: vec![], + lint_on_build: false, + }; + }); + + // Run forge build - should NOT show linting output because lint_on_build is false + cmd.arg("build").assert_success().stderr_eq(str![[""]]).stdout_eq(str![[r#" +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +Compiler run successful with warnings: +Warning (2072): Unused local variable. + [FILE]:13:9: + | +13 | uint256 result = 8 >> localValue; + | ^^^^^^^^^^^^^^ + +Warning (6133): Statement has no effect. + [FILE]:16:9: + | +16 | (1 / 2) * 3; + | ^^^^^^^^^^^ + +Warning (2018): Function state mutability can be restricted to pure + [FILE]:11:5: + | +11 | function incorrectShiftHigh() public { + | ^ (Relevant source part starts here and spans across multiple lines). + +Warning (2018): Function state mutability can be restricted to pure + [FILE]:15:5: + | +15 | function divideBeforeMultiplyMedium() public { + | ^ (Relevant source part starts here and spans across multiple lines). + +Warning (2018): Function state mutability can be restricted to pure + [FILE]:18:5: + | +18 | function unoptimizedHashGas(uint256 a, uint256 b) public view { + | ^ (Relevant source part starts here and spans across multiple lines). + + +"#]]); +}); + +#[tokio::test] +async fn ensure_lint_rule_docs() { + const FOUNDRY_BOOK_LINT_PAGE_URL: &str = + "https://book.getfoundry.sh/reference/forge/forge-lint"; + + // Fetch the content of the lint reference + let content = match reqwest::get(FOUNDRY_BOOK_LINT_PAGE_URL).await { + Ok(resp) => { + if !resp.status().is_success() { + panic!( + "Failed to fetch Foundry Book lint page ({FOUNDRY_BOOK_LINT_PAGE_URL}). Status: {status}", + status = resp.status() + ); + } + match resp.text().await { + Ok(text) => text, + Err(e) => { + panic!("Failed to read response text: {e}"); + } + } + } + Err(e) => { + panic!("Failed to fetch Foundry Book lint page ({FOUNDRY_BOOK_LINT_PAGE_URL}): {e}",); + } + }; + + // Ensure no missing lints + let mut missing_lints = Vec::new(); + for lint in REGISTERED_LINTS { + let selector = format!("#{}", lint.id()); + if !content.contains(&selector) { + missing_lints.push(lint.id()); + } + } + + if !missing_lints.is_empty() { + let mut msg = String::from( + "Foundry Book lint validation failed. The following lints must be added to the docs:\n", + ); + for lint in missing_lints { + msg.push_str(&format!(" - {lint}\n")); + } + msg.push_str("Please open a PR: https://github.com/foundry-rs/book"); + panic!("{msg}"); + } +} diff --git a/crates/forge/tests/cli/main.rs b/crates/forge/tests/cli/main.rs index d48eae912c050..40a8c9f8b5d5a 100644 --- a/crates/forge/tests/cli/main.rs +++ b/crates/forge/tests/cli/main.rs @@ -16,10 +16,10 @@ mod create; mod debug; mod doc; mod eip712; -mod eof; mod failure_assertions; mod geiger; mod inline_config; +mod lint; mod multi_script; mod script; mod soldeer; diff --git a/crates/forge/tests/cli/script.rs b/crates/forge/tests/cli/script.rs index 1b1180e793015..64af01bdb1566 100644 --- a/crates/forge/tests/cli/script.rs +++ b/crates/forge/tests/cli/script.rs @@ -1,6 +1,7 @@ //! Contains various tests related to `forge script`. use crate::constants::TEMPLATE_CONTRACT; +use alloy_hardforks::EthereumHardfork; use alloy_primitives::{address, hex, Address, Bytes}; use anvil::{spawn, NodeConfig}; use forge_script_sequence::ScriptSequence; @@ -1952,7 +1953,7 @@ contract SimpleScript is Script { ]) .assert_success() .stdout_eq(str![[r#" -{"logs":[],"returns":{"success":{"internal_type":"bool","value":"true"}},"success":true,"raw_logs":[],"traces":[["Deployment",{"arena":[{"parent":null,"children":[],"idx":0,"trace":{"depth":0,"success":true,"caller":"0x1804c8ab1f12e6bbf3894d4083f33e07309d1f38","address":"0x5b73c5498c1e3b4dba84de0f1833c4a029d90519","maybe_precompile":false,"selfdestruct_address":null,"selfdestruct_refund_target":null,"selfdestruct_transferred_value":null,"kind":"CREATE","value":"0x0","data":"[..]","output":"[..]","gas_used":"{...}","gas_limit":"{...}","status":"Return","steps":[],"decoded":{"label":null,"return_data":null,"call_data":null}},"logs":[],"ordering":[]}]}],["Execution",{"arena":[{"parent":null,"children":[1,2],"idx":0,"trace":{"depth":0,"success":true,"caller":"0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266","address":"0x5b73c5498c1e3b4dba84de0f1833c4a029d90519","maybe_precompile":null,"selfdestruct_address":null,"selfdestruct_refund_target":null,"selfdestruct_transferred_value":null,"kind":"CALL","value":"0x0","data":"0xc0406226","output":"0x0000000000000000000000000000000000000000000000000000000000000001","gas_used":"{...}","gas_limit":1073720760,"status":"Return","steps":[],"decoded":{"label":null,"return_data":null,"call_data":null}},"logs":[],"ordering":[{"Call":0},{"Call":1}]},{"parent":0,"children":[],"idx":1,"trace":{"depth":1,"success":true,"caller":"0x5b73c5498c1e3b4dba84de0f1833c4a029d90519","address":"0x7109709ecfa91a80626ff3989d68f67f5b1dd12d","maybe_precompile":null,"selfdestruct_address":null,"selfdestruct_refund_target":null,"selfdestruct_transferred_value":null,"kind":"CALL","value":"0x0","data":"0x7fb5297f","output":"0x","gas_used":"{...}","gas_limit":1056940999,"status":"Return","steps":[],"decoded":{"label":null,"return_data":null,"call_data":null}},"logs":[],"ordering":[]},{"parent":0,"children":[],"idx":2,"trace":{"depth":1,"success":true,"caller":"0x5b73c5498c1e3b4dba84de0f1833c4a029d90519","address":"0x0000000000000000000000000000000000000000","maybe_precompile":null,"selfdestruct_address":null,"selfdestruct_refund_target":null,"selfdestruct_transferred_value":null,"kind":"CALL","value":"0x0","data":"0x","output":"0x","gas_used":"{...}","gas_limit":1056940650,"status":"Stop","steps":[],"decoded":{"label":null,"return_data":null,"call_data":null}},"logs":[],"ordering":[]}]}]],"gas_used":"{...}","labeled_addresses":{},"returned":"0x0000000000000000000000000000000000000000000000000000000000000001","address":null} +{"logs":[],"returns":{"success":{"internal_type":"bool","value":"true"}},"success":true,"raw_logs":[],"traces":[["Deployment",{"arena":[{"parent":null,"children":[],"idx":0,"trace":{"depth":0,"success":true,"caller":"0x1804c8ab1f12e6bbf3894d4083f33e07309d1f38","address":"0x5b73c5498c1e3b4dba84de0f1833c4a029d90519","maybe_precompile":false,"selfdestruct_address":null,"selfdestruct_refund_target":null,"selfdestruct_transferred_value":null,"kind":"CREATE","value":"0x0","data":"[..]","output":"[..]","gas_used":"{...}","gas_limit":"{...}","status":"Return","steps":[],"decoded":{"label":"SimpleScript","return_data":null,"call_data":null}},"logs":[],"ordering":[]}]}],["Execution",{"arena":[{"parent":null,"children":[1,2],"idx":0,"trace":{"depth":0,"success":true,"caller":"0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266","address":"0x5b73c5498c1e3b4dba84de0f1833c4a029d90519","maybe_precompile":null,"selfdestruct_address":null,"selfdestruct_refund_target":null,"selfdestruct_transferred_value":null,"kind":"CALL","value":"0x0","data":"0xc0406226","output":"0x0000000000000000000000000000000000000000000000000000000000000001","gas_used":"{...}","gas_limit":1073720760,"status":"Return","steps":[],"decoded":{"label":"SimpleScript","return_data":"true","call_data":{"signature":"run()","args":[]}}},"logs":[],"ordering":[{"Call":0},{"Call":1}]},{"parent":0,"children":[],"idx":1,"trace":{"depth":1,"success":true,"caller":"0x5b73c5498c1e3b4dba84de0f1833c4a029d90519","address":"0x7109709ecfa91a80626ff3989d68f67f5b1dd12d","maybe_precompile":null,"selfdestruct_address":null,"selfdestruct_refund_target":null,"selfdestruct_transferred_value":null,"kind":"CALL","value":"0x0","data":"0x7fb5297f","output":"0x","gas_used":"{...}","gas_limit":1056940999,"status":"Return","steps":[],"decoded":{"label":"VM","return_data":null,"call_data":{"signature":"startBroadcast()","args":[]}}},"logs":[],"ordering":[]},{"parent":0,"children":[],"idx":2,"trace":{"depth":1,"success":true,"caller":"0x5b73c5498c1e3b4dba84de0f1833c4a029d90519","address":"0x0000000000000000000000000000000000000000","maybe_precompile":null,"selfdestruct_address":null,"selfdestruct_refund_target":null,"selfdestruct_transferred_value":null,"kind":"CALL","value":"0x0","data":"0x","output":"0x","gas_used":"{...}","gas_limit":1056940650,"status":"Stop","steps":[],"decoded":{"label":null,"return_data":null,"call_data":null}},"logs":[],"ordering":[]}]}]],"gas_used":"{...}","labeled_addresses":{},"returned":"0x0000000000000000000000000000000000000000000000000000000000000001","address":null} {"chain":31337,"estimated_gas_price":"{...}","estimated_total_gas_used":"{...}","estimated_amount_required":"{...}","token_symbol":"ETH"} {"chain":"anvil-hardhat","status":"success","tx_hash":"0x4f78afe915fceb282c7625a68eb350bc0bf78acb59ad893e5c62b710a37f3156","contract_address":null,"block_number":1,"gas_used":"{...}","gas_price":"{...}"} {"status":"success","transactions":"[..]/broadcast/Foo.sol/31337/run-latest.json","sensitive":"[..]/cache/Foo.sol/31337/run-latest.json"} @@ -2575,7 +2576,7 @@ Chain 31337 accessList [] chainId 31337 -gasLimit 228231 +gasLimit [..] gasPrice input [..] maxFeePerBlobGas @@ -2720,3 +2721,293 @@ Warning: No transactions to broadcast. "# ]); }); + +// Tests EIP-7702 broadcast +forgetest_async!(can_broadcast_txes_with_signed_auth, |prj, cmd| { + foundry_test_utils::util::initialize(prj.root()); + prj.add_script( + "EIP7702Script.s.sol", + r#" +import "forge-std/Script.sol"; +import {Vm} from "forge-std/Vm.sol"; +import {Counter} from "../src/Counter.sol"; +contract EIP7702Script is Script { + uint256 constant PRIVATE_KEY = uint256(bytes32(0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80)); + address constant SENDER = 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266; + function setUp() public {} + function run() public { + vm.startBroadcast(PRIVATE_KEY); + Counter counter = new Counter(); + Counter counter1 = new Counter(); + Counter counter2 = new Counter(); + vm.signAndAttachDelegation(address(counter), PRIVATE_KEY); + Counter(SENDER).increment(); + Counter(SENDER).increment(); + vm.signAndAttachDelegation(address(counter1), PRIVATE_KEY); + Counter(SENDER).setNumber(0); + vm.signAndAttachDelegation(address(counter2), PRIVATE_KEY); + Counter(SENDER).setNumber(0); + vm.stopBroadcast(); + } +} + "#, + ) + .unwrap(); + + let node_config = NodeConfig::test().with_hardfork(Some(EthereumHardfork::Prague.into())); + let (_api, handle) = spawn(node_config).await; + + cmd.args([ + "script", + "script/EIP7702Script.s.sol", + "--rpc-url", + &handle.http_endpoint(), + "-vvvvv", + "--non-interactive", + "--slow", + "--broadcast", + "--evm-version", + "prague", + ]) + .assert_success() + .stdout_eq(str![[r#" +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +Compiler run successful! +Traces: + [..] EIP7702Script::setUp() + └─ ← [Stop] + + [..] EIP7702Script::run() + ├─ [0] VM::startBroadcast() + │ └─ ← [Return] + ├─ [..] → new Counter@0x5FbDB2315678afecb367f032d93F642f64180aa3 + │ └─ ← [Return] 481 bytes of code + ├─ [..] → new Counter@0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512 + │ └─ ← [Return] 481 bytes of code + ├─ [..] → new Counter@0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0 + │ └─ ← [Return] 481 bytes of code + ├─ [0] VM::signAndAttachDelegation(0x5FbDB2315678afecb367f032d93F642f64180aa3, "") + │ └─ ← [Return] (0, 0xd4301eb9f82f747137a5f2c3dc3a5c2d253917cf99ecdc0d49f7bb85313c3159, 0x786d354f0bbd456f44116ddd3aa50475e989d72d8396005e5b3a12cede83fb68, 4, 0x5FbDB2315678afecb367f032d93F642f64180aa3) + ├─ [..] 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266::increment() + │ └─ ← [Stop] + ├─ [..] 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266::increment() + │ └─ ← [Stop] + ├─ [0] VM::signAndAttachDelegation(0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512, "") + │ └─ ← [Return] (0, 0xaba9128338f7ff036a0d2ecb96d4f4376389005cd565f87aba33b312570af962, 0x69acbe0831fb8ca95338bc4b908dcfebaf7b81b0f770a12c073ceb07b89fbdf3, 7, 0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512) + ├─ [..] 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266::setNumber(0) + │ └─ ← [Stop] + ├─ [0] VM::signAndAttachDelegation(0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0, "") + │ └─ ← [Return] (1, 0x3a3427b66e589338ce7ea06135650708f9152e93e257b4a5ec6eb86a3e09a2ce, 0x444651c354c89fd3312aafb05948e12c0a16220827a5e467705253ab4d8aa8d3, 9, 0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0) + ├─ [..] 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266::setNumber(0) + │ └─ ← [Stop] + ├─ [0] VM::stopBroadcast() + │ └─ ← [Return] + └─ ← [Stop] + + +Script ran successfully. + +## Setting up 1 EVM. +========================== +Simulated On-chain Traces: + + [..] → new Counter@0x5FbDB2315678afecb367f032d93F642f64180aa3 + └─ ← [Return] 481 bytes of code + + [..] → new Counter@0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512 + └─ ← [Return] 481 bytes of code + + [..] → new Counter@0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0 + └─ ← [Return] 481 bytes of code + + [0] 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266::increment() + └─ ← [Stop] + + [0] 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266::increment() + └─ ← [Stop] + + [0] 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266::setNumber(0) + └─ ← [Stop] + + [0] 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266::setNumber(0) + └─ ← [Stop] + + +========================== + +Chain 31337 + +[ESTIMATED_GAS_PRICE] + +[ESTIMATED_TOTAL_GAS_USED] + +[ESTIMATED_AMOUNT_REQUIRED] + +========================== + + +========================== + +ONCHAIN EXECUTION COMPLETE & SUCCESSFUL. + +[SAVED_TRANSACTIONS] + +[SAVED_SENSITIVE_VALUES] + + +"#]]); +}); + +// Tests EIP-7702 with multiple auth +// Alice sends 5 ETH from Bob to Receiver1 and 1 ETH to Receiver2 +forgetest_async!(can_broadcast_txes_with_multiple_auth, |prj, cmd| { + foundry_test_utils::util::initialize(prj.root()); + prj.add_source( + "BatchCallDelegation.sol", + r#" +contract BatchCallDelegation { + event CallExecuted(address indexed to, uint256 indexed value, bytes data, bool success); + + struct Call { + bytes data; + address to; + uint256 value; + } + + function execute(Call[] calldata calls) external payable { + for (uint256 i = 0; i < calls.length; i++) { + Call memory call = calls[i]; + (bool success,) = call.to.call{value: call.value}(call.data); + require(success, "call reverted"); + emit CallExecuted(call.to, call.value, call.data, success); + } + } +} + "#, + ) + .unwrap(); + + prj.add_script( + "BatchCallDelegationScript.s.sol", + r#" +import {Script, console} from "forge-std/Script.sol"; +import {Vm} from "forge-std/Vm.sol"; +import {BatchCallDelegation} from "../src/BatchCallDelegation.sol"; + +contract BatchCallDelegationScript is Script { + // Alice's address and private key (EOA with no initial contract code). + address payable ALICE_ADDRESS = payable(0x70997970C51812dc3A010C7d01b50e0d17dc79C8); + uint256 constant ALICE_PK = 0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d; + + // Bob's address and private key (Bob will execute transactions on Alice's behalf). + address constant BOB_ADDRESS = 0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC; + uint256 constant BOB_PK = 0x5de4111afa1a4b94908f83103eb1f1706367c2e68ca870fc3fb9a804cdab365a; + + address constant RECEIVER_1 = 0x14dC79964da2C08b23698B3D3cc7Ca32193d9955; + address constant RECEIVER_2 = 0x9965507D1a55bcC2695C58ba16FB37d819B0A4dc; + + uint256 constant DEPLOYER_PK = 0x2a871d0798f97d79848a013d4936a73bf4cc922c825d33c1cf7073dff6d409c6; + + function run() public { + BatchCallDelegation.Call[] memory aliceCalls = new BatchCallDelegation.Call[](1); + aliceCalls[0] = BatchCallDelegation.Call({to: RECEIVER_1, value: 5 ether, data: ""}); + + BatchCallDelegation.Call[] memory bobCalls = new BatchCallDelegation.Call[](2); + bobCalls[0] = BatchCallDelegation.Call({to: RECEIVER_1, value: 5 ether, data: ""}); + bobCalls[1] = BatchCallDelegation.Call({to: RECEIVER_2, value: 1 ether, data: ""}); + + vm.startBroadcast(DEPLOYER_PK); + BatchCallDelegation batcher = new BatchCallDelegation(); + vm.stopBroadcast(); + + vm.startBroadcast(ALICE_PK); + vm.signAndAttachDelegation(address(batcher), ALICE_PK); + vm.signAndAttachDelegation(address(batcher), BOB_PK); + vm.signAndAttachDelegation(address(batcher), BOB_PK); + + BatchCallDelegation(BOB_ADDRESS).execute(bobCalls); + + vm.stopBroadcast(); + } +} + "#, + ) + .unwrap(); + + let node_config = NodeConfig::test().with_hardfork(Some(EthereumHardfork::Prague.into())); + let (api, handle) = spawn(node_config).await; + + cmd.args([ + "script", + "script/BatchCallDelegationScript.s.sol", + "--rpc-url", + &handle.http_endpoint(), + "--non-interactive", + "--slow", + "--broadcast", + "--evm-version", + "prague", + ]) + .assert_success() + .stdout_eq(str![[r#" +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +Compiler run successful! +Script ran successfully. + +## Setting up 1 EVM. + +========================== + +Chain 31337 + +[ESTIMATED_GAS_PRICE] + +[ESTIMATED_TOTAL_GAS_USED] + +[ESTIMATED_AMOUNT_REQUIRED] + +========================== + + +========================== + +ONCHAIN EXECUTION COMPLETE & SUCCESSFUL. + +[SAVED_TRANSACTIONS] + +[SAVED_SENSITIVE_VALUES] + + +"#]]); + + // Alice nonce should be 2 (tx sender and one auth) + let alice_acc = api + .get_account(address!("0x70997970C51812dc3A010C7d01b50e0d17dc79C8"), None) + .await + .unwrap(); + assert_eq!(alice_acc.nonce, 2); + + // Bob nonce should be 2 (two auths) and balance reduced by 6 ETH. + let bob_acc = api + .get_account(address!("0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC"), None) + .await + .unwrap(); + assert_eq!(bob_acc.nonce, 2); + assert_eq!(bob_acc.balance.to_string(), "94000000000000000000"); + + // Receiver balances should be updated with 5 ETH and 1 ETH. + let receiver1 = api + .get_account(address!("0x14dC79964da2C08b23698B3D3cc7Ca32193d9955"), None) + .await + .unwrap(); + assert_eq!(receiver1.nonce, 0); + assert_eq!(receiver1.balance.to_string(), "105000000000000000000"); + let receiver2 = api + .get_account(address!("0x9965507D1a55bcC2695C58ba16FB37d819B0A4dc"), None) + .await + .unwrap(); + assert_eq!(receiver2.nonce, 0); + assert_eq!(receiver2.balance.to_string(), "101000000000000000000"); +}); diff --git a/crates/forge/tests/cli/svm.rs b/crates/forge/tests/cli/svm.rs index d585529da8e40..5fe82f5670cc2 100644 --- a/crates/forge/tests/cli/svm.rs +++ b/crates/forge/tests/cli/svm.rs @@ -11,7 +11,7 @@ use svm::Platform; /// 3. svm bumped in foundry-compilers /// 4. foundry-compilers update with any breaking changes /// 5. upgrade the `LATEST_SOLC` -const LATEST_SOLC: Version = Version::new(0, 8, 29); +const LATEST_SOLC: Version = Version::new(0, 8, 30); macro_rules! ensure_svm_releases { ($($test:ident => $platform:ident),* $(,)?) => {$( diff --git a/crates/forge/tests/cli/test_cmd.rs b/crates/forge/tests/cli/test_cmd.rs index fb2e990f28984..6a6f067722cad 100644 --- a/crates/forge/tests/cli/test_cmd.rs +++ b/crates/forge/tests/cli/test_cmd.rs @@ -793,14 +793,14 @@ contract CounterTest is Test { Compiler run successful! Ran 1 test for test/CounterFuzz.t.sol:CounterTest -[FAIL: panic: arithmetic underflow or overflow (0x11); counterexample: calldata=0xa76d58f5ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff args=[115792089237316195423570985008687907853269984665640564039457584007913129639935 [1.157e77]]] testAddOne(uint256) (runs: 61, [AVG_GAS]) +[FAIL: panic: arithmetic underflow or overflow (0x11); counterexample: calldata=0xa76d58f5fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffd args=[115792089237316195423570985008687907853269984665640564039457584007913129639933 [1.157e77]]] testAddOne(uint256) (runs: 84, [AVG_GAS]) Suite result: FAILED. 0 passed; 1 failed; 0 skipped; [ELAPSED] Ran 1 test suite [ELAPSED]: 0 tests passed, 1 failed, 0 skipped (1 total tests) Failing tests: Encountered 1 failing test in test/CounterFuzz.t.sol:CounterTest -[FAIL: panic: arithmetic underflow or overflow (0x11); counterexample: calldata=0xa76d58f5ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff args=[115792089237316195423570985008687907853269984665640564039457584007913129639935 [1.157e77]]] testAddOne(uint256) (runs: 61, [AVG_GAS]) +[FAIL: panic: arithmetic underflow or overflow (0x11); counterexample: calldata=0xa76d58f5fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffd args=[115792089237316195423570985008687907853269984665640564039457584007913129639933 [1.157e77]]] testAddOne(uint256) (runs: 84, [AVG_GAS]) Encountered a total of 1 failing tests, 0 tests succeeded @@ -1582,7 +1582,7 @@ contract ATest is Test { cmd.args(["test"]).with_no_redact().assert_success().stdout_eq(str![[r#" ... -[PASS] test_negativeGas() (gas: 0) +[PASS] test_negativeGas() (gas: 96) ... "#]]); }); @@ -1779,17 +1779,17 @@ contract ATest is DSTest { cmd.args(["test"]).with_no_redact().assert_success().stdout_eq(str![[r#" ... -[PASS] testResetGas() (gas: 40) -[PASS] testResetGas1() (gas: 40) -[PASS] testResetGas2() (gas: 40) +[PASS] testResetGas() (gas: 96) +[PASS] testResetGas1() (gas: 96) +[PASS] testResetGas2() (gas: 96) [PASS] testResetGas3() (gas: [..]) [PASS] testResetGas4() (gas: [..]) -[PASS] testResetGas5() (gas: 40) -[PASS] testResetGas6() (gas: 40) -[PASS] testResetGas7() (gas: 49) +[PASS] testResetGas5() (gas: 96) +[PASS] testResetGas6() (gas: 96) +[PASS] testResetGas7() (gas: 96) [PASS] testResetGas8() (gas: [..]) -[PASS] testResetGas9() (gas: 40) -[PASS] testResetNegativeGas() (gas: 0) +[PASS] testResetGas9() (gas: 96) +[PASS] testResetNegativeGas() (gas: 96) ... "#]]); }); @@ -2456,7 +2456,6 @@ contract Dummy { forgetest_init!(test_assume_no_revert_with_data, |prj, cmd| { prj.update_config(|config| { - config.fuzz.runs = 60; config.fuzz.seed = Some(U256::from(100)); }); @@ -2788,7 +2787,7 @@ forgetest_async!(can_get_broadcast_txs, |prj, cmd| { 31337 ); - assertEq(deployedAddress, address(0x90d4E26f2e78feDf488c7F3C46B8053a0515c71F)); + assertEq(deployedAddress, address(0xD32c10E38A626Db0b0978B1A5828eb2957665668)); } function test_getDeployments() public { @@ -2798,7 +2797,7 @@ forgetest_async!(can_get_broadcast_txs, |prj, cmd| { ); assertEq(deployments.length, 2); - assertEq(deployments[0], address(0x90d4E26f2e78feDf488c7F3C46B8053a0515c71F)); // Create2 address - latest deployment + assertEq(deployments[0], address(0xD32c10E38A626Db0b0978B1A5828eb2957665668)); // Create2 address - latest deployment assertEq(deployments[1], address(0x5FbDB2315678afecb367f032d93F642f64180aa3)); // Create address - oldest deployment } } @@ -2916,8 +2915,6 @@ Traces: │ └─ ← [Return] 1 ├─ [0] VM::assertEq(1, 1) [staticcall] │ └─ ← [Return] - ├─ storage changes: - │ @ 0: 0 → 1 └─ ← [Stop] Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED] @@ -2944,7 +2941,7 @@ contract ContractTest { ... Failing tests: Encountered 1 failing test in test/Foo.t.sol:ContractTest -[FAIL: EVM error; transaction validation error: call gas cost exceeds the gas limit] setUp() ([GAS]) +[FAIL: EVM error; transaction validation error: call [GAS_COST] exceeds the [GAS_LIMIT]] setUp() ([GAS]) Encountered a total of 1 failing tests, 0 tests succeeded @@ -3523,6 +3520,7 @@ contract InterceptInitcodeTest is DSTest { }); // +// forgetest_init!(should_preserve_fork_state_setup, |prj, cmd| { prj.wipe_contracts(); prj.add_test( @@ -3551,6 +3549,13 @@ contract CounterTest is Test { mapping(uint256 => SomeStruct) internal data; function setUp() public { + // Temporary workaround for `https://eth.llamarpc.com/` being down + setChain("mainnet", ChainData({ + name: "mainnet", + rpcUrl: "https://reth-ethereum.ithaca.xyz/rpc", + chainId: 1 + })); + StdChains.Chain memory chain1 = getChain("mainnet"); StdChains.Chain memory chain2 = getChain("base"); Domain memory domain1 = Domain(chain1, vm.createFork(chain1.rpcUrl, 22253716)); @@ -3600,3 +3605,263 @@ Ran 1 test suite [ELAPSED]: 2 tests passed, 0 failed, 0 skipped (2 total tests) "#]]); }); + +// +forgetest_init!(should_not_panic_on_cool, |prj, cmd| { + prj.add_test( + "Counter.t.sol", + r#" +import "forge-std/Test.sol"; +import {Counter} from "../src/Counter.sol"; + +contract CounterTest is Test { + Counter counter = new Counter(); + + function testCoolPanic() public { + address alice = makeAddr("alice"); + vm.deal(alice, 10000 ether); + counter.setNumber(1); + vm.cool(address(counter)); + vm.prank(alice); + payable(address(counter)).transfer(1 ether); + } +} + "#, + ) + .unwrap(); + + cmd.args(["test", "--mc", "CounterTest"]).assert_failure().stdout_eq(str![[r#" +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +Compiler run successful! + +Ran 1 test for test/Counter.t.sol:CounterTest +[FAIL: EvmError: Revert] testCoolPanic() ([GAS]) +Suite result: FAILED. 0 passed; 1 failed; 0 skipped; [ELAPSED] + +Ran 1 test suite [ELAPSED]: 0 tests passed, 1 failed, 0 skipped (1 total tests) + +Failing tests: +Encountered 1 failing test in test/Counter.t.sol:CounterTest +[FAIL: EvmError: Revert] testCoolPanic() ([GAS]) + +Encountered a total of 1 failing tests, 0 tests succeeded + +"#]]); +}); + +#[cfg(not(feature = "isolate-by-default"))] +forgetest_init!(detailed_revert_when_calling_non_contract_address, |prj, cmd| { + prj.add_test( + "NonContractCallRevertTest.t.sol", + r#" +import "forge-std/Test.sol"; +import {Counter} from "../src/Counter.sol"; + +interface ICounter { + function increment() external; + function number() external returns (uint256); + function random() external returns (uint256); +} + +contract NonContractCallRevertTest is Test { + Counter public counter; + address constant ADDRESS = 0xdEADBEeF00000000000000000000000000000000; + + function setUp() public { + counter = new Counter(); + counter.setNumber(1); + } + + function test_non_supported_selector_call_failure() public { + console.log("test non supported fn selector call failure"); + ICounter(address(counter)).random(); + } + + function test_non_contract_call_failure() public { + console.log("test non contract call failure"); + ICounter(ADDRESS).number(); + } + + function test_non_contract_void_call_failure() public { + console.log("test non contract (void) call failure"); + ICounter(ADDRESS).increment(); + } +} + "#, + ) + .unwrap(); + + cmd.args(["test", "--mc", "NonContractCallRevertTest", "-vvv"]) + .assert_failure() + .stdout_eq(str![[r#" +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +Compiler run successful! + +Ran 3 tests for test/NonContractCallRevertTest.t.sol:NonContractCallRevertTest +[FAIL: call to non-contract address 0xdEADBEeF00000000000000000000000000000000] test_non_contract_call_failure() ([GAS]) +Logs: + test non contract call failure + +Traces: + [6350] NonContractCallRevertTest::test_non_contract_call_failure() + ├─ [0] console::log("test non contract call failure") [staticcall] + │ └─ ← [Stop] + ├─ [0] 0xdEADBEeF00000000000000000000000000000000::number() + │ └─ ← [Stop] + └─ ← [Revert] call to non-contract address 0xdEADBEeF00000000000000000000000000000000 + +[FAIL: call to non-contract address 0xdEADBEeF00000000000000000000000000000000] test_non_contract_void_call_failure() ([GAS]) +Logs: + test non contract (void) call failure + +Traces: + [6215] NonContractCallRevertTest::test_non_contract_void_call_failure() + ├─ [0] console::log("test non contract (void) call failure") [staticcall] + │ └─ ← [Stop] + └─ ← [Revert] call to non-contract address 0xdEADBEeF00000000000000000000000000000000 + +[FAIL: EvmError: Revert] test_non_supported_selector_call_failure() ([GAS]) +Logs: + test non supported fn selector call failure + +Traces: + [8620] NonContractCallRevertTest::test_non_supported_selector_call_failure() + ├─ [0] console::log("test non supported fn selector call failure") [staticcall] + │ └─ ← [Stop] + ├─ [145] Counter::random() + │ └─ ← [Revert] unrecognized function selector 0x5ec01e4d for contract 0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f, which has no fallback function. + └─ ← [Revert] EvmError: Revert + +Suite result: FAILED. 0 passed; 3 failed; 0 skipped; [ELAPSED] + +Ran 1 test suite [ELAPSED]: 0 tests passed, 3 failed, 0 skipped (3 total tests) + +Failing tests: +Encountered 3 failing tests in test/NonContractCallRevertTest.t.sol:NonContractCallRevertTest +[FAIL: call to non-contract address 0xdEADBEeF00000000000000000000000000000000] test_non_contract_call_failure() ([GAS]) +[FAIL: call to non-contract address 0xdEADBEeF00000000000000000000000000000000] test_non_contract_void_call_failure() ([GAS]) +[FAIL: EvmError: Revert] test_non_supported_selector_call_failure() ([GAS]) + +Encountered a total of 3 failing tests, 0 tests succeeded + +"#]]); +}); + +#[cfg(not(feature = "isolate-by-default"))] +forgetest_init!(detailed_revert_when_delegatecalling_unlinked_library, |prj, cmd| { + prj.add_test( + "NonContractDelegateCallRevertTest.t.sol", + r#" +import "forge-std/Test.sol"; + +library TestLibrary { + function foo(uint256 a) public pure returns (uint256) { + return a * 2; + } +} + +contract LibraryCaller { + address public lib; + + constructor(address _lib) { + lib = _lib; + } + + function foobar(uint256 val) public returns (uint256) { + (bool success, bytes memory data) = lib.delegatecall( + abi.encodeWithSelector(TestLibrary.foo.selector, val) + ); + + assert(success); + return abi.decode(data, (uint256)); + } +} + +contract NonContractDelegateCallRevertTest is Test { + function test_unlinked_library_call_failure() public { + console.log("Test: Simulating call to unlinked library"); + LibraryCaller caller = new LibraryCaller(0xdEADBEeF00000000000000000000000000000000); + + caller.foobar(10); + } +} + "#, + ) + .unwrap(); + + cmd.args(["test", "--mc", "NonContractDelegateCallRevertTest", "-vvv"]) + .assert_failure() + .stdout_eq(str![[r#" +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +Compiler run successful! + +Ran 1 test for test/NonContractDelegateCallRevertTest.t.sol:NonContractDelegateCallRevertTest +[FAIL: delegatecall to non-contract address 0xdEADBEeF00000000000000000000000000000000 (usually an unliked library)] test_unlinked_library_call_failure() ([GAS]) +Logs: + Test: Simulating call to unlinked library + +Traces: + [255303] NonContractDelegateCallRevertTest::test_unlinked_library_call_failure() + ├─ [0] console::log("Test: Simulating call to unlinked library") [staticcall] + │ └─ ← [Stop] + ├─ [214746] → new LibraryCaller@0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f + │ └─ ← [Return] 960 bytes of code + ├─ [3896] LibraryCaller::foobar(10) + │ ├─ [0] 0xdEADBEeF00000000000000000000000000000000::foo(10) [delegatecall] + │ │ └─ ← [Stop] + │ └─ ← [Revert] delegatecall to non-contract address 0xdEADBEeF00000000000000000000000000000000 (usually an unliked library) + └─ ← [Revert] delegatecall to non-contract address 0xdEADBEeF00000000000000000000000000000000 (usually an unliked library) + +Suite result: FAILED. 0 passed; 1 failed; 0 skipped; [ELAPSED] + +Ran 1 test suite [ELAPSED]: 0 tests passed, 1 failed, 0 skipped (1 total tests) + +Failing tests: +Encountered 1 failing test in test/NonContractDelegateCallRevertTest.t.sol:NonContractDelegateCallRevertTest +[FAIL: delegatecall to non-contract address 0xdEADBEeF00000000000000000000000000000000 (usually an unliked library)] test_unlinked_library_call_failure() ([GAS]) + +Encountered a total of 1 failing tests, 0 tests succeeded + +"#]]); +}); + +// This test is a copy of `error_event_decode_with_cache` in cast/tests/cli/selectors.rs +// but it uses `forge build` to check that the project selectors are cached by default. +forgetest_init!(build_with_selectors_cache, |prj, cmd| { + prj.add_source( + "LocalProjectContract", + r#" +contract ContractWithCustomError { + error AnotherValueTooHigh(uint256, address); + event MyUniqueEventWithinLocalProject(uint256 a, address b); +} + "#, + ) + .unwrap(); + // Build and cache project selectors. + cmd.forge_fuse().args(["build"]).assert_success(); + + // Assert cast can decode custom error with local cache. + cmd.cast_fuse() + .args(["decode-error", "0x7191bc6200000000000000000000000000000000000000000000000000000000000000650000000000000000000000000000000000000000000000000000000000D0004F"]) + .assert_success() + .stdout_eq(str![[r#" +AnotherValueTooHigh(uint256,address) +101 +0x0000000000000000000000000000000000D0004F + +"#]]); + // Assert cast can decode event with local cache. + cmd.cast_fuse() + .args(["decode-event", "0xbd3699995dcc867b64dbb607be2c33be38df9134bef1178df13bfb9446e73104000000000000000000000000000000000000000000000000000000000000004e00000000000000000000000000000000000000000000000000000dd00000004e"]) + .assert_success() + .stdout_eq(str![[r#" +MyUniqueEventWithinLocalProject(uint256,address) +78 +0x00000000000000000000000000000DD00000004e + +"#]]); +}); diff --git a/crates/forge/tests/cli/test_optimizer.rs b/crates/forge/tests/cli/test_optimizer.rs index 8b522e5c95862..de0f57022a96b 100644 --- a/crates/forge/tests/cli/test_optimizer.rs +++ b/crates/forge/tests/cli/test_optimizer.rs @@ -1082,7 +1082,7 @@ contract CounterTest is Test { function test_Increment_In_Counter_With_Salt() public { CounterWithSalt counter = new CounterWithSalt{value: 111, salt: bytes32("preprocess_counter_with_salt")}(1); - assertEq(address(counter), 0x3Efe9ecFc73fB3baB7ECafBB40D3e134260Be6AB); + assertEq(address(counter), 0x223e63BE3BF01DD04f852d70f1bE217017055f49); } } "#, @@ -1160,7 +1160,7 @@ contract CounterWithSalt { Compiling 1 files with [..] ... [FAIL: assertion failed: 113 != 112] test_Increment_In_Counter() (gas: [..]) -[FAIL: assertion failed: 0x6cDcb015cFcAd0C23560322EdEE8f324520E4b93 != 0x3Efe9ecFc73fB3baB7ECafBB40D3e134260Be6AB] test_Increment_In_Counter_With_Salt() (gas: [..]) +[FAIL: assertion failed: 0x11acEfcD29A1BA964A05C0E7F3901054BEfb17c0 != 0x223e63BE3BF01DD04f852d70f1bE217017055f49] test_Increment_In_Counter_With_Salt() (gas: [..]) ... "#]]); @@ -1412,3 +1412,180 @@ Ran 1 test suite [ELAPSED]: 1 tests passed, 0 failed, 0 skipped (1 total tests) "#]]); }); + +// +// Preprocess test contracts with try constructor statements. +forgetest_init!(preprocess_contract_with_try_ctor_stmt, |prj, cmd| { + prj.wipe_contracts(); + prj.update_config(|config| { + config.dynamic_test_linking = true; + }); + + prj.add_source( + "CounterA.sol", + r#" +contract CounterA { + uint256 number; +} + "#, + ) + .unwrap(); + prj.add_source( + "CounterB.sol", + r#" +contract CounterB { + uint256 number; + constructor(uint256 a) payable { + require(a > 0, "ctor failure"); + number = a; + } +} + "#, + ) + .unwrap(); + prj.add_source( + "CounterC.sol", + r#" +contract CounterC { + uint256 number; + constructor(uint256 a) { + require(a > 0, "ctor failure"); + number = a; + } +} + "#, + ) + .unwrap(); + + prj.add_test( + "Counter.t.sol", + r#" +import {Test} from "forge-std/Test.sol"; +import {CounterA} from "../src/CounterA.sol"; +import {CounterB} from "../src/CounterB.sol"; +import {CounterC} from "../src/CounterC.sol"; + +contract CounterTest is Test { + function test_try_counterA_creation() public { + try new CounterA() {} catch { + revert(); + } + } + + function test_try_counterB_creation() public { + try new CounterB(1) {} catch { + revert(); + } + } + + function test_try_counterB_creation_with_salt() public { + try new CounterB{value: 111, salt: bytes32("preprocess_counter_with_salt")}(1) {} catch { + revert(); + } + } + + function test_try_counterC_creation() public { + try new CounterC(2) { + new CounterC(1); + } catch { + revert(); + } + } +} + "#, + ) + .unwrap(); + // All 23 files should properly compile, tests pass. + cmd.args(["test"]).with_no_redact().assert_success().stdout_eq(str![[r#" +... +Compiling 23 files with [..] +... +[PASS] test_try_counterA_creation() (gas: [..]) +[PASS] test_try_counterB_creation() (gas: [..]) +[PASS] test_try_counterB_creation_with_salt() (gas: [..]) +[PASS] test_try_counterC_creation() (gas: [..]) +... + +"#]]); + + // Change CounterB to fail test. + prj.add_source( + "CounterB.sol", + r#" +contract CounterB { + uint256 number; + constructor(uint256 a) payable { + require(a > 11, "ctor failure"); + number = a; + } +} + "#, + ) + .unwrap(); + // Only CounterB should compile. + cmd.assert_failure().stdout_eq(str![[r#" +... +Compiling 1 files with [..] +... +[PASS] test_try_counterA_creation() (gas: [..]) +[FAIL: EvmError: Revert] test_try_counterB_creation() (gas: [..]) +[FAIL: EvmError: Revert] test_try_counterB_creation_with_salt() (gas: [..]) +[PASS] test_try_counterC_creation() (gas: [..]) +... + +"#]]); + + // Change CounterC to fail test in try statement. + prj.add_source( + "CounterC.sol", + r#" +contract CounterC { + uint256 number; + constructor(uint256 a) { + require(a > 1, "ctor failure"); + number = a; + } +} + "#, + ) + .unwrap(); + // Only CounterC should compile. + cmd.assert_failure().stdout_eq(str![[r#" +... +Compiling 1 files with [..] +... +[PASS] test_try_counterA_creation() (gas: [..]) +[FAIL: EvmError: Revert] test_try_counterB_creation() (gas: [..]) +[FAIL: EvmError: Revert] test_try_counterB_creation_with_salt() (gas: [..]) +[FAIL: ctor failure] test_try_counterC_creation() (gas: [..]) +... + +"#]]); + + // Change CounterC to fail test in try statement. + prj.add_source( + "CounterC.sol", + r#" +contract CounterC { + uint256 number; + constructor(uint256 a) { + require(a > 2, "ctor failure"); + number = a; + } +} + "#, + ) + .unwrap(); + // Only CounterC should compile and revert. + cmd.assert_failure().stdout_eq(str![[r#" +... +Compiling 1 files with [..] +... +[PASS] test_try_counterA_creation() (gas: [..]) +[FAIL: EvmError: Revert] test_try_counterB_creation() (gas: [..]) +[FAIL: EvmError: Revert] test_try_counterB_creation_with_salt() (gas: [..]) +[FAIL: EvmError: Revert] test_try_counterC_creation() (gas: [..]) +... + +"#]]); +}); diff --git a/crates/forge/tests/cli/verify_bytecode.rs b/crates/forge/tests/cli/verify_bytecode.rs index 2b61e4e847cdb..9ef4d6ddf4606 100644 --- a/crates/forge/tests/cli/verify_bytecode.rs +++ b/crates/forge/tests/cli/verify_bytecode.rs @@ -2,7 +2,7 @@ use foundry_compilers::artifacts::{BytecodeHash, EvmVersion}; use foundry_config::Config; use foundry_test_utils::{ forgetest_async, - rpc::{next_http_archive_rpc_url, next_mainnet_etherscan_api_key}, + rpc::{next_etherscan_api_key, next_http_archive_rpc_url}, util::OutputExt, TestCommand, TestProject, }; @@ -19,7 +19,7 @@ fn test_verify_bytecode( verifier_url: &str, expected_matches: (&str, &str), ) { - let etherscan_key = next_mainnet_etherscan_api_key(); + let etherscan_key = next_etherscan_api_key(); let rpc_url = next_http_archive_rpc_url(); // fetch and flatten source code @@ -33,7 +33,7 @@ fn test_verify_bytecode( prj.add_source(contract_name, &source_code).unwrap(); prj.write_config(config); - let etherscan_key = next_mainnet_etherscan_api_key(); + let etherscan_key = next_etherscan_api_key(); let mut args = vec![ "verify-bytecode", addr, @@ -74,7 +74,7 @@ fn test_verify_bytecode_with_ignore( ignore: &str, chain: &str, ) { - let etherscan_key = next_mainnet_etherscan_api_key(); + let etherscan_key = next_etherscan_api_key(); let rpc_url = next_http_archive_rpc_url(); // fetch and flatten source code diff --git a/crates/forge/tests/fixtures/colored_traces.svg b/crates/forge/tests/fixtures/colored_traces.svg index 4b2be87d03df7..e4181676bcc86 100644 --- a/crates/forge/tests/fixtures/colored_traces.svg +++ b/crates/forge/tests/fixtures/colored_traces.svg @@ -1,4 +1,4 @@ - + , { self.word("["); - self.commasep(values, print, get_span, false); + self.commasep(values, span, print, get_span, true); self.word("]"); } fn commasep<'a, T, P, S>( &mut self, values: &'a [T], + bracket_span: Span, mut print: P, mut get_span: S, compact: bool, @@ -298,12 +306,19 @@ impl<'sess> State<'sess, '_> { return; } + let first_pos = get_span(&values[0]).map(Span::lo); + self.print_trailing_comment(bracket_span, first_pos); + self.s.cbox(self.ind); - self.zerobreak(); + if !self.last_token_is_hardbreak() { + self.zerobreak(); + } if compact { self.s.cbox(0); } + let mut deind = true; for (i, value) in values.iter().enumerate() { + self.s.ibox(0); let span = get_span(value); if let Some(span) = span { self.print_comments(span.lo()); @@ -315,22 +330,27 @@ impl<'sess> State<'sess, '_> { } if let Some(span) = span { let next_pos = if is_last { None } else { get_span(&values[i + 1]).map(Span::lo) }; - self.print_trailing_comment(span, next_pos); + if self.print_trailing_comment(span, next_pos) && is_last { + // if a trailing comment is printed at the very end, we have to manually adjust + // the offset to avoid having a double break. + self.break_offset_if_not_bol(0, -self.ind); + deind = false; + }; + self.print_inline_comments(next_pos.unwrap_or(bracket_span.hi())); } if !is_last && !self.is_beginning_of_line() { self.space(); } + self.end(); } if compact { - if !self.last_token_is_hardbreak() { - self.end(); - self.zerobreak(); - } - } else { + self.end(); + } + if deind { self.zerobreak(); + self.s.offset(-self.ind); } - self.s.offset(-self.ind); self.end(); } } @@ -637,7 +657,7 @@ impl<'ast> State<'_, 'ast> { self.nbsp(); self.print_ident(&name); } - self.print_parameter_list(parameters, ListFormat::Consistent); + self.print_parameter_list(parameters, header.span, ListFormat::Consistent); self.end(); // Attributes. @@ -665,7 +685,7 @@ impl<'ast> State<'_, 'ast> { if !returns.is_empty() { self.space(); self.word("returns "); - self.print_parameter_list(returns, ListFormat::Consistent); + self.print_parameter_list(returns, header.span, ListFormat::Consistent); } if let Some(body) = body { @@ -708,7 +728,7 @@ impl<'ast> State<'_, 'ast> { let ast::ItemError { name, parameters } = err; self.word("error "); self.print_ident(name); - self.print_parameter_list(parameters, ListFormat::Consistent); + self.print_parameter_list(parameters, Span::DUMMY, ListFormat::Consistent); self.word(";"); } @@ -716,7 +736,7 @@ impl<'ast> State<'_, 'ast> { let ast::ItemEvent { name, parameters, anonymous } = event; self.word("event "); self.print_ident(name); - self.print_parameter_list(parameters, ListFormat::Consistent); + self.print_parameter_list(parameters, Span::DUMMY, ListFormat::Consistent); if *anonymous { self.word(" anonymous"); } @@ -780,9 +800,10 @@ impl<'ast> State<'_, 'ast> { fn print_parameter_list( &mut self, parameters: &'ast [ast::VariableDefinition<'ast>], + span: Span, format: ListFormat, ) { - self.print_tuple(parameters, Self::print_var, get_span!(), format); + self.print_tuple(parameters, span, Self::print_var, get_span!(), format); } fn print_docs(&mut self, docs: &'ast ast::DocComments<'ast>) { @@ -1071,6 +1092,7 @@ impl<'ast> State<'_, 'ast> { } self.print_tuple( paths, + *span, |this, path| this.print_path(path), get_span!(()), ListFormat::Consistent, @@ -1088,7 +1110,7 @@ impl<'ast> State<'_, 'ast> { match kind { ast::ExprKind::Array(exprs) => { - self.print_array(exprs, |this, e| this.print_expr(e), get_span!()) + self.print_array(exprs, expr.span, |this, e| this.print_expr(e), get_span!()) } ast::ExprKind::Assign(lhs, None, rhs) => { self.ibox(0); @@ -1182,6 +1204,7 @@ impl<'ast> State<'_, 'ast> { } ast::ExprKind::Tuple(exprs) => self.print_tuple( exprs, + span, |this, expr| { if let Some(expr) = expr { this.print_expr(expr); @@ -1194,6 +1217,7 @@ impl<'ast> State<'_, 'ast> { self.word("type"); self.print_tuple( std::slice::from_ref(ty), + span, Self::print_ty, get_span!(), ListFormat::Consistent, @@ -1238,6 +1262,7 @@ impl<'ast> State<'_, 'ast> { ast::CallArgsKind::Unnamed(exprs) => { self.print_tuple( exprs, + span, |this, e| this.print_expr(e), get_span!(), ListFormat::Consistent, @@ -1289,6 +1314,7 @@ impl<'ast> State<'_, 'ast> { if !flags.is_empty() { self.print_tuple( flags, + span, Self::print_ast_str_lit, get_span!(), ListFormat::Consistent, @@ -1300,6 +1326,7 @@ impl<'ast> State<'_, 'ast> { ast::StmtKind::DeclMulti(vars, expr) => { self.print_tuple( vars, + span, |this, var| { if let Some(var) = var { this.print_var(var); @@ -1400,11 +1427,18 @@ impl<'ast> State<'_, 'ast> { } if !args.is_empty() { self.word("returns "); - self.print_parameter_list(args, ListFormat::Compact); + self.print_parameter_list(args, *try_span, ListFormat::Compact); self.nbsp(); } self.print_block(block, *try_span); - self.print_trailing_comment(*try_span, other.first().map(|c| c.span.lo())); + + let mut skip_ind = false; + if self.print_trailing_comment(*try_span, other.first().map(|c| c.span.lo())) { + // if a trailing comment is printed at the very end, we have to manually + // adjust the offset to avoid having a double break. + self.break_offset_if_not_bol(0, self.ind); + skip_ind = true; + }; self.end(); // Handle 'catch' clauses @@ -1412,7 +1446,9 @@ impl<'ast> State<'_, 'ast> { for (pos, ast::TryCatchClause { name, args, block, span: catch_span }) in other.iter().delimited() { - self.handle_try_catch_indent(&mut should_break, block.is_empty(), pos); + if !pos.is_first || !skip_ind { + self.handle_try_catch_indent(&mut should_break, block.is_empty(), pos); + } self.ibox(0); self.print_inline_comments(catch_span.lo()); self.word("catch "); @@ -1421,7 +1457,7 @@ impl<'ast> State<'_, 'ast> { if let Some(name) = name { self.print_ident(name); } - self.print_parameter_list(args, ListFormat::Inline); + self.print_parameter_list(args, *catch_span, ListFormat::Inline); self.nbsp(); } self.print_block(block, *catch_span); @@ -1461,6 +1497,7 @@ impl<'ast> State<'_, 'ast> { self.word_nbsp(kw); self.print_tuple( std::slice::from_ref(cond), + cond.span, Self::print_expr, get_span!(), ListFormat::Consistent, @@ -1605,7 +1642,13 @@ impl<'ast> State<'_, 'ast> { self.print_yul_expr(expr); } yul::StmtKind::AssignMulti(paths, expr_call) => { - self.commasep(paths, |this, path| this.print_path(path), get_span!(()), false); + self.commasep( + paths, + stmt.span, + |this, path| this.print_path(path), + get_span!(()), + false, + ); self.word(" := "); self.neverbreak(); self.print_yul_expr_call(expr_call); @@ -1667,6 +1710,7 @@ impl<'ast> State<'_, 'ast> { self.print_ident(name); self.print_tuple( parameters, + span, Self::print_ident, get_span!(), ListFormat::Consistent, @@ -1674,7 +1718,7 @@ impl<'ast> State<'_, 'ast> { self.nbsp(); if !returns.is_empty() { self.word("-> "); - self.commasep(returns, Self::print_ident, get_span!(), false); + self.commasep(returns, stmt.span, Self::print_ident, get_span!(), false); self.nbsp(); } self.end(); @@ -1684,7 +1728,7 @@ impl<'ast> State<'_, 'ast> { yul::StmtKind::VarDecl(idents, expr) => { self.ibox(0); self.word("let "); - self.commasep(idents, Self::print_ident, get_span!(), false); + self.commasep(idents, stmt.span, Self::print_ident, get_span!(), false); if let Some(expr) = expr { self.word(" := "); self.neverbreak(); @@ -1716,7 +1760,13 @@ impl<'ast> State<'_, 'ast> { fn print_yul_expr_call(&mut self, expr: &'ast yul::ExprCall<'ast>) { let yul::ExprCall { name, arguments } = expr; self.print_ident(name); - self.print_tuple(arguments, Self::print_yul_expr, get_span!(), ListFormat::Consistent); + self.print_tuple( + arguments, + Span::DUMMY, + Self::print_yul_expr, + get_span!(), + ListFormat::Consistent, + ); } fn handle_try_catch_indent( From 43e1fc22432c1e040c5994fea94a64c6e10a6d3a Mon Sep 17 00:00:00 2001 From: 0xrusowsky <0xrusowsky@proton.me> Date: Mon, 23 Jun 2025 12:55:02 +0200 Subject: [PATCH 08/31] wip: array expr --- crates/fmt-2/src/comments.rs | 6 ++++-- crates/fmt-2/src/pp/helpers.rs | 4 ++-- crates/fmt-2/src/state.rs | 34 ++++++++++++++++++++-------------- 3 files changed, 26 insertions(+), 18 deletions(-) diff --git a/crates/fmt-2/src/comments.rs b/crates/fmt-2/src/comments.rs index fb13654f811fc..d4773eed4afe8 100644 --- a/crates/fmt-2/src/comments.rs +++ b/crates/fmt-2/src/comments.rs @@ -114,9 +114,11 @@ fn gather_comments(sf: &SourceFile) -> Vec { let code_to_the_right = !matches!(text[pos + token.len as usize..].chars().next(), Some('\r' | '\n')); let style = match (code_to_the_left, code_to_the_right) { - (_, true) => CommentStyle::Mixed, (false, false) => CommentStyle::Isolated, - (true, false) => CommentStyle::Trailing, + // Unlike with `Trailing` comments, which are always printed with a hardbreak, + // `Mixed` comments should be followed by a space and defer breaks to the + // printer. Because of that, non-isolated code blocks are labeled as mixed. + _ => CommentStyle::Mixed, }; let kind = CommentKind::Block; diff --git a/crates/fmt-2/src/pp/helpers.rs b/crates/fmt-2/src/pp/helpers.rs index dfe2a2f5de62a..5ffcc50b1ccf8 100644 --- a/crates/fmt-2/src/pp/helpers.rs +++ b/crates/fmt-2/src/pp/helpers.rs @@ -8,13 +8,13 @@ impl Printer { } pub fn hardbreak_if_not_bol(&mut self) { - if !self.is_beginning_of_line() { + if !self.is_bol_or_only_ind() { self.hardbreak(); } } pub fn space_if_not_bol(&mut self) { - if !self.is_beginning_of_line() { + if !self.is_bol_or_only_ind() { self.space(); } } diff --git a/crates/fmt-2/src/state.rs b/crates/fmt-2/src/state.rs index 22e7a094a9173..9bec81c7d4d56 100644 --- a/crates/fmt-2/src/state.rs +++ b/crates/fmt-2/src/state.rs @@ -90,10 +90,8 @@ impl<'sess> State<'sess, '_> { self.print_comments_inner(pos, true) } - /// Print comments inline without adding line breaks. - /// - /// Only works for trailing and mixed [`CommentStyle`]. - fn print_inline_comments(&mut self, pos: BytePos) -> bool { + /// Print mixed comments without adding line breaks. + fn print_mixed_comments(&mut self, pos: BytePos) -> bool { let mut printed = false; while let Some(cmnt) = self.peek_comment() { if cmnt.pos() >= pos { @@ -102,7 +100,7 @@ impl<'sess> State<'sess, '_> { let cmnt = self.next_comment().unwrap(); printed = true; - if matches!(cmnt.style, CommentStyle::Mixed | CommentStyle::Trailing) { + if matches!(cmnt.style, CommentStyle::Mixed) { if !self.last_token_is_space() && !self.is_bol_or_only_ind() { self.space(); } @@ -307,10 +305,10 @@ impl<'sess> State<'sess, '_> { } let first_pos = get_span(&values[0]).map(Span::lo); - self.print_trailing_comment(bracket_span, first_pos); + let skip_break = self.print_trailing_comment(bracket_span, first_pos); self.s.cbox(self.ind); - if !self.last_token_is_hardbreak() { + if !skip_break { self.zerobreak(); } if compact { @@ -336,7 +334,7 @@ impl<'sess> State<'sess, '_> { self.break_offset_if_not_bol(0, -self.ind); deind = false; }; - self.print_inline_comments(next_pos.unwrap_or(bracket_span.hi())); + self.print_mixed_comments(next_pos.unwrap_or(bracket_span.hi())); } if !is_last && !self.is_beginning_of_line() { self.space(); @@ -1151,22 +1149,30 @@ impl<'ast> State<'_, 'ast> { ast::ExprKind::Index(expr, kind) => { self.print_expr(expr); self.word("["); + self.cbox(0); match kind { ast::IndexKind::Index(expr) => { if let Some(expr) = expr { self.print_expr(expr); } } - ast::IndexKind::Range(expr, expr1) => { - if let Some(expr) = expr { - self.print_expr(expr); + ast::IndexKind::Range(expr0, expr1) => { + if let Some(expr0) = expr0 { + if let Some(cmnt) = self.peek_comment() { + if cmnt.span.hi() < expr0.span.lo() { + self.print_comments(expr0.span.lo()); + } + } + self.print_expr(expr0); } self.word(":"); if let Some(expr1) = expr1 { self.print_expr(expr1); } + self.print_comments(span.hi()); } } + self.end(); self.word("]"); } ast::ExprKind::Lit(lit, unit) => { @@ -1417,7 +1423,7 @@ impl<'ast> State<'_, 'ast> { let ast::TryCatchClause { args, block, span: try_span, .. } = first; self.ibox(0); self.word("try "); - self.print_inline_comments(expr.span.lo()); + self.print_mixed_comments(expr.span.lo()); self.print_expr(expr); self.print_comments_skip_ws( args.first().map(|p| p.span.lo()).unwrap_or_else(|| expr.span.lo()), @@ -1450,10 +1456,10 @@ impl<'ast> State<'_, 'ast> { self.handle_try_catch_indent(&mut should_break, block.is_empty(), pos); } self.ibox(0); - self.print_inline_comments(catch_span.lo()); + self.print_mixed_comments(catch_span.lo()); self.word("catch "); if !args.is_empty() { - self.print_inline_comments(args[0].span.lo()); + self.print_mixed_comments(args[0].span.lo()); if let Some(name) = name { self.print_ident(name); } From 9694fa1fc10b319f720badc990bb574d732956ca Mon Sep 17 00:00:00 2001 From: 0xrusowsky <0xrusowsky@proton.me> Date: Mon, 23 Jun 2025 16:21:56 +0200 Subject: [PATCH 09/31] finish arrays --- crates/fmt-2/src/state.rs | 112 ++++++++++++++++++++++++++------------ 1 file changed, 76 insertions(+), 36 deletions(-) diff --git a/crates/fmt-2/src/state.rs b/crates/fmt-2/src/state.rs index 9bec81c7d4d56..5224604e4fd11 100644 --- a/crates/fmt-2/src/state.rs +++ b/crates/fmt-2/src/state.rs @@ -90,29 +90,6 @@ impl<'sess> State<'sess, '_> { self.print_comments_inner(pos, true) } - /// Print mixed comments without adding line breaks. - fn print_mixed_comments(&mut self, pos: BytePos) -> bool { - let mut printed = false; - while let Some(cmnt) = self.peek_comment() { - if cmnt.pos() >= pos { - break; - } - let cmnt = self.next_comment().unwrap(); - printed = true; - - if matches!(cmnt.style, CommentStyle::Mixed) { - if !self.last_token_is_space() && !self.is_bol_or_only_ind() { - self.space(); - } - for line in cmnt.lines { - self.word(line); - } - self.space(); - } - } - printed - } - fn print_comments_inner(&mut self, pos: BytePos, skip_ws: bool) -> Option { let mut has_comment = None; while let Some(cmnt) = self.peek_comment() { @@ -204,6 +181,13 @@ impl<'sess> State<'sess, '_> { self.comments.peek() } + fn peek_comment_before<'b>(&'b self, pos: BytePos) -> Option<&'b Comment> + where + 'sess: 'b, + { + self.comments.peek().filter(|c| c.pos() < pos) + } + fn next_comment(&mut self) -> Option { self.comments.next() } @@ -217,6 +201,29 @@ impl<'sess> State<'sess, '_> { false } + /// Print mixed comments without adding line breaks. + fn print_mixed_comments(&mut self, pos: BytePos) -> bool { + let mut printed = false; + while let Some(cmnt) = self.peek_comment() { + if cmnt.pos() >= pos { + break; + } + let cmnt = self.next_comment().unwrap(); + printed = true; + + if matches!(cmnt.style, CommentStyle::Mixed) { + if !self.last_token_is_space() && !self.is_bol_or_only_ind() { + self.space(); + } + for line in cmnt.lines { + self.word(line); + } + self.space(); + } + } + printed + } + fn print_remaining_comments(&mut self) { // If there aren't any remaining comments, then we need to manually // make sure there is a line break at the end. @@ -285,7 +292,7 @@ impl<'sess> State<'sess, '_> { S: FnMut(&T) -> Option, { self.word("["); - self.commasep(values, span, print, get_span, true); + self.commasep(values, span, print, get_span, false); self.word("]"); } @@ -306,7 +313,6 @@ impl<'sess> State<'sess, '_> { let first_pos = get_span(&values[0]).map(Span::lo); let skip_break = self.print_trailing_comment(bracket_span, first_pos); - self.s.cbox(self.ind); if !skip_break { self.zerobreak(); @@ -314,7 +320,8 @@ impl<'sess> State<'sess, '_> { if compact { self.s.cbox(0); } - let mut deind = true; + + let mut skip_break = false; for (i, value) in values.iter().enumerate() { self.s.ibox(0); let span = get_span(value); @@ -332,20 +339,20 @@ impl<'sess> State<'sess, '_> { // if a trailing comment is printed at the very end, we have to manually adjust // the offset to avoid having a double break. self.break_offset_if_not_bol(0, -self.ind); - deind = false; + skip_break = true; }; self.print_mixed_comments(next_pos.unwrap_or(bracket_span.hi())); } + self.end(); if !is_last && !self.is_beginning_of_line() { self.space(); } - self.end(); } if compact { self.end(); } - if deind { + if !skip_break { self.zerobreak(); self.s.offset(-self.ind); } @@ -1149,7 +1156,10 @@ impl<'ast> State<'_, 'ast> { ast::ExprKind::Index(expr, kind) => { self.print_expr(expr); self.word("["); - self.cbox(0); + self.s.cbox(self.ind); + self.zerobreak(); + + let mut skip_break = false; match kind { ast::IndexKind::Index(expr) => { if let Some(expr) = expr { @@ -1158,20 +1168,50 @@ impl<'ast> State<'_, 'ast> { } ast::IndexKind::Range(expr0, expr1) => { if let Some(expr0) = expr0 { - if let Some(cmnt) = self.peek_comment() { - if cmnt.span.hi() < expr0.span.lo() { - self.print_comments(expr0.span.lo()); - } - } + self.print_comments(expr0.span.lo()); self.print_expr(expr0); } self.word(":"); if let Some(expr1) = expr1 { + self.s.ibox(self.ind); + if self.peek_comment_before(expr1.span.lo()).is_some() { + self.space(); + } else if expr0.is_some() { + self.zerobreak(); + } + self.print_comments(expr1.span.lo()); self.print_expr(expr1); } - self.print_comments(span.hi()); + + let mut style = None; + if let Some(cmnt) = self.peek_comment_before(span.hi()) { + if matches!(cmnt.style, CommentStyle::Mixed) { + self.space(); + } + style = self.print_comments(span.hi()); + skip_break = true; + } + + // Manually revert indentation if there is `expr1` and/or comments. + if skip_break && expr1.is_some() { + self.break_offset_if_not_bol(0, -2 * self.ind); + self.end(); + // if a trailing comment is printed at the very end, we have to manually + // adjust the offset to avoid having a double break. + if !matches!(style.unwrap(), CommentStyle::Trailing) { + self.break_offset_if_not_bol(0, -self.ind); + } + } else if skip_break { + self.break_offset_if_not_bol(0, -self.ind); + } else if expr1.is_some() { + self.end(); + } } } + if !skip_break { + self.zerobreak(); + self.s.offset(-self.ind); + } self.end(); self.word("]"); } From 2322863d0c4c6fa4fa36ceff56fd442fb221a034 Mon Sep 17 00:00:00 2001 From: 0xrusowsky <0xrusowsky@proton.me> Date: Mon, 23 Jun 2025 18:01:19 +0200 Subject: [PATCH 10/31] block comments --- crates/fmt-2/src/comments.rs | 33 ++++++++++++++++++++++++--------- 1 file changed, 24 insertions(+), 9 deletions(-) diff --git a/crates/fmt-2/src/comments.rs b/crates/fmt-2/src/comments.rs index d4773eed4afe8..79f9ff9861c08 100644 --- a/crates/fmt-2/src/comments.rs +++ b/crates/fmt-2/src/comments.rs @@ -31,18 +31,33 @@ fn all_whitespace(s: &str, col: CharPos) -> Option { Some(idx) } +/// Returns `Some(k)` where `k` is the byte offset of the first non-whitespace char preceded by a +/// whitespace. Returns `k = 0` if `s` starts with a non-whitespace char. If `s` only contains +/// whitespaces, returns `None`. +fn first_non_whitespace(s: &str) -> Option { + let mut len = 0; + for (i, ch) in s.char_indices() { + if ch.is_whitespace() { + len = ch.len_utf8() + } else { + return if i == 0 { Some(0) } else { Some(i - len) }; + } + } + None +} + +/// Returns a slice of `s` with a whitespace prefix removed based on `col`. If the first `col` chars +/// of `s` are all whitespace, returns a slice starting after that prefix. Otherwise, +/// returns a slice that leaves at most one leading whitespace char. fn trim_whitespace_prefix(s: &str, col: CharPos) -> &str { let len = s.len(); - match all_whitespace(s, col) { - Some(col) => { - if col < len { - &s[col..] - } else { - "" - } - } - None => s, + if let Some(col) = all_whitespace(s, col) { + return if col < len { &s[col..] } else { "" }; + } + if let Some(col) = first_non_whitespace(s) { + return &s[col..]; } + s } fn split_block_comment_into_lines(text: &str, col: CharPos) -> Vec { From e174b3c1c1b562d6b7f4c9b1a51639f469326c1b Mon Sep 17 00:00:00 2001 From: 0xrusowsky <0xrusowsky@proton.me> Date: Mon, 23 Jun 2025 18:49:21 +0200 Subject: [PATCH 11/31] doc block comments --- crates/fmt-2/src/comments.rs | 36 +++++++++++++++++++++++++++++------- 1 file changed, 29 insertions(+), 7 deletions(-) diff --git a/crates/fmt-2/src/comments.rs b/crates/fmt-2/src/comments.rs index 79f9ff9861c08..1392a0d424e07 100644 --- a/crates/fmt-2/src/comments.rs +++ b/crates/fmt-2/src/comments.rs @@ -1,3 +1,5 @@ +use crate::iter::IterDelimited; + use super::comment::{Comment, CommentStyle}; use solar_parse::{ ast::{CommentKind, Span}, @@ -5,6 +7,7 @@ use solar_parse::{ lexer::token::RawTokenKind as TokenKind, }; use std::fmt; +use tracing::instrument::WithSubscriber; pub struct Comments { comments: std::vec::IntoIter, @@ -49,7 +52,7 @@ fn first_non_whitespace(s: &str) -> Option { /// Returns a slice of `s` with a whitespace prefix removed based on `col`. If the first `col` chars /// of `s` are all whitespace, returns a slice starting after that prefix. Otherwise, /// returns a slice that leaves at most one leading whitespace char. -fn trim_whitespace_prefix(s: &str, col: CharPos) -> &str { +fn normalize_block_comment(s: &str, col: CharPos) -> &str { let len = s.len(); if let Some(col) = all_whitespace(s, col) { return if col < len { &s[col..] } else { "" }; @@ -60,14 +63,33 @@ fn trim_whitespace_prefix(s: &str, col: CharPos) -> &str { s } -fn split_block_comment_into_lines(text: &str, col: CharPos) -> Vec { +fn split_block_comment_into_lines(text: &str, is_doc: bool, col: CharPos) -> Vec { let mut res: Vec = vec![]; let mut lines = text.lines(); - // just push the first line + // Just push the first line res.extend(lines.next().map(|it| it.to_string())); - // for other lines, strip common whitespace prefix - for line in lines { - res.push(trim_whitespace_prefix(line, col).to_string()) + // For other lines, strip common whitespace prefix + for (pos, line) in lines.into_iter().delimited() { + let mut line = normalize_block_comment(line, col).to_string(); + // For regular block comments, just normalize whitespace + if !is_doc { + res.push(line); + } + // Doc block comment lines must have the ` *` decorator + else { + if !pos.is_last { + if line.is_empty() { + line = format!(" *"); + } else if line.starts_with("*") { + line = format!(" {line}"); + } else if !line.starts_with(" *") { + line = format!(" * {line}"); + } + res.push(line); + } else if line.trim() == "*/" { + res.push(" */".to_string()) + } + } } res } @@ -143,7 +165,7 @@ fn gather_comments(sf: &SourceFile) -> Vec { let line_begin_pos = (line_begin_in_file - start_bpos).to_usize(); let col = CharPos(text[line_begin_pos..pos].chars().count()); - let lines = split_block_comment_into_lines(token_text, col); + let lines = split_block_comment_into_lines(token_text, is_doc, col); comments.push(Comment { is_doc, kind, style, lines, span }) } TokenKind::LineComment { is_doc } => { From 20d91e2c3f835467ae5d61f67a2996d4de2d8456 Mon Sep 17 00:00:00 2001 From: 0xrusowsky <0xrusowsky@proton.me> Date: Tue, 24 Jun 2025 08:03:44 +0200 Subject: [PATCH 12/31] ternary operators --- crates/fmt-2/src/comments.rs | 3 --- crates/fmt-2/src/state.rs | 29 +++++++++++++++++++++++++++-- 2 files changed, 27 insertions(+), 5 deletions(-) diff --git a/crates/fmt-2/src/comments.rs b/crates/fmt-2/src/comments.rs index 1392a0d424e07..4cfe9c086badc 100644 --- a/crates/fmt-2/src/comments.rs +++ b/crates/fmt-2/src/comments.rs @@ -7,7 +7,6 @@ use solar_parse::{ lexer::token::RawTokenKind as TokenKind, }; use std::fmt; -use tracing::instrument::WithSubscriber; pub struct Comments { comments: std::vec::IntoIter, @@ -66,9 +65,7 @@ fn normalize_block_comment(s: &str, col: CharPos) -> &str { fn split_block_comment_into_lines(text: &str, is_doc: bool, col: CharPos) -> Vec { let mut res: Vec = vec![]; let mut lines = text.lines(); - // Just push the first line res.extend(lines.next().map(|it| it.to_string())); - // For other lines, strip common whitespace prefix for (pos, line) in lines.into_iter().delimited() { let mut line = normalize_block_comment(line, col).to_string(); // For regular block comments, just normalize whitespace diff --git a/crates/fmt-2/src/state.rs b/crates/fmt-2/src/state.rs index 5224604e4fd11..5e6f12ce9b17c 100644 --- a/crates/fmt-2/src/state.rs +++ b/crates/fmt-2/src/state.rs @@ -97,6 +97,7 @@ impl<'sess> State<'sess, '_> { break; } let cmnt = self.next_comment().unwrap(); + println!("{cmnt:?}"); if skip_ws && cmnt.style == CommentStyle::BlankLine { continue; } @@ -1237,13 +1238,37 @@ impl<'ast> State<'_, 'ast> { } ast::ExprKind::Ternary(cond, then, els) => { self.s.cbox(self.ind); + // conditional expression + self.s.ibox(0); + self.print_comments(cond.span.lo()); self.print_expr(cond); - self.space(); + let cmnt = self.peek_comment_before(then.span.lo()); + if cmnt.is_some() { + self.space(); + } + self.print_comments(then.span.lo()); + self.end(); + if !self.is_bol_or_only_ind() { + self.space(); + } + // then expression + self.s.ibox(0); self.word("? "); self.print_expr(then); - self.space(); + let cmnt = self.peek_comment_before(els.span.lo()); + if cmnt.is_some() { + self.space(); + } + self.print_comments(els.span.lo()); + self.end(); + if !self.is_bol_or_only_ind() { + self.space(); + } + // then expression + self.s.ibox(0); self.word(": "); self.print_expr(els); + self.end(); self.neverbreak(); self.s.offset(-self.ind); self.end(); From 361db142d44330f13ae6304e8d830544c05bae13 Mon Sep 17 00:00:00 2001 From: 0xrusowsky <0xrusowsky@proton.me> Date: Fri, 27 Jun 2025 12:46:19 +0200 Subject: [PATCH 13/31] wip: fn header --- Cargo.lock | 40 +- Cargo.toml | 8 +- crates/fmt-2/src/comments.rs | 6 +- crates/fmt-2/src/lib.rs | 1 + crates/fmt-2/src/pp/convenience.rs | 12 +- crates/fmt-2/src/pp/helpers.rs | 12 +- crates/fmt-2/src/state.rs | 252 ++++-- .../FunctionDefinition/all-params.fmt.sol | 732 ------------------ .../testdata/FunctionDefinition/all.fmt.sol | 730 ----------------- .../fmt/testdata/FunctionDefinition/fmt.sol | 2 +- .../testdata/FunctionDefinition/original.sol | 1 - .../override-spacing.fmt.sol | 710 ----------------- .../FunctionDefinition/params-first.fmt.sol | 716 ----------------- .../FunctionDefinition/params-multi.fmt.sol | 710 ----------------- 14 files changed, 228 insertions(+), 3704 deletions(-) delete mode 100644 crates/fmt/testdata/FunctionDefinition/all-params.fmt.sol delete mode 100644 crates/fmt/testdata/FunctionDefinition/all.fmt.sol delete mode 100644 crates/fmt/testdata/FunctionDefinition/override-spacing.fmt.sol delete mode 100644 crates/fmt/testdata/FunctionDefinition/params-first.fmt.sol delete mode 100644 crates/fmt/testdata/FunctionDefinition/params-multi.fmt.sol diff --git a/Cargo.lock b/Cargo.lock index cce654320611b..b1c22079b7d11 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2793,7 +2793,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "117725a109d387c937a1533ce01b450cbde6b88abceea8473c4d7a85853cda3c" dependencies = [ "lazy_static", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -3596,7 +3596,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cea14ef9355e3beab063703aa9dab15afd25f0667c341310c1e5274bb1d0da18" dependencies = [ "libc", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -3705,7 +3705,7 @@ checksum = "0ce92ff622d6dadf7349484f42c93271a0d49b7cc4d466a936405bacbe10aa78" dependencies = [ "cfg-if", "rustix 1.0.7", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -5509,7 +5509,7 @@ checksum = "e04d7f318608d35d4b61ddd75cbdaee86b023ebe2bd5a66ee0915f0bf93095a9" dependencies = [ "hermit-abi", "libc", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -5572,7 +5572,7 @@ dependencies = [ "portable-atomic", "portable-atomic-util", "serde", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -7282,7 +7282,7 @@ dependencies = [ "once_cell", "socket2", "tracing", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -7959,7 +7959,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys 0.4.15", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -7972,7 +7972,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys 0.9.4", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -8617,7 +8617,7 @@ dependencies = [ [[package]] name = "solar-ast" version = "0.1.4" -source = "git+https://github.com/paradigmxyz/solar?branch=main#2020b272fb5d805f6487c9b5b439ab40cc79671c" +source = "git+https://github.com/0xrusowsky/solar?branch=rusowsky%2Ffn-header-spans#919763125cc65669611bc66a915ea45da2093806" dependencies = [ "alloy-primitives", "bumpalo", @@ -8635,7 +8635,7 @@ dependencies = [ [[package]] name = "solar-config" version = "0.1.4" -source = "git+https://github.com/paradigmxyz/solar?branch=main#2020b272fb5d805f6487c9b5b439ab40cc79671c" +source = "git+https://github.com/0xrusowsky/solar?branch=rusowsky%2Ffn-header-spans#919763125cc65669611bc66a915ea45da2093806" dependencies = [ "strum 0.27.1", ] @@ -8643,7 +8643,7 @@ dependencies = [ [[package]] name = "solar-data-structures" version = "0.1.4" -source = "git+https://github.com/paradigmxyz/solar?branch=main#2020b272fb5d805f6487c9b5b439ab40cc79671c" +source = "git+https://github.com/0xrusowsky/solar?branch=rusowsky%2Ffn-header-spans#919763125cc65669611bc66a915ea45da2093806" dependencies = [ "bumpalo", "index_vec", @@ -8657,7 +8657,7 @@ dependencies = [ [[package]] name = "solar-interface" version = "0.1.4" -source = "git+https://github.com/paradigmxyz/solar?branch=main#2020b272fb5d805f6487c9b5b439ab40cc79671c" +source = "git+https://github.com/0xrusowsky/solar?branch=rusowsky%2Ffn-header-spans#919763125cc65669611bc66a915ea45da2093806" dependencies = [ "annotate-snippets", "anstream", @@ -8667,7 +8667,7 @@ dependencies = [ "derive_more 2.0.1", "dunce", "inturn", - "itertools 0.10.5", + "itertools 0.12.1", "itoa", "match_cfg", "normalize-path", @@ -8678,7 +8678,7 @@ dependencies = [ "solar-config", "solar-data-structures", "solar-macros", - "thiserror 1.0.69", + "thiserror 2.0.12", "tracing", "unicode-width 0.2.0", ] @@ -8686,7 +8686,7 @@ dependencies = [ [[package]] name = "solar-macros" version = "0.1.4" -source = "git+https://github.com/paradigmxyz/solar?branch=main#2020b272fb5d805f6487c9b5b439ab40cc79671c" +source = "git+https://github.com/0xrusowsky/solar?branch=rusowsky%2Ffn-header-spans#919763125cc65669611bc66a915ea45da2093806" dependencies = [ "proc-macro2", "quote", @@ -8696,12 +8696,12 @@ dependencies = [ [[package]] name = "solar-parse" version = "0.1.4" -source = "git+https://github.com/paradigmxyz/solar?branch=main#2020b272fb5d805f6487c9b5b439ab40cc79671c" +source = "git+https://github.com/0xrusowsky/solar?branch=rusowsky%2Ffn-header-spans#919763125cc65669611bc66a915ea45da2093806" dependencies = [ "alloy-primitives", "bitflags 2.9.1", "bumpalo", - "itertools 0.10.5", + "itertools 0.12.1", "memchr", "num-bigint", "num-rational", @@ -8716,7 +8716,7 @@ dependencies = [ [[package]] name = "solar-sema" version = "0.1.4" -source = "git+https://github.com/paradigmxyz/solar?branch=main#2020b272fb5d805f6487c9b5b439ab40cc79671c" +source = "git+https://github.com/0xrusowsky/solar?branch=rusowsky%2Ffn-header-spans#919763125cc65669611bc66a915ea45da2093806" dependencies = [ "alloy-json-abi", "alloy-primitives", @@ -9108,7 +9108,7 @@ dependencies = [ "getrandom 0.3.3", "once_cell", "rustix 1.0.7", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -10309,7 +10309,7 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" dependencies = [ - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index e810f505733e0..abbe3ff099050 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -364,10 +364,10 @@ idna_adapter = "=1.1.0" zip-extract = "=0.2.1" [patch.crates-io] -solar-parse = { git = "https://github.com/paradigmxyz/solar", branch = "main" } -solar-sema = { git = "https://github.com/paradigmxyz/solar", branch = "main" } -solar-ast = { git = "https://github.com/paradigmxyz/solar", branch = "main" } -solar-interface = { git = "https://github.com/paradigmxyz/solar", branch = "main" } +solar-parse = { git = "https://github.com/0xrusowsky/solar", branch = "rusowsky/fn-header-spans" } +solar-sema = { git = "https://github.com/0xrusowsky/solar", branch = "rusowsky/fn-header-spans" } +solar-ast = { git = "https://github.com/0xrusowsky/solar", branch = "rusowsky/fn-header-spans" } +solar-interface = { git = "https://github.com/0xrusowsky/solar", branch = "rusowsky/fn-header-spans" } figment = { git = "https://github.com/DaniPopes/Figment", branch = "less-mono" } diff --git a/crates/fmt-2/src/comments.rs b/crates/fmt-2/src/comments.rs index 4cfe9c086badc..1dabeeea717df 100644 --- a/crates/fmt-2/src/comments.rs +++ b/crates/fmt-2/src/comments.rs @@ -209,17 +209,17 @@ impl Comments { pub fn trailing_comment( &mut self, sm: &SourceMap, - span: Span, + span_pos: BytePos, next_pos: Option, ) -> Option { if let Some(cmnt) = self.peek() { if cmnt.style != CommentStyle::Trailing { return None; } - let span_line = sm.lookup_char_pos(span.hi()); + let span_line = sm.lookup_char_pos(span_pos); let comment_line = sm.lookup_char_pos(cmnt.pos()); let next = next_pos.unwrap_or_else(|| cmnt.pos() + BytePos(1)); - if span.hi() < cmnt.pos() && cmnt.pos() < next && span_line.line == comment_line.line { + if span_pos < cmnt.pos() && cmnt.pos() < next && span_line.line == comment_line.line { return Some(self.next().unwrap()); } } diff --git a/crates/fmt-2/src/lib.rs b/crates/fmt-2/src/lib.rs index 53d0242e0a87e..a025ef88c71b1 100644 --- a/crates/fmt-2/src/lib.rs +++ b/crates/fmt-2/src/lib.rs @@ -126,6 +126,7 @@ fn format_inner( if first_result.is_err() { return first_result; } + return first_result; let Some(first_formatted) = first_result.ok_ref() else { return first_result }; // Second pass formatting diff --git a/crates/fmt-2/src/pp/convenience.rs b/crates/fmt-2/src/pp/convenience.rs index 91f8fdee31807..7aa8b57aa1d43 100644 --- a/crates/fmt-2/src/pp/convenience.rs +++ b/crates/fmt-2/src/pp/convenience.rs @@ -64,10 +64,13 @@ impl Printer { pub fn last_token_is_space(&self) -> bool { if let Some(token) = self.last_token() { - return token.is_space(); + if token.is_space() { + return true; + } } - false + let res = self.out.ends_with(" "); + res } pub fn is_beginning_of_line(&self) -> bool { @@ -77,10 +80,13 @@ impl Printer { } } + /// Identifies whether the current position is: + /// 1. the beginning of a line (empty) + /// 2. a line with only indendation (just whitespaces) pub fn is_bol_or_only_ind(&self) -> bool { for i in self.buf.index_range().rev() { let token = &self.buf[i].token; - if token.is_hardbreak() || matches!(token, Token::Begin(_)) { + if token.is_hardbreak() { return true; } if Self::token_has_non_whitespace_content(token) { diff --git a/crates/fmt-2/src/pp/helpers.rs b/crates/fmt-2/src/pp/helpers.rs index 5ffcc50b1ccf8..6cb8f41217586 100644 --- a/crates/fmt-2/src/pp/helpers.rs +++ b/crates/fmt-2/src/pp/helpers.rs @@ -1,4 +1,4 @@ -use super::Printer; +use super::{Printer, Token}; use std::borrow::Cow; impl Printer { @@ -7,9 +7,17 @@ impl Printer { self.space(); } + /// Adds a new hardbrak if not at the beginning of the line. + /// If there was a buffered break token, replaces it (ensures hardbreak) keeping the offset. pub fn hardbreak_if_not_bol(&mut self) { if !self.is_bol_or_only_ind() { - self.hardbreak(); + match self.last_token_still_buffered() { + Some(Token::Break(last)) => { + self.replace_last_token_still_buffered(Self::hardbreak_tok_offset(last.offset)) + } + // Some(Token::Begin(_) | Token::End) => {} + _ => self.hardbreak(), + } } } diff --git a/crates/fmt-2/src/state.rs b/crates/fmt-2/src/state.rs index 5e6f12ce9b17c..3ba4c1000d4a6 100644 --- a/crates/fmt-2/src/state.rs +++ b/crates/fmt-2/src/state.rs @@ -5,7 +5,7 @@ use super::{ }; use crate::{ iter::{IterDelimited, IteratorPosition}, - pp::BreakToken, + pp::{BreakToken, Printer}, FormatterConfig, InlineConfig, }; use foundry_config::fmt as config; @@ -15,7 +15,7 @@ use solar_parse::{ interface::{BytePos, SourceMap}, Cursor, }; -use std::borrow::Cow; +use std::{borrow::Cow, collections::HashMap}; /// Formatting style for comma-separated lists #[derive(Debug, Clone, Copy, PartialEq, Eq)] @@ -91,27 +91,26 @@ impl<'sess> State<'sess, '_> { } fn print_comments_inner(&mut self, pos: BytePos, skip_ws: bool) -> Option { - let mut has_comment = None; + let mut last_style = None; while let Some(cmnt) = self.peek_comment() { if cmnt.pos() >= pos { break; } let cmnt = self.next_comment().unwrap(); - println!("{cmnt:?}"); if skip_ws && cmnt.style == CommentStyle::BlankLine { continue; } - has_comment = Some(cmnt.style); + last_style = Some(cmnt.style); self.print_comment(cmnt); } - has_comment + last_style } fn print_comment(&mut self, mut cmnt: Comment) { match cmnt.style { CommentStyle::Mixed => { // TODO(dani): ? - if !self.is_beginning_of_line() { + if !self.is_bol_or_only_ind() { self.zerobreak(); // self.space(); } @@ -193,8 +192,8 @@ impl<'sess> State<'sess, '_> { self.comments.next() } - fn print_trailing_comment(&mut self, span: Span, next_pos: Option) -> bool { - if let Some(cmnt) = self.comments.trailing_comment(self.sm, span, next_pos) { + fn print_trailing_comment(&mut self, span_pos: BytePos, next_pos: Option) -> bool { + if let Some(cmnt) = self.comments.trailing_comment(self.sm, span_pos, next_pos) { self.print_comment(cmnt); return true; } @@ -202,29 +201,6 @@ impl<'sess> State<'sess, '_> { false } - /// Print mixed comments without adding line breaks. - fn print_mixed_comments(&mut self, pos: BytePos) -> bool { - let mut printed = false; - while let Some(cmnt) = self.peek_comment() { - if cmnt.pos() >= pos { - break; - } - let cmnt = self.next_comment().unwrap(); - printed = true; - - if matches!(cmnt.style, CommentStyle::Mixed) { - if !self.last_token_is_space() && !self.is_bol_or_only_ind() { - self.space(); - } - for line in cmnt.lines { - self.word(line); - } - self.space(); - } - } - printed - } - fn print_remaining_comments(&mut self) { // If there aren't any remaining comments, then we need to manually // make sure there is a line break at the end. @@ -312,9 +288,9 @@ impl<'sess> State<'sess, '_> { return; } - let first_pos = get_span(&values[0]).map(Span::lo); - let skip_break = self.print_trailing_comment(bracket_span, first_pos); self.s.cbox(self.ind); + let first_pos = get_span(&values[0]).map(Span::lo); + let skip_break = self.print_trailing_comment(bracket_span.lo(), first_pos); if !skip_break { self.zerobreak(); } @@ -324,28 +300,28 @@ impl<'sess> State<'sess, '_> { let mut skip_break = false; for (i, value) in values.iter().enumerate() { + let is_last = i == values.len() - 1; self.s.ibox(0); let span = get_span(value); if let Some(span) = span { - self.print_comments(span.lo()); + self.print_comments_skip_ws(span.lo()); } print(self, value); - let is_last = i == values.len() - 1; if !is_last { self.word(","); } - if let Some(span) = span { - let next_pos = if is_last { None } else { get_span(&values[i + 1]).map(Span::lo) }; - if self.print_trailing_comment(span, next_pos) && is_last { + let next_pos = if is_last { None } else { get_span(&values[i + 1]).map(Span::lo) }; + self.print_comments_skip_ws(next_pos.unwrap_or_else(|| bracket_span.hi())); + if self.is_bol_or_only_ind() { + if is_last { // if a trailing comment is printed at the very end, we have to manually adjust // the offset to avoid having a double break. self.break_offset_if_not_bol(0, -self.ind); skip_break = true; - }; - self.print_mixed_comments(next_pos.unwrap_or(bracket_span.hi())); - } + } + }; self.end(); - if !is_last && !self.is_beginning_of_line() { + if !is_last && !self.is_bol_or_only_ind() { self.space(); } } @@ -439,7 +415,7 @@ impl<'ast> State<'_, 'ast> { ast::ItemKind::Event(event) => self.print_event(event), } self.print_comments(span.hi()); - self.print_trailing_comment(span, None); + self.print_trailing_comment(span.hi(), None); self.hardbreak_if_not_bol(); } @@ -603,7 +579,7 @@ impl<'ast> State<'_, 'ast> { self.hardbreak_if_nonempty(); for var in fields.iter() { self.print_var_def(var); - self.print_trailing_comment(var.span, None); + self.print_trailing_comment(var.span.hi(), None); self.hardbreak_if_not_bol(); } self.print_comments_skip_ws(span.hi()); @@ -624,7 +600,7 @@ impl<'ast> State<'_, 'ast> { if !pos.is_last { self.word(","); } - self.print_trailing_comment(ident.span, None); + self.print_trailing_comment(ident.span.hi(), None); self.hardbreak_if_not_bol(); } self.print_comments_skip_ws(span.hi()); @@ -647,12 +623,16 @@ impl<'ast> State<'_, 'ast> { let ast::FunctionHeader { name, ref parameters, + parameters_span, + visibility_span, visibility, + state_mutability_span, state_mutability, ref modifiers, virtual_, ref override_, ref returns, + returns_span, .. } = *header; self.cbox(0); @@ -663,35 +643,117 @@ impl<'ast> State<'_, 'ast> { self.nbsp(); self.print_ident(&name); } - self.print_parameter_list(parameters, header.span, ListFormat::Consistent); + self.print_parameter_list(parameters, parameters_span, ListFormat::Consistent); self.end(); - // Attributes. + // Cache attribute comments first, as they can be wrongly ordered. + let (mut attrib_comments, mut pending) = (Vec::new(), None); + for cmnt in self.comments.iter() { + if cmnt.pos() >= returns_span.lo() { + break; + } + + match pending { + Some(ref p) => pending = Some(p + 1), + None => pending = Some(0), + } + } + while let Some(p) = pending { + if p == 0 { + pending = None; + } else { + pending = Some(p - 1); + } + let cmnt = self.next_comment().unwrap(); + if cmnt.style == CommentStyle::BlankLine { + continue; + } + attrib_comments.push(cmnt); + } + + let mut attributes: Vec> = Vec::new(); + if let Some(v) = visibility { + attributes.push(AttributeInfo { + kind: AttributeKind::Visibility(v), + span: visibility_span.unwrap(), + }); + } + attributes.push(AttributeInfo { + kind: AttributeKind::StateMutability(state_mutability), + span: state_mutability_span, + }); + if let Some(v) = virtual_ { + attributes.push(AttributeInfo { kind: AttributeKind::Virtual, span: v }); + } + if let Some(o) = override_ { + attributes.push(AttributeInfo { kind: AttributeKind::Override(o), span: o.span }); + } + for m in modifiers.iter() { + attributes.push(AttributeInfo { kind: AttributeKind::Modifier(m), span: m.span() }); + } + attributes.sort_by_key(|attr| attr.span.lo()); + + // Claim comments for each source-ordered attribute + let mut map: HashMap, Vec)> = HashMap::new(); + for a in 0..attributes.len() { + let is_last = a == attributes.len() - 1; + let mut before = Vec::new(); + let mut after = Vec::new(); + + let before_limit = attributes[a].span.lo(); + let after_limit = + if !is_last { attributes[a + 1].span.lo() } else { returns_span.lo() }; + let mut c = 0; + while c < attrib_comments.len() { + if attrib_comments[c].pos() <= before_limit { + before.push(attrib_comments.remove(c)); + } else if (after.is_empty() || is_last) && attrib_comments[c].pos() <= after_limit { + after.push(attrib_comments.remove(c)); + } else { + c += 1; + } + } + map.insert(before_limit, (before, after)); + } + self.s.cbox(self.ind); - if let Some(visibility) = visibility { - self.space(); - self.word(visibility.to_str()); + if let Some(v) = visibility { + self.print_attribute(visibility_span.unwrap(), &mut map, &mut |s| s.word(v.to_str())); } if state_mutability != ast::StateMutability::NonPayable { - self.space(); - self.word(state_mutability.to_str()); + if !self.is_bol_or_only_ind() && !self.last_token_is_space() { + self.space(); + } + self.print_attribute(state_mutability_span, &mut map, &mut |s| { + s.word(state_mutability.to_str()) + }); } - if virtual_ { - self.space(); - self.word("virtual"); + if let Some(virtual_) = virtual_ { + if !self.is_bol_or_only_ind() && !self.last_token_is_space() { + self.space(); + } + self.print_attribute(virtual_, &mut map, &mut |s| s.word("virtual")); } if let Some(override_) = override_ { - self.space(); - self.print_override(override_); + if !self.is_bol_or_only_ind() && !self.last_token_is_space() { + self.space(); + } + self.print_attribute(override_.span, &mut map, &mut |s| s.print_override(override_)); } - for modifier in modifiers.iter() { - self.space(); - self.print_modifier_call(modifier, self.is_modifier_a_base_contract(kind, modifier)); + for m in attributes.iter().filter(|a| matches!(a.kind, AttributeKind::Modifier(_))) { + if let AttributeKind::Modifier(modifier) = m.kind { + let is_base = self.is_modifier_a_base_contract(kind, modifier); + self.print_attribute(m.span, &mut map, &mut |s| { + s.print_modifier_call(modifier, is_base) + }); + } } if !returns.is_empty() { - self.space(); + if !self.is_bol_or_only_ind() && !self.last_token_is_space() { + self.space(); + } self.word("returns "); - self.print_parameter_list(returns, header.span, ListFormat::Consistent); + self.print_parameter_list(returns, returns_span, ListFormat::Consistent); } if let Some(body) = body { @@ -861,7 +923,7 @@ impl<'ast> State<'_, 'ast> { } if !pos.is_last { self.space_if_not_bol(); - self.print_trailing_comment(span, None); + self.print_trailing_comment(span.hi(), None); } else { self.neverbreak(); } @@ -1488,7 +1550,7 @@ impl<'ast> State<'_, 'ast> { let ast::TryCatchClause { args, block, span: try_span, .. } = first; self.ibox(0); self.word("try "); - self.print_mixed_comments(expr.span.lo()); + self.print_comments(expr.span.lo()); self.print_expr(expr); self.print_comments_skip_ws( args.first().map(|p| p.span.lo()).unwrap_or_else(|| expr.span.lo()), @@ -1504,7 +1566,9 @@ impl<'ast> State<'_, 'ast> { self.print_block(block, *try_span); let mut skip_ind = false; - if self.print_trailing_comment(*try_span, other.first().map(|c| c.span.lo())) { + if self + .print_trailing_comment(try_span.hi(), other.first().map(|c| c.span.lo())) + { // if a trailing comment is printed at the very end, we have to manually // adjust the offset to avoid having a double break. self.break_offset_if_not_bol(0, self.ind); @@ -1521,10 +1585,10 @@ impl<'ast> State<'_, 'ast> { self.handle_try_catch_indent(&mut should_break, block.is_empty(), pos); } self.ibox(0); - self.print_mixed_comments(catch_span.lo()); + self.print_comments(catch_span.lo()); self.word("catch "); if !args.is_empty() { - self.print_mixed_comments(args[0].span.lo()); + self.print_comments(args[0].span.lo()); if let Some(name) = name { self.print_ident(name); } @@ -1554,7 +1618,7 @@ impl<'ast> State<'_, 'ast> { self.word(";"); } self.print_comments(stmt.span.hi()); - self.print_trailing_comment(stmt.span, None); + self.print_trailing_comment(stmt.span.hi(), None); } fn print_if_no_else(&mut self, cond: &'ast ast::Expr<'ast>, then: &'ast ast::Stmt<'ast>) { @@ -1753,7 +1817,7 @@ impl<'ast> State<'_, 'ast> { self.word("switch "); self.print_yul_expr(selector); - self.print_trailing_comment(selector.span, None); + self.print_trailing_comment(selector.span.hi(), None); for yul::StmtSwitchCase { constant, body } in branches.iter() { self.hardbreak_if_not_bol(); @@ -1762,7 +1826,7 @@ impl<'ast> State<'_, 'ast> { self.nbsp(); self.print_yul_block(body, span, true); - self.print_trailing_comment(selector.span, None); + self.print_trailing_comment(selector.span.hi(), None); } if let Some(default_case) = default_case { @@ -1811,7 +1875,7 @@ impl<'ast> State<'_, 'ast> { } } self.print_comments(span.hi()); - self.print_trailing_comment(span, None); + self.print_trailing_comment(span.hi(), None); self.hardbreak_if_not_bol(); } @@ -1871,6 +1935,35 @@ impl<'ast> State<'_, 'ast> { *should_break = true; } } + + fn print_attribute( + &mut self, + span: Span, + map: &mut HashMap, Vec)>, + print_fn: &mut dyn FnMut(&mut Self), + ) { + match map.remove(&span.lo()) { + Some((pre_comments, post_comments)) => { + for cmnt in pre_comments { + self.print_comment(cmnt); + } + if !self.is_bol_or_only_ind() { + self.space(); + } + print_fn(self); + for cmnt in post_comments { + self.print_comment(cmnt); + } + } + // Fallback for attributes with no comments + None => { + if !self.is_beginning_of_line() { + self.space(); + } + print_fn(self); + } + } + } } fn stmt_needs_semi<'ast>(stmt: &'ast ast::StmtKind<'ast>) -> bool { @@ -1895,3 +1988,18 @@ fn stmt_needs_semi<'ast>(stmt: &'ast ast::StmtKind<'ast>) -> bool { ast::StmtKind::Placeholder { .. } => true, } } + +#[derive(Debug, Clone)] +enum AttributeKind<'a> { + Visibility(ast::Visibility), + StateMutability(ast::StateMutability), + Virtual, + Override(&'a ast::Override<'a>), + Modifier(&'a ast::Modifier<'a>), +} + +#[derive(Debug, Clone)] +struct AttributeInfo<'a> { + kind: AttributeKind<'a>, + span: Span, +} diff --git a/crates/fmt/testdata/FunctionDefinition/all-params.fmt.sol b/crates/fmt/testdata/FunctionDefinition/all-params.fmt.sol deleted file mode 100644 index db7164d284a54..0000000000000 --- a/crates/fmt/testdata/FunctionDefinition/all-params.fmt.sol +++ /dev/null @@ -1,732 +0,0 @@ -// config: line_length = 60 -// config: multiline_func_header = "all_params" -interface FunctionInterfaces { - function noParamsNoModifiersNoReturns(); - - function oneParam(uint256 x); - - function oneModifier() modifier1; - - function oneReturn() returns (uint256 y1); - - // function prefix - function withComments( // function name postfix - // x1 prefix - uint256 x1, // x1 postfix - // x2 prefix - uint256 x2, // x2 postfix - // x2 postfix2 - /* - multi-line x3 prefix - */ - uint256 x3 // x3 postfix - ) - // public prefix - public // public postfix - // pure prefix - pure // pure postfix - // modifier1 prefix - modifier1 // modifier1 postfix - // modifier2 prefix - modifier2 /* - mutliline modifier2 postfix - */ - // modifier3 prefix - modifier3 // modifier3 postfix - returns ( - // y1 prefix - uint256 y1, // y1 postfix - // y2 prefix - uint256 y2, // y2 postfix - // y3 prefix - uint256 y3 - ); // y3 postfix - // function postfix - - /*////////////////////////////////////////////////////////////////////////// - TEST - //////////////////////////////////////////////////////////////////////////*/ - function manyParams( - uint256 x1, - uint256 x2, - uint256 x3, - uint256 x4, - uint256 x5, - uint256 x6, - uint256 x7, - uint256 x8, - uint256 x9, - uint256 x10 - ); - - function manyModifiers() - modifier1 - modifier2 - modifier3 - modifier4 - modifier5 - modifier6 - modifier7 - modifier8 - modifier9 - modifier10; - - function manyReturns() - returns ( - uint256 y1, - uint256 y2, - uint256 y3, - uint256 y4, - uint256 y5, - uint256 y6, - uint256 y7, - uint256 y8, - uint256 y9, - uint256 y10 - ); - - function someParamsSomeModifiers( - uint256 x1, - uint256 x2, - uint256 x3 - ) - modifier1 - modifier2 - modifier3; - - function someParamsSomeReturns( - uint256 x1, - uint256 x2, - uint256 x3 - ) - returns (uint256 y1, uint256 y2, uint256 y3); - - function someModifiersSomeReturns() - modifier1 - modifier2 - modifier3 - returns (uint256 y1, uint256 y2, uint256 y3); - - function someParamSomeModifiersSomeReturns( - uint256 x1, - uint256 x2, - uint256 x3 - ) - modifier1 - modifier2 - modifier3 - returns (uint256 y1, uint256 y2, uint256 y3); - - function someParamsManyModifiers( - uint256 x1, - uint256 x2, - uint256 x3 - ) - modifier1 - modifier2 - modifier3 - modifier4 - modifier5 - modifier6 - modifier7 - modifier8 - modifier9 - modifier10; - - function someParamsManyReturns( - uint256 x1, - uint256 x2, - uint256 x3 - ) - returns ( - uint256 y1, - uint256 y2, - uint256 y3, - uint256 y4, - uint256 y5, - uint256 y6, - uint256 y7, - uint256 y8, - uint256 y9, - uint256 y10 - ); - - function manyParamsSomeModifiers( - uint256 x1, - uint256 x2, - uint256 x3, - uint256 x4, - uint256 x5, - uint256 x6, - uint256 x7, - uint256 x8, - uint256 x9, - uint256 x10 - ) - modifier1 - modifier2 - modifier3; - - function manyParamsSomeReturns( - uint256 x1, - uint256 x2, - uint256 x3, - uint256 x4, - uint256 x5, - uint256 x6, - uint256 x7, - uint256 x8, - uint256 x9, - uint256 x10 - ) - returns (uint256 y1, uint256 y2, uint256 y3); - - function manyParamsManyModifiers( - uint256 x1, - uint256 x2, - uint256 x3, - uint256 x4, - uint256 x5, - uint256 x6, - uint256 x7, - uint256 x8, - uint256 x9, - uint256 x10 - ) - modifier1 - modifier2 - modifier3 - modifier4 - modifier5 - modifier6 - modifier7 - modifier8 - modifier9 - modifier10; - - function manyParamsManyReturns( - uint256 x1, - uint256 x2, - uint256 x3, - uint256 x4, - uint256 x5, - uint256 x6, - uint256 x7, - uint256 x8, - uint256 x9, - uint256 x10 - ) - returns ( - uint256 y1, - uint256 y2, - uint256 y3, - uint256 y4, - uint256 y5, - uint256 y6, - uint256 y7, - uint256 y8, - uint256 y9, - uint256 y10 - ); - - function manyParamsManyModifiersManyReturns( - uint256 x1, - uint256 x2, - uint256 x3, - uint256 x4, - uint256 x5, - uint256 x6, - uint256 x7, - uint256 x8, - uint256 x9, - uint256 x10 - ) - modifier1 - modifier2 - modifier3 - modifier4 - modifier5 - modifier6 - modifier7 - modifier8 - modifier9 - modifier10 - returns ( - uint256 y1, - uint256 y2, - uint256 y3, - uint256 y4, - uint256 y5, - uint256 y6, - uint256 y7, - uint256 y8, - uint256 y9, - uint256 y10 - ); - - function modifierOrderCorrect01() - public - view - virtual - override - modifier1 - modifier2 - returns (uint256); - - function modifierOrderCorrect02() - private - pure - virtual - modifier1 - modifier2 - returns (string); - - function modifierOrderCorrect03() - external - payable - override - modifier1 - modifier2 - returns (address); - - function modifierOrderCorrect04() - internal - virtual - override - modifier1 - modifier2 - returns (uint256); - - function modifierOrderIncorrect01() - public - view - virtual - override - modifier1 - modifier2 - returns (uint256); - - function modifierOrderIncorrect02() - external - virtual - override - modifier1 - modifier2 - returns (uint256); - - function modifierOrderIncorrect03() - internal - pure - virtual - modifier1 - modifier2 - returns (uint256); - - function modifierOrderIncorrect04() - external - payable - override - modifier1 - modifier2 - returns (uint256); -} - -contract FunctionDefinitions { - function() external {} - fallback() external {} - - function() external payable {} - fallback() external payable {} - receive() external payable {} - - function noParamsNoModifiersNoReturns() { - a = 1; - } - - function oneParam(uint256 x) { - a = 1; - } - - function oneModifier() modifier1 { - a = 1; - } - - function oneReturn() returns (uint256 y1) { - a = 1; - } - - function manyParams( - uint256 x1, - uint256 x2, - uint256 x3, - uint256 x4, - uint256 x5, - uint256 x6, - uint256 x7, - uint256 x8, - uint256 x9, - uint256 x10 - ) { - a = 1; - } - - function manyModifiers() - modifier1 - modifier2 - modifier3 - modifier4 - modifier5 - modifier6 - modifier7 - modifier8 - modifier9 - modifier10 - { - a = 1; - } - - function manyReturns() - returns ( - uint256 y1, - uint256 y2, - uint256 y3, - uint256 y4, - uint256 y5, - uint256 y6, - uint256 y7, - uint256 y8, - uint256 y9, - uint256 y10 - ) - { - a = 1; - } - - function someParamsSomeModifiers( - uint256 x1, - uint256 x2, - uint256 x3 - ) - modifier1 - modifier2 - modifier3 - { - a = 1; - } - - function someParamsSomeReturns( - uint256 x1, - uint256 x2, - uint256 x3 - ) - returns (uint256 y1, uint256 y2, uint256 y3) - { - a = 1; - } - - function someModifiersSomeReturns() - modifier1 - modifier2 - modifier3 - returns (uint256 y1, uint256 y2, uint256 y3) - { - a = 1; - } - - function someParamSomeModifiersSomeReturns( - uint256 x1, - uint256 x2, - uint256 x3 - ) - modifier1 - modifier2 - modifier3 - returns (uint256 y1, uint256 y2, uint256 y3) - { - a = 1; - } - - function someParamsManyModifiers( - uint256 x1, - uint256 x2, - uint256 x3 - ) - modifier1 - modifier2 - modifier3 - modifier4 - modifier5 - modifier6 - modifier7 - modifier8 - modifier9 - modifier10 - { - a = 1; - } - - function someParamsManyReturns( - uint256 x1, - uint256 x2, - uint256 x3 - ) - returns ( - uint256 y1, - uint256 y2, - uint256 y3, - uint256 y4, - uint256 y5, - uint256 y6, - uint256 y7, - uint256 y8, - uint256 y9, - uint256 y10 - ) - { - a = 1; - } - - function manyParamsSomeModifiers( - uint256 x1, - uint256 x2, - uint256 x3, - uint256 x4, - uint256 x5, - uint256 x6, - uint256 x7, - uint256 x8, - uint256 x9, - uint256 x10 - ) - modifier1 - modifier2 - modifier3 - { - a = 1; - } - - function manyParamsSomeReturns( - uint256 x1, - uint256 x2, - uint256 x3, - uint256 x4, - uint256 x5, - uint256 x6, - uint256 x7, - uint256 x8, - uint256 x9, - uint256 x10 - ) - returns (uint256 y1, uint256 y2, uint256 y3) - { - a = 1; - } - - function manyParamsManyModifiers( - uint256 x1, - uint256 x2, - uint256 x3, - uint256 x4, - uint256 x5, - uint256 x6, - uint256 x7, - uint256 x8, - uint256 x9, - uint256 x10 - ) - public - modifier1 - modifier2 - modifier3 - modifier4 - modifier5 - modifier6 - modifier7 - modifier8 - modifier9 - modifier10 - { - a = 1; - } - - function manyParamsManyReturns( - uint256 x1, - uint256 x2, - uint256 x3, - uint256 x4, - uint256 x5, - uint256 x6, - uint256 x7, - uint256 x8, - uint256 x9, - uint256 x10 - ) - returns ( - uint256 y1, - uint256 y2, - uint256 y3, - uint256 y4, - uint256 y5, - uint256 y6, - uint256 y7, - uint256 y8, - uint256 y9, - uint256 y10 - ) - { - a = 1; - } - - function manyParamsManyModifiersManyReturns( - uint256 x1, - uint256 x2, - uint256 x3, - uint256 x4, - uint256 x5, - uint256 x6, - uint256 x7, - uint256 x8, - uint256 x9, - uint256 x10 - ) - modifier1 - modifier2 - modifier3 - modifier4 - modifier5 - modifier6 - modifier7 - modifier8 - modifier9 - modifier10 - returns ( - uint256 y1, - uint256 y2, - uint256 y3, - uint256 y4, - uint256 y5, - uint256 y6, - uint256 y7, - uint256 y8, - uint256 y9, - uint256 y10 - ) - { - a = 1; - } - - function modifierOrderCorrect01() - public - view - virtual - override - modifier1 - modifier2 - returns (uint256) - { - a = 1; - } - - function modifierOrderCorrect02() - private - pure - virtual - modifier1 - modifier2 - returns (string) - { - a = 1; - } - - function modifierOrderCorrect03() - external - payable - override - modifier1 - modifier2 - returns (address) - { - a = 1; - } - - function modifierOrderCorrect04() - internal - virtual - override - modifier1 - modifier2 - returns (uint256) - { - a = 1; - } - - function modifierOrderIncorrect01() - public - view - virtual - override - modifier1 - modifier2 - returns (uint256) - { - a = 1; - } - - function modifierOrderIncorrect02() - external - virtual - override - modifier1 - modifier2 - returns (uint256) - { - a = 1; - } - - function modifierOrderIncorrect03() - internal - pure - virtual - modifier1 - modifier2 - returns (uint256) - { - a = 1; - } - - function modifierOrderIncorrect04() - external - payable - override - modifier1 - modifier2 - returns (uint256) - { - a = 1; - } - - fallback() external payable virtual {} - receive() external payable virtual {} -} - -contract FunctionOverrides is - FunctionInterfaces, - FunctionDefinitions -{ - function noParamsNoModifiersNoReturns() override { - a = 1; - } - - function oneParam( - uint256 x - ) - override( - FunctionInterfaces, - FunctionDefinitions, - SomeOtherFunctionContract, - SomeImport.AndAnotherFunctionContract - ) - { - a = 1; - } -} diff --git a/crates/fmt/testdata/FunctionDefinition/all.fmt.sol b/crates/fmt/testdata/FunctionDefinition/all.fmt.sol deleted file mode 100644 index 6d90880679199..0000000000000 --- a/crates/fmt/testdata/FunctionDefinition/all.fmt.sol +++ /dev/null @@ -1,730 +0,0 @@ -// config: line_length = 60 -// config: multiline_func_header = "all" -interface FunctionInterfaces { - function noParamsNoModifiersNoReturns(); - - function oneParam(uint256 x); - - function oneModifier() modifier1; - - function oneReturn() returns (uint256 y1); - - // function prefix - function withComments( // function name postfix - // x1 prefix - uint256 x1, // x1 postfix - // x2 prefix - uint256 x2, // x2 postfix - // x2 postfix2 - /* - multi-line x3 prefix - */ - uint256 x3 // x3 postfix - ) - // public prefix - public // public postfix - // pure prefix - pure // pure postfix - // modifier1 prefix - modifier1 // modifier1 postfix - // modifier2 prefix - modifier2 /* - mutliline modifier2 postfix - */ - // modifier3 prefix - modifier3 // modifier3 postfix - returns ( - // y1 prefix - uint256 y1, // y1 postfix - // y2 prefix - uint256 y2, // y2 postfix - // y3 prefix - uint256 y3 - ); // y3 postfix - // function postfix - - /*////////////////////////////////////////////////////////////////////////// - TEST - //////////////////////////////////////////////////////////////////////////*/ - function manyParams( - uint256 x1, - uint256 x2, - uint256 x3, - uint256 x4, - uint256 x5, - uint256 x6, - uint256 x7, - uint256 x8, - uint256 x9, - uint256 x10 - ); - - function manyModifiers() - modifier1 - modifier2 - modifier3 - modifier4 - modifier5 - modifier6 - modifier7 - modifier8 - modifier9 - modifier10; - - function manyReturns() - returns ( - uint256 y1, - uint256 y2, - uint256 y3, - uint256 y4, - uint256 y5, - uint256 y6, - uint256 y7, - uint256 y8, - uint256 y9, - uint256 y10 - ); - - function someParamsSomeModifiers( - uint256 x1, - uint256 x2, - uint256 x3 - ) - modifier1 - modifier2 - modifier3; - - function someParamsSomeReturns( - uint256 x1, - uint256 x2, - uint256 x3 - ) - returns (uint256 y1, uint256 y2, uint256 y3); - - function someModifiersSomeReturns() - modifier1 - modifier2 - modifier3 - returns (uint256 y1, uint256 y2, uint256 y3); - - function someParamSomeModifiersSomeReturns( - uint256 x1, - uint256 x2, - uint256 x3 - ) - modifier1 - modifier2 - modifier3 - returns (uint256 y1, uint256 y2, uint256 y3); - - function someParamsManyModifiers( - uint256 x1, - uint256 x2, - uint256 x3 - ) - modifier1 - modifier2 - modifier3 - modifier4 - modifier5 - modifier6 - modifier7 - modifier8 - modifier9 - modifier10; - - function someParamsManyReturns( - uint256 x1, - uint256 x2, - uint256 x3 - ) - returns ( - uint256 y1, - uint256 y2, - uint256 y3, - uint256 y4, - uint256 y5, - uint256 y6, - uint256 y7, - uint256 y8, - uint256 y9, - uint256 y10 - ); - - function manyParamsSomeModifiers( - uint256 x1, - uint256 x2, - uint256 x3, - uint256 x4, - uint256 x5, - uint256 x6, - uint256 x7, - uint256 x8, - uint256 x9, - uint256 x10 - ) - modifier1 - modifier2 - modifier3; - - function manyParamsSomeReturns( - uint256 x1, - uint256 x2, - uint256 x3, - uint256 x4, - uint256 x5, - uint256 x6, - uint256 x7, - uint256 x8, - uint256 x9, - uint256 x10 - ) - returns (uint256 y1, uint256 y2, uint256 y3); - - function manyParamsManyModifiers( - uint256 x1, - uint256 x2, - uint256 x3, - uint256 x4, - uint256 x5, - uint256 x6, - uint256 x7, - uint256 x8, - uint256 x9, - uint256 x10 - ) - modifier1 - modifier2 - modifier3 - modifier4 - modifier5 - modifier6 - modifier7 - modifier8 - modifier9 - modifier10; - - function manyParamsManyReturns( - uint256 x1, - uint256 x2, - uint256 x3, - uint256 x4, - uint256 x5, - uint256 x6, - uint256 x7, - uint256 x8, - uint256 x9, - uint256 x10 - ) - returns ( - uint256 y1, - uint256 y2, - uint256 y3, - uint256 y4, - uint256 y5, - uint256 y6, - uint256 y7, - uint256 y8, - uint256 y9, - uint256 y10 - ); - - function manyParamsManyModifiersManyReturns( - uint256 x1, - uint256 x2, - uint256 x3, - uint256 x4, - uint256 x5, - uint256 x6, - uint256 x7, - uint256 x8, - uint256 x9, - uint256 x10 - ) - modifier1 - modifier2 - modifier3 - modifier4 - modifier5 - modifier6 - modifier7 - modifier8 - modifier9 - modifier10 - returns ( - uint256 y1, - uint256 y2, - uint256 y3, - uint256 y4, - uint256 y5, - uint256 y6, - uint256 y7, - uint256 y8, - uint256 y9, - uint256 y10 - ); - - function modifierOrderCorrect01() - public - view - virtual - override - modifier1 - modifier2 - returns (uint256); - - function modifierOrderCorrect02() - private - pure - virtual - modifier1 - modifier2 - returns (string); - - function modifierOrderCorrect03() - external - payable - override - modifier1 - modifier2 - returns (address); - - function modifierOrderCorrect04() - internal - virtual - override - modifier1 - modifier2 - returns (uint256); - - function modifierOrderIncorrect01() - public - view - virtual - override - modifier1 - modifier2 - returns (uint256); - - function modifierOrderIncorrect02() - external - virtual - override - modifier1 - modifier2 - returns (uint256); - - function modifierOrderIncorrect03() - internal - pure - virtual - modifier1 - modifier2 - returns (uint256); - - function modifierOrderIncorrect04() - external - payable - override - modifier1 - modifier2 - returns (uint256); -} - -contract FunctionDefinitions { - function() external {} - fallback() external {} - - function() external payable {} - fallback() external payable {} - receive() external payable {} - - function noParamsNoModifiersNoReturns() { - a = 1; - } - - function oneParam(uint256 x) { - a = 1; - } - - function oneModifier() modifier1 { - a = 1; - } - - function oneReturn() returns (uint256 y1) { - a = 1; - } - - function manyParams( - uint256 x1, - uint256 x2, - uint256 x3, - uint256 x4, - uint256 x5, - uint256 x6, - uint256 x7, - uint256 x8, - uint256 x9, - uint256 x10 - ) { - a = 1; - } - - function manyModifiers() - modifier1 - modifier2 - modifier3 - modifier4 - modifier5 - modifier6 - modifier7 - modifier8 - modifier9 - modifier10 - { - a = 1; - } - - function manyReturns() - returns ( - uint256 y1, - uint256 y2, - uint256 y3, - uint256 y4, - uint256 y5, - uint256 y6, - uint256 y7, - uint256 y8, - uint256 y9, - uint256 y10 - ) - { - a = 1; - } - - function someParamsSomeModifiers( - uint256 x1, - uint256 x2, - uint256 x3 - ) - modifier1 - modifier2 - modifier3 - { - a = 1; - } - - function someParamsSomeReturns( - uint256 x1, - uint256 x2, - uint256 x3 - ) - returns (uint256 y1, uint256 y2, uint256 y3) - { - a = 1; - } - - function someModifiersSomeReturns() - modifier1 - modifier2 - modifier3 - returns (uint256 y1, uint256 y2, uint256 y3) - { - a = 1; - } - - function someParamSomeModifiersSomeReturns( - uint256 x1, - uint256 x2, - uint256 x3 - ) - modifier1 - modifier2 - modifier3 - returns (uint256 y1, uint256 y2, uint256 y3) - { - a = 1; - } - - function someParamsManyModifiers( - uint256 x1, - uint256 x2, - uint256 x3 - ) - modifier1 - modifier2 - modifier3 - modifier4 - modifier5 - modifier6 - modifier7 - modifier8 - modifier9 - modifier10 - { - a = 1; - } - - function someParamsManyReturns( - uint256 x1, - uint256 x2, - uint256 x3 - ) - returns ( - uint256 y1, - uint256 y2, - uint256 y3, - uint256 y4, - uint256 y5, - uint256 y6, - uint256 y7, - uint256 y8, - uint256 y9, - uint256 y10 - ) - { - a = 1; - } - - function manyParamsSomeModifiers( - uint256 x1, - uint256 x2, - uint256 x3, - uint256 x4, - uint256 x5, - uint256 x6, - uint256 x7, - uint256 x8, - uint256 x9, - uint256 x10 - ) - modifier1 - modifier2 - modifier3 - { - a = 1; - } - - function manyParamsSomeReturns( - uint256 x1, - uint256 x2, - uint256 x3, - uint256 x4, - uint256 x5, - uint256 x6, - uint256 x7, - uint256 x8, - uint256 x9, - uint256 x10 - ) - returns (uint256 y1, uint256 y2, uint256 y3) - { - a = 1; - } - - function manyParamsManyModifiers( - uint256 x1, - uint256 x2, - uint256 x3, - uint256 x4, - uint256 x5, - uint256 x6, - uint256 x7, - uint256 x8, - uint256 x9, - uint256 x10 - ) - public - modifier1 - modifier2 - modifier3 - modifier4 - modifier5 - modifier6 - modifier7 - modifier8 - modifier9 - modifier10 - { - a = 1; - } - - function manyParamsManyReturns( - uint256 x1, - uint256 x2, - uint256 x3, - uint256 x4, - uint256 x5, - uint256 x6, - uint256 x7, - uint256 x8, - uint256 x9, - uint256 x10 - ) - returns ( - uint256 y1, - uint256 y2, - uint256 y3, - uint256 y4, - uint256 y5, - uint256 y6, - uint256 y7, - uint256 y8, - uint256 y9, - uint256 y10 - ) - { - a = 1; - } - - function manyParamsManyModifiersManyReturns( - uint256 x1, - uint256 x2, - uint256 x3, - uint256 x4, - uint256 x5, - uint256 x6, - uint256 x7, - uint256 x8, - uint256 x9, - uint256 x10 - ) - modifier1 - modifier2 - modifier3 - modifier4 - modifier5 - modifier6 - modifier7 - modifier8 - modifier9 - modifier10 - returns ( - uint256 y1, - uint256 y2, - uint256 y3, - uint256 y4, - uint256 y5, - uint256 y6, - uint256 y7, - uint256 y8, - uint256 y9, - uint256 y10 - ) - { - a = 1; - } - - function modifierOrderCorrect01() - public - view - virtual - override - modifier1 - modifier2 - returns (uint256) - { - a = 1; - } - - function modifierOrderCorrect02() - private - pure - virtual - modifier1 - modifier2 - returns (string) - { - a = 1; - } - - function modifierOrderCorrect03() - external - payable - override - modifier1 - modifier2 - returns (address) - { - a = 1; - } - - function modifierOrderCorrect04() - internal - virtual - override - modifier1 - modifier2 - returns (uint256) - { - a = 1; - } - - function modifierOrderIncorrect01() - public - view - virtual - override - modifier1 - modifier2 - returns (uint256) - { - a = 1; - } - - function modifierOrderIncorrect02() - external - virtual - override - modifier1 - modifier2 - returns (uint256) - { - a = 1; - } - - function modifierOrderIncorrect03() - internal - pure - virtual - modifier1 - modifier2 - returns (uint256) - { - a = 1; - } - - function modifierOrderIncorrect04() - external - payable - override - modifier1 - modifier2 - returns (uint256) - { - a = 1; - } - - fallback() external payable virtual {} - receive() external payable virtual {} -} - -contract FunctionOverrides is - FunctionInterfaces, - FunctionDefinitions -{ - function noParamsNoModifiersNoReturns() override { - a = 1; - } - - function oneParam(uint256 x) - override( - FunctionInterfaces, - FunctionDefinitions, - SomeOtherFunctionContract, - SomeImport.AndAnotherFunctionContract - ) - { - a = 1; - } -} diff --git a/crates/fmt/testdata/FunctionDefinition/fmt.sol b/crates/fmt/testdata/FunctionDefinition/fmt.sol index 9e34a8bea2682..ae9e892aadb6f 100644 --- a/crates/fmt/testdata/FunctionDefinition/fmt.sol +++ b/crates/fmt/testdata/FunctionDefinition/fmt.sol @@ -14,7 +14,7 @@ interface FunctionInterfaces { uint256 x1, // x1 postfix // x2 prefix uint256 x2, // x2 postfix - // x2 postfix2 + // x2 postfix2 /* multi-line x3 prefix */ diff --git a/crates/fmt/testdata/FunctionDefinition/original.sol b/crates/fmt/testdata/FunctionDefinition/original.sol index 5ae2c3fdd92ce..a416fc98de47b 100644 --- a/crates/fmt/testdata/FunctionDefinition/original.sol +++ b/crates/fmt/testdata/FunctionDefinition/original.sol @@ -215,4 +215,3 @@ contract FunctionOverrides is FunctionInterfaces, FunctionDefinitions { a = 1; } } - diff --git a/crates/fmt/testdata/FunctionDefinition/override-spacing.fmt.sol b/crates/fmt/testdata/FunctionDefinition/override-spacing.fmt.sol deleted file mode 100644 index 516e5c2fd42ed..0000000000000 --- a/crates/fmt/testdata/FunctionDefinition/override-spacing.fmt.sol +++ /dev/null @@ -1,710 +0,0 @@ -// config: line_length = 60 -// config: override_spacing = true -interface FunctionInterfaces { - function noParamsNoModifiersNoReturns(); - - function oneParam(uint256 x); - - function oneModifier() modifier1; - - function oneReturn() returns (uint256 y1); - - // function prefix - function withComments( // function name postfix - // x1 prefix - uint256 x1, // x1 postfix - // x2 prefix - uint256 x2, // x2 postfix - // x2 postfix2 - /* - multi-line x3 prefix - */ - uint256 x3 // x3 postfix - ) - // public prefix - public // public postfix - // pure prefix - pure // pure postfix - // modifier1 prefix - modifier1 // modifier1 postfix - // modifier2 prefix - modifier2 /* - mutliline modifier2 postfix - */ - // modifier3 prefix - modifier3 // modifier3 postfix - returns ( - // y1 prefix - uint256 y1, // y1 postfix - // y2 prefix - uint256 y2, // y2 postfix - // y3 prefix - uint256 y3 - ); // y3 postfix - // function postfix - - /*////////////////////////////////////////////////////////////////////////// - TEST - //////////////////////////////////////////////////////////////////////////*/ - function manyParams( - uint256 x1, - uint256 x2, - uint256 x3, - uint256 x4, - uint256 x5, - uint256 x6, - uint256 x7, - uint256 x8, - uint256 x9, - uint256 x10 - ); - - function manyModifiers() - modifier1 - modifier2 - modifier3 - modifier4 - modifier5 - modifier6 - modifier7 - modifier8 - modifier9 - modifier10; - - function manyReturns() - returns ( - uint256 y1, - uint256 y2, - uint256 y3, - uint256 y4, - uint256 y5, - uint256 y6, - uint256 y7, - uint256 y8, - uint256 y9, - uint256 y10 - ); - - function someParamsSomeModifiers( - uint256 x1, - uint256 x2, - uint256 x3 - ) modifier1 modifier2 modifier3; - - function someParamsSomeReturns( - uint256 x1, - uint256 x2, - uint256 x3 - ) returns (uint256 y1, uint256 y2, uint256 y3); - - function someModifiersSomeReturns() - modifier1 - modifier2 - modifier3 - returns (uint256 y1, uint256 y2, uint256 y3); - - function someParamSomeModifiersSomeReturns( - uint256 x1, - uint256 x2, - uint256 x3 - ) - modifier1 - modifier2 - modifier3 - returns (uint256 y1, uint256 y2, uint256 y3); - - function someParamsManyModifiers( - uint256 x1, - uint256 x2, - uint256 x3 - ) - modifier1 - modifier2 - modifier3 - modifier4 - modifier5 - modifier6 - modifier7 - modifier8 - modifier9 - modifier10; - - function someParamsManyReturns( - uint256 x1, - uint256 x2, - uint256 x3 - ) - returns ( - uint256 y1, - uint256 y2, - uint256 y3, - uint256 y4, - uint256 y5, - uint256 y6, - uint256 y7, - uint256 y8, - uint256 y9, - uint256 y10 - ); - - function manyParamsSomeModifiers( - uint256 x1, - uint256 x2, - uint256 x3, - uint256 x4, - uint256 x5, - uint256 x6, - uint256 x7, - uint256 x8, - uint256 x9, - uint256 x10 - ) modifier1 modifier2 modifier3; - - function manyParamsSomeReturns( - uint256 x1, - uint256 x2, - uint256 x3, - uint256 x4, - uint256 x5, - uint256 x6, - uint256 x7, - uint256 x8, - uint256 x9, - uint256 x10 - ) returns (uint256 y1, uint256 y2, uint256 y3); - - function manyParamsManyModifiers( - uint256 x1, - uint256 x2, - uint256 x3, - uint256 x4, - uint256 x5, - uint256 x6, - uint256 x7, - uint256 x8, - uint256 x9, - uint256 x10 - ) - modifier1 - modifier2 - modifier3 - modifier4 - modifier5 - modifier6 - modifier7 - modifier8 - modifier9 - modifier10; - - function manyParamsManyReturns( - uint256 x1, - uint256 x2, - uint256 x3, - uint256 x4, - uint256 x5, - uint256 x6, - uint256 x7, - uint256 x8, - uint256 x9, - uint256 x10 - ) - returns ( - uint256 y1, - uint256 y2, - uint256 y3, - uint256 y4, - uint256 y5, - uint256 y6, - uint256 y7, - uint256 y8, - uint256 y9, - uint256 y10 - ); - - function manyParamsManyModifiersManyReturns( - uint256 x1, - uint256 x2, - uint256 x3, - uint256 x4, - uint256 x5, - uint256 x6, - uint256 x7, - uint256 x8, - uint256 x9, - uint256 x10 - ) - modifier1 - modifier2 - modifier3 - modifier4 - modifier5 - modifier6 - modifier7 - modifier8 - modifier9 - modifier10 - returns ( - uint256 y1, - uint256 y2, - uint256 y3, - uint256 y4, - uint256 y5, - uint256 y6, - uint256 y7, - uint256 y8, - uint256 y9, - uint256 y10 - ); - - function modifierOrderCorrect01() - public - view - virtual - override - modifier1 - modifier2 - returns (uint256); - - function modifierOrderCorrect02() - private - pure - virtual - modifier1 - modifier2 - returns (string); - - function modifierOrderCorrect03() - external - payable - override - modifier1 - modifier2 - returns (address); - - function modifierOrderCorrect04() - internal - virtual - override - modifier1 - modifier2 - returns (uint256); - - function modifierOrderIncorrect01() - public - view - virtual - override - modifier1 - modifier2 - returns (uint256); - - function modifierOrderIncorrect02() - external - virtual - override - modifier1 - modifier2 - returns (uint256); - - function modifierOrderIncorrect03() - internal - pure - virtual - modifier1 - modifier2 - returns (uint256); - - function modifierOrderIncorrect04() - external - payable - override - modifier1 - modifier2 - returns (uint256); -} - -contract FunctionDefinitions { - function() external {} - fallback() external {} - - function() external payable {} - fallback() external payable {} - receive() external payable {} - - function noParamsNoModifiersNoReturns() { - a = 1; - } - - function oneParam(uint256 x) { - a = 1; - } - - function oneModifier() modifier1 { - a = 1; - } - - function oneReturn() returns (uint256 y1) { - a = 1; - } - - function manyParams( - uint256 x1, - uint256 x2, - uint256 x3, - uint256 x4, - uint256 x5, - uint256 x6, - uint256 x7, - uint256 x8, - uint256 x9, - uint256 x10 - ) { - a = 1; - } - - function manyModifiers() - modifier1 - modifier2 - modifier3 - modifier4 - modifier5 - modifier6 - modifier7 - modifier8 - modifier9 - modifier10 - { - a = 1; - } - - function manyReturns() - returns ( - uint256 y1, - uint256 y2, - uint256 y3, - uint256 y4, - uint256 y5, - uint256 y6, - uint256 y7, - uint256 y8, - uint256 y9, - uint256 y10 - ) - { - a = 1; - } - - function someParamsSomeModifiers( - uint256 x1, - uint256 x2, - uint256 x3 - ) modifier1 modifier2 modifier3 { - a = 1; - } - - function someParamsSomeReturns( - uint256 x1, - uint256 x2, - uint256 x3 - ) returns (uint256 y1, uint256 y2, uint256 y3) { - a = 1; - } - - function someModifiersSomeReturns() - modifier1 - modifier2 - modifier3 - returns (uint256 y1, uint256 y2, uint256 y3) - { - a = 1; - } - - function someParamSomeModifiersSomeReturns( - uint256 x1, - uint256 x2, - uint256 x3 - ) - modifier1 - modifier2 - modifier3 - returns (uint256 y1, uint256 y2, uint256 y3) - { - a = 1; - } - - function someParamsManyModifiers( - uint256 x1, - uint256 x2, - uint256 x3 - ) - modifier1 - modifier2 - modifier3 - modifier4 - modifier5 - modifier6 - modifier7 - modifier8 - modifier9 - modifier10 - { - a = 1; - } - - function someParamsManyReturns( - uint256 x1, - uint256 x2, - uint256 x3 - ) - returns ( - uint256 y1, - uint256 y2, - uint256 y3, - uint256 y4, - uint256 y5, - uint256 y6, - uint256 y7, - uint256 y8, - uint256 y9, - uint256 y10 - ) - { - a = 1; - } - - function manyParamsSomeModifiers( - uint256 x1, - uint256 x2, - uint256 x3, - uint256 x4, - uint256 x5, - uint256 x6, - uint256 x7, - uint256 x8, - uint256 x9, - uint256 x10 - ) modifier1 modifier2 modifier3 { - a = 1; - } - - function manyParamsSomeReturns( - uint256 x1, - uint256 x2, - uint256 x3, - uint256 x4, - uint256 x5, - uint256 x6, - uint256 x7, - uint256 x8, - uint256 x9, - uint256 x10 - ) returns (uint256 y1, uint256 y2, uint256 y3) { - a = 1; - } - - function manyParamsManyModifiers( - uint256 x1, - uint256 x2, - uint256 x3, - uint256 x4, - uint256 x5, - uint256 x6, - uint256 x7, - uint256 x8, - uint256 x9, - uint256 x10 - ) - public - modifier1 - modifier2 - modifier3 - modifier4 - modifier5 - modifier6 - modifier7 - modifier8 - modifier9 - modifier10 - { - a = 1; - } - - function manyParamsManyReturns( - uint256 x1, - uint256 x2, - uint256 x3, - uint256 x4, - uint256 x5, - uint256 x6, - uint256 x7, - uint256 x8, - uint256 x9, - uint256 x10 - ) - returns ( - uint256 y1, - uint256 y2, - uint256 y3, - uint256 y4, - uint256 y5, - uint256 y6, - uint256 y7, - uint256 y8, - uint256 y9, - uint256 y10 - ) - { - a = 1; - } - - function manyParamsManyModifiersManyReturns( - uint256 x1, - uint256 x2, - uint256 x3, - uint256 x4, - uint256 x5, - uint256 x6, - uint256 x7, - uint256 x8, - uint256 x9, - uint256 x10 - ) - modifier1 - modifier2 - modifier3 - modifier4 - modifier5 - modifier6 - modifier7 - modifier8 - modifier9 - modifier10 - returns ( - uint256 y1, - uint256 y2, - uint256 y3, - uint256 y4, - uint256 y5, - uint256 y6, - uint256 y7, - uint256 y8, - uint256 y9, - uint256 y10 - ) - { - a = 1; - } - - function modifierOrderCorrect01() - public - view - virtual - override - modifier1 - modifier2 - returns (uint256) - { - a = 1; - } - - function modifierOrderCorrect02() - private - pure - virtual - modifier1 - modifier2 - returns (string) - { - a = 1; - } - - function modifierOrderCorrect03() - external - payable - override - modifier1 - modifier2 - returns (address) - { - a = 1; - } - - function modifierOrderCorrect04() - internal - virtual - override - modifier1 - modifier2 - returns (uint256) - { - a = 1; - } - - function modifierOrderIncorrect01() - public - view - virtual - override - modifier1 - modifier2 - returns (uint256) - { - a = 1; - } - - function modifierOrderIncorrect02() - external - virtual - override - modifier1 - modifier2 - returns (uint256) - { - a = 1; - } - - function modifierOrderIncorrect03() - internal - pure - virtual - modifier1 - modifier2 - returns (uint256) - { - a = 1; - } - - function modifierOrderIncorrect04() - external - payable - override - modifier1 - modifier2 - returns (uint256) - { - a = 1; - } - - fallback() external payable virtual {} - receive() external payable virtual {} -} - -contract FunctionOverrides is - FunctionInterfaces, - FunctionDefinitions -{ - function noParamsNoModifiersNoReturns() override { - a = 1; - } - - function oneParam(uint256 x) - override ( - FunctionInterfaces, - FunctionDefinitions, - SomeOtherFunctionContract, - SomeImport.AndAnotherFunctionContract - ) - { - a = 1; - } -} diff --git a/crates/fmt/testdata/FunctionDefinition/params-first.fmt.sol b/crates/fmt/testdata/FunctionDefinition/params-first.fmt.sol deleted file mode 100644 index 3e7ebfff6b3aa..0000000000000 --- a/crates/fmt/testdata/FunctionDefinition/params-first.fmt.sol +++ /dev/null @@ -1,716 +0,0 @@ -// config: line_length = 60 -// config: multiline_func_header = "params_first" -interface FunctionInterfaces { - function noParamsNoModifiersNoReturns(); - - function oneParam( - uint256 x - ); - - function oneModifier() modifier1; - - function oneReturn() returns (uint256 y1); - - // function prefix - function withComments( // function name postfix - // x1 prefix - uint256 x1, // x1 postfix - // x2 prefix - uint256 x2, // x2 postfix - // x2 postfix2 - /* - multi-line x3 prefix - */ - uint256 x3 // x3 postfix - ) - // public prefix - public // public postfix - // pure prefix - pure // pure postfix - // modifier1 prefix - modifier1 // modifier1 postfix - // modifier2 prefix - modifier2 /* - mutliline modifier2 postfix - */ - // modifier3 prefix - modifier3 // modifier3 postfix - returns ( - // y1 prefix - uint256 y1, // y1 postfix - // y2 prefix - uint256 y2, // y2 postfix - // y3 prefix - uint256 y3 - ); // y3 postfix - // function postfix - - /*////////////////////////////////////////////////////////////////////////// - TEST - //////////////////////////////////////////////////////////////////////////*/ - function manyParams( - uint256 x1, - uint256 x2, - uint256 x3, - uint256 x4, - uint256 x5, - uint256 x6, - uint256 x7, - uint256 x8, - uint256 x9, - uint256 x10 - ); - - function manyModifiers() - modifier1 - modifier2 - modifier3 - modifier4 - modifier5 - modifier6 - modifier7 - modifier8 - modifier9 - modifier10; - - function manyReturns() - returns ( - uint256 y1, - uint256 y2, - uint256 y3, - uint256 y4, - uint256 y5, - uint256 y6, - uint256 y7, - uint256 y8, - uint256 y9, - uint256 y10 - ); - - function someParamsSomeModifiers( - uint256 x1, - uint256 x2, - uint256 x3 - ) modifier1 modifier2 modifier3; - - function someParamsSomeReturns( - uint256 x1, - uint256 x2, - uint256 x3 - ) returns (uint256 y1, uint256 y2, uint256 y3); - - function someModifiersSomeReturns() - modifier1 - modifier2 - modifier3 - returns (uint256 y1, uint256 y2, uint256 y3); - - function someParamSomeModifiersSomeReturns( - uint256 x1, - uint256 x2, - uint256 x3 - ) - modifier1 - modifier2 - modifier3 - returns (uint256 y1, uint256 y2, uint256 y3); - - function someParamsManyModifiers( - uint256 x1, - uint256 x2, - uint256 x3 - ) - modifier1 - modifier2 - modifier3 - modifier4 - modifier5 - modifier6 - modifier7 - modifier8 - modifier9 - modifier10; - - function someParamsManyReturns( - uint256 x1, - uint256 x2, - uint256 x3 - ) - returns ( - uint256 y1, - uint256 y2, - uint256 y3, - uint256 y4, - uint256 y5, - uint256 y6, - uint256 y7, - uint256 y8, - uint256 y9, - uint256 y10 - ); - - function manyParamsSomeModifiers( - uint256 x1, - uint256 x2, - uint256 x3, - uint256 x4, - uint256 x5, - uint256 x6, - uint256 x7, - uint256 x8, - uint256 x9, - uint256 x10 - ) modifier1 modifier2 modifier3; - - function manyParamsSomeReturns( - uint256 x1, - uint256 x2, - uint256 x3, - uint256 x4, - uint256 x5, - uint256 x6, - uint256 x7, - uint256 x8, - uint256 x9, - uint256 x10 - ) returns (uint256 y1, uint256 y2, uint256 y3); - - function manyParamsManyModifiers( - uint256 x1, - uint256 x2, - uint256 x3, - uint256 x4, - uint256 x5, - uint256 x6, - uint256 x7, - uint256 x8, - uint256 x9, - uint256 x10 - ) - modifier1 - modifier2 - modifier3 - modifier4 - modifier5 - modifier6 - modifier7 - modifier8 - modifier9 - modifier10; - - function manyParamsManyReturns( - uint256 x1, - uint256 x2, - uint256 x3, - uint256 x4, - uint256 x5, - uint256 x6, - uint256 x7, - uint256 x8, - uint256 x9, - uint256 x10 - ) - returns ( - uint256 y1, - uint256 y2, - uint256 y3, - uint256 y4, - uint256 y5, - uint256 y6, - uint256 y7, - uint256 y8, - uint256 y9, - uint256 y10 - ); - - function manyParamsManyModifiersManyReturns( - uint256 x1, - uint256 x2, - uint256 x3, - uint256 x4, - uint256 x5, - uint256 x6, - uint256 x7, - uint256 x8, - uint256 x9, - uint256 x10 - ) - modifier1 - modifier2 - modifier3 - modifier4 - modifier5 - modifier6 - modifier7 - modifier8 - modifier9 - modifier10 - returns ( - uint256 y1, - uint256 y2, - uint256 y3, - uint256 y4, - uint256 y5, - uint256 y6, - uint256 y7, - uint256 y8, - uint256 y9, - uint256 y10 - ); - - function modifierOrderCorrect01() - public - view - virtual - override - modifier1 - modifier2 - returns (uint256); - - function modifierOrderCorrect02() - private - pure - virtual - modifier1 - modifier2 - returns (string); - - function modifierOrderCorrect03() - external - payable - override - modifier1 - modifier2 - returns (address); - - function modifierOrderCorrect04() - internal - virtual - override - modifier1 - modifier2 - returns (uint256); - - function modifierOrderIncorrect01() - public - view - virtual - override - modifier1 - modifier2 - returns (uint256); - - function modifierOrderIncorrect02() - external - virtual - override - modifier1 - modifier2 - returns (uint256); - - function modifierOrderIncorrect03() - internal - pure - virtual - modifier1 - modifier2 - returns (uint256); - - function modifierOrderIncorrect04() - external - payable - override - modifier1 - modifier2 - returns (uint256); -} - -contract FunctionDefinitions { - function() external {} - fallback() external {} - - function() external payable {} - fallback() external payable {} - receive() external payable {} - - function noParamsNoModifiersNoReturns() { - a = 1; - } - - function oneParam( - uint256 x - ) { - a = 1; - } - - function oneModifier() modifier1 { - a = 1; - } - - function oneReturn() returns (uint256 y1) { - a = 1; - } - - function manyParams( - uint256 x1, - uint256 x2, - uint256 x3, - uint256 x4, - uint256 x5, - uint256 x6, - uint256 x7, - uint256 x8, - uint256 x9, - uint256 x10 - ) { - a = 1; - } - - function manyModifiers() - modifier1 - modifier2 - modifier3 - modifier4 - modifier5 - modifier6 - modifier7 - modifier8 - modifier9 - modifier10 - { - a = 1; - } - - function manyReturns() - returns ( - uint256 y1, - uint256 y2, - uint256 y3, - uint256 y4, - uint256 y5, - uint256 y6, - uint256 y7, - uint256 y8, - uint256 y9, - uint256 y10 - ) - { - a = 1; - } - - function someParamsSomeModifiers( - uint256 x1, - uint256 x2, - uint256 x3 - ) modifier1 modifier2 modifier3 { - a = 1; - } - - function someParamsSomeReturns( - uint256 x1, - uint256 x2, - uint256 x3 - ) returns (uint256 y1, uint256 y2, uint256 y3) { - a = 1; - } - - function someModifiersSomeReturns() - modifier1 - modifier2 - modifier3 - returns (uint256 y1, uint256 y2, uint256 y3) - { - a = 1; - } - - function someParamSomeModifiersSomeReturns( - uint256 x1, - uint256 x2, - uint256 x3 - ) - modifier1 - modifier2 - modifier3 - returns (uint256 y1, uint256 y2, uint256 y3) - { - a = 1; - } - - function someParamsManyModifiers( - uint256 x1, - uint256 x2, - uint256 x3 - ) - modifier1 - modifier2 - modifier3 - modifier4 - modifier5 - modifier6 - modifier7 - modifier8 - modifier9 - modifier10 - { - a = 1; - } - - function someParamsManyReturns( - uint256 x1, - uint256 x2, - uint256 x3 - ) - returns ( - uint256 y1, - uint256 y2, - uint256 y3, - uint256 y4, - uint256 y5, - uint256 y6, - uint256 y7, - uint256 y8, - uint256 y9, - uint256 y10 - ) - { - a = 1; - } - - function manyParamsSomeModifiers( - uint256 x1, - uint256 x2, - uint256 x3, - uint256 x4, - uint256 x5, - uint256 x6, - uint256 x7, - uint256 x8, - uint256 x9, - uint256 x10 - ) modifier1 modifier2 modifier3 { - a = 1; - } - - function manyParamsSomeReturns( - uint256 x1, - uint256 x2, - uint256 x3, - uint256 x4, - uint256 x5, - uint256 x6, - uint256 x7, - uint256 x8, - uint256 x9, - uint256 x10 - ) returns (uint256 y1, uint256 y2, uint256 y3) { - a = 1; - } - - function manyParamsManyModifiers( - uint256 x1, - uint256 x2, - uint256 x3, - uint256 x4, - uint256 x5, - uint256 x6, - uint256 x7, - uint256 x8, - uint256 x9, - uint256 x10 - ) - public - modifier1 - modifier2 - modifier3 - modifier4 - modifier5 - modifier6 - modifier7 - modifier8 - modifier9 - modifier10 - { - a = 1; - } - - function manyParamsManyReturns( - uint256 x1, - uint256 x2, - uint256 x3, - uint256 x4, - uint256 x5, - uint256 x6, - uint256 x7, - uint256 x8, - uint256 x9, - uint256 x10 - ) - returns ( - uint256 y1, - uint256 y2, - uint256 y3, - uint256 y4, - uint256 y5, - uint256 y6, - uint256 y7, - uint256 y8, - uint256 y9, - uint256 y10 - ) - { - a = 1; - } - - function manyParamsManyModifiersManyReturns( - uint256 x1, - uint256 x2, - uint256 x3, - uint256 x4, - uint256 x5, - uint256 x6, - uint256 x7, - uint256 x8, - uint256 x9, - uint256 x10 - ) - modifier1 - modifier2 - modifier3 - modifier4 - modifier5 - modifier6 - modifier7 - modifier8 - modifier9 - modifier10 - returns ( - uint256 y1, - uint256 y2, - uint256 y3, - uint256 y4, - uint256 y5, - uint256 y6, - uint256 y7, - uint256 y8, - uint256 y9, - uint256 y10 - ) - { - a = 1; - } - - function modifierOrderCorrect01() - public - view - virtual - override - modifier1 - modifier2 - returns (uint256) - { - a = 1; - } - - function modifierOrderCorrect02() - private - pure - virtual - modifier1 - modifier2 - returns (string) - { - a = 1; - } - - function modifierOrderCorrect03() - external - payable - override - modifier1 - modifier2 - returns (address) - { - a = 1; - } - - function modifierOrderCorrect04() - internal - virtual - override - modifier1 - modifier2 - returns (uint256) - { - a = 1; - } - - function modifierOrderIncorrect01() - public - view - virtual - override - modifier1 - modifier2 - returns (uint256) - { - a = 1; - } - - function modifierOrderIncorrect02() - external - virtual - override - modifier1 - modifier2 - returns (uint256) - { - a = 1; - } - - function modifierOrderIncorrect03() - internal - pure - virtual - modifier1 - modifier2 - returns (uint256) - { - a = 1; - } - - function modifierOrderIncorrect04() - external - payable - override - modifier1 - modifier2 - returns (uint256) - { - a = 1; - } - - fallback() external payable virtual {} - receive() external payable virtual {} -} - -contract FunctionOverrides is - FunctionInterfaces, - FunctionDefinitions -{ - function noParamsNoModifiersNoReturns() override { - a = 1; - } - - function oneParam( - uint256 x - ) - override( - FunctionInterfaces, - FunctionDefinitions, - SomeOtherFunctionContract, - SomeImport.AndAnotherFunctionContract - ) - { - a = 1; - } -} diff --git a/crates/fmt/testdata/FunctionDefinition/params-multi.fmt.sol b/crates/fmt/testdata/FunctionDefinition/params-multi.fmt.sol deleted file mode 100644 index cd2015c9e050e..0000000000000 --- a/crates/fmt/testdata/FunctionDefinition/params-multi.fmt.sol +++ /dev/null @@ -1,710 +0,0 @@ -// config: line_length = 60 -// config: multiline_func_header = "params_first_multi" -interface FunctionInterfaces { - function noParamsNoModifiersNoReturns(); - - function oneParam(uint256 x); - - function oneModifier() modifier1; - - function oneReturn() returns (uint256 y1); - - // function prefix - function withComments( // function name postfix - // x1 prefix - uint256 x1, // x1 postfix - // x2 prefix - uint256 x2, // x2 postfix - // x2 postfix2 - /* - multi-line x3 prefix - */ - uint256 x3 // x3 postfix - ) - // public prefix - public // public postfix - // pure prefix - pure // pure postfix - // modifier1 prefix - modifier1 // modifier1 postfix - // modifier2 prefix - modifier2 /* - mutliline modifier2 postfix - */ - // modifier3 prefix - modifier3 // modifier3 postfix - returns ( - // y1 prefix - uint256 y1, // y1 postfix - // y2 prefix - uint256 y2, // y2 postfix - // y3 prefix - uint256 y3 - ); // y3 postfix - // function postfix - - /*////////////////////////////////////////////////////////////////////////// - TEST - //////////////////////////////////////////////////////////////////////////*/ - function manyParams( - uint256 x1, - uint256 x2, - uint256 x3, - uint256 x4, - uint256 x5, - uint256 x6, - uint256 x7, - uint256 x8, - uint256 x9, - uint256 x10 - ); - - function manyModifiers() - modifier1 - modifier2 - modifier3 - modifier4 - modifier5 - modifier6 - modifier7 - modifier8 - modifier9 - modifier10; - - function manyReturns() - returns ( - uint256 y1, - uint256 y2, - uint256 y3, - uint256 y4, - uint256 y5, - uint256 y6, - uint256 y7, - uint256 y8, - uint256 y9, - uint256 y10 - ); - - function someParamsSomeModifiers( - uint256 x1, - uint256 x2, - uint256 x3 - ) modifier1 modifier2 modifier3; - - function someParamsSomeReturns( - uint256 x1, - uint256 x2, - uint256 x3 - ) returns (uint256 y1, uint256 y2, uint256 y3); - - function someModifiersSomeReturns() - modifier1 - modifier2 - modifier3 - returns (uint256 y1, uint256 y2, uint256 y3); - - function someParamSomeModifiersSomeReturns( - uint256 x1, - uint256 x2, - uint256 x3 - ) - modifier1 - modifier2 - modifier3 - returns (uint256 y1, uint256 y2, uint256 y3); - - function someParamsManyModifiers( - uint256 x1, - uint256 x2, - uint256 x3 - ) - modifier1 - modifier2 - modifier3 - modifier4 - modifier5 - modifier6 - modifier7 - modifier8 - modifier9 - modifier10; - - function someParamsManyReturns( - uint256 x1, - uint256 x2, - uint256 x3 - ) - returns ( - uint256 y1, - uint256 y2, - uint256 y3, - uint256 y4, - uint256 y5, - uint256 y6, - uint256 y7, - uint256 y8, - uint256 y9, - uint256 y10 - ); - - function manyParamsSomeModifiers( - uint256 x1, - uint256 x2, - uint256 x3, - uint256 x4, - uint256 x5, - uint256 x6, - uint256 x7, - uint256 x8, - uint256 x9, - uint256 x10 - ) modifier1 modifier2 modifier3; - - function manyParamsSomeReturns( - uint256 x1, - uint256 x2, - uint256 x3, - uint256 x4, - uint256 x5, - uint256 x6, - uint256 x7, - uint256 x8, - uint256 x9, - uint256 x10 - ) returns (uint256 y1, uint256 y2, uint256 y3); - - function manyParamsManyModifiers( - uint256 x1, - uint256 x2, - uint256 x3, - uint256 x4, - uint256 x5, - uint256 x6, - uint256 x7, - uint256 x8, - uint256 x9, - uint256 x10 - ) - modifier1 - modifier2 - modifier3 - modifier4 - modifier5 - modifier6 - modifier7 - modifier8 - modifier9 - modifier10; - - function manyParamsManyReturns( - uint256 x1, - uint256 x2, - uint256 x3, - uint256 x4, - uint256 x5, - uint256 x6, - uint256 x7, - uint256 x8, - uint256 x9, - uint256 x10 - ) - returns ( - uint256 y1, - uint256 y2, - uint256 y3, - uint256 y4, - uint256 y5, - uint256 y6, - uint256 y7, - uint256 y8, - uint256 y9, - uint256 y10 - ); - - function manyParamsManyModifiersManyReturns( - uint256 x1, - uint256 x2, - uint256 x3, - uint256 x4, - uint256 x5, - uint256 x6, - uint256 x7, - uint256 x8, - uint256 x9, - uint256 x10 - ) - modifier1 - modifier2 - modifier3 - modifier4 - modifier5 - modifier6 - modifier7 - modifier8 - modifier9 - modifier10 - returns ( - uint256 y1, - uint256 y2, - uint256 y3, - uint256 y4, - uint256 y5, - uint256 y6, - uint256 y7, - uint256 y8, - uint256 y9, - uint256 y10 - ); - - function modifierOrderCorrect01() - public - view - virtual - override - modifier1 - modifier2 - returns (uint256); - - function modifierOrderCorrect02() - private - pure - virtual - modifier1 - modifier2 - returns (string); - - function modifierOrderCorrect03() - external - payable - override - modifier1 - modifier2 - returns (address); - - function modifierOrderCorrect04() - internal - virtual - override - modifier1 - modifier2 - returns (uint256); - - function modifierOrderIncorrect01() - public - view - virtual - override - modifier1 - modifier2 - returns (uint256); - - function modifierOrderIncorrect02() - external - virtual - override - modifier1 - modifier2 - returns (uint256); - - function modifierOrderIncorrect03() - internal - pure - virtual - modifier1 - modifier2 - returns (uint256); - - function modifierOrderIncorrect04() - external - payable - override - modifier1 - modifier2 - returns (uint256); -} - -contract FunctionDefinitions { - function() external {} - fallback() external {} - - function() external payable {} - fallback() external payable {} - receive() external payable {} - - function noParamsNoModifiersNoReturns() { - a = 1; - } - - function oneParam(uint256 x) { - a = 1; - } - - function oneModifier() modifier1 { - a = 1; - } - - function oneReturn() returns (uint256 y1) { - a = 1; - } - - function manyParams( - uint256 x1, - uint256 x2, - uint256 x3, - uint256 x4, - uint256 x5, - uint256 x6, - uint256 x7, - uint256 x8, - uint256 x9, - uint256 x10 - ) { - a = 1; - } - - function manyModifiers() - modifier1 - modifier2 - modifier3 - modifier4 - modifier5 - modifier6 - modifier7 - modifier8 - modifier9 - modifier10 - { - a = 1; - } - - function manyReturns() - returns ( - uint256 y1, - uint256 y2, - uint256 y3, - uint256 y4, - uint256 y5, - uint256 y6, - uint256 y7, - uint256 y8, - uint256 y9, - uint256 y10 - ) - { - a = 1; - } - - function someParamsSomeModifiers( - uint256 x1, - uint256 x2, - uint256 x3 - ) modifier1 modifier2 modifier3 { - a = 1; - } - - function someParamsSomeReturns( - uint256 x1, - uint256 x2, - uint256 x3 - ) returns (uint256 y1, uint256 y2, uint256 y3) { - a = 1; - } - - function someModifiersSomeReturns() - modifier1 - modifier2 - modifier3 - returns (uint256 y1, uint256 y2, uint256 y3) - { - a = 1; - } - - function someParamSomeModifiersSomeReturns( - uint256 x1, - uint256 x2, - uint256 x3 - ) - modifier1 - modifier2 - modifier3 - returns (uint256 y1, uint256 y2, uint256 y3) - { - a = 1; - } - - function someParamsManyModifiers( - uint256 x1, - uint256 x2, - uint256 x3 - ) - modifier1 - modifier2 - modifier3 - modifier4 - modifier5 - modifier6 - modifier7 - modifier8 - modifier9 - modifier10 - { - a = 1; - } - - function someParamsManyReturns( - uint256 x1, - uint256 x2, - uint256 x3 - ) - returns ( - uint256 y1, - uint256 y2, - uint256 y3, - uint256 y4, - uint256 y5, - uint256 y6, - uint256 y7, - uint256 y8, - uint256 y9, - uint256 y10 - ) - { - a = 1; - } - - function manyParamsSomeModifiers( - uint256 x1, - uint256 x2, - uint256 x3, - uint256 x4, - uint256 x5, - uint256 x6, - uint256 x7, - uint256 x8, - uint256 x9, - uint256 x10 - ) modifier1 modifier2 modifier3 { - a = 1; - } - - function manyParamsSomeReturns( - uint256 x1, - uint256 x2, - uint256 x3, - uint256 x4, - uint256 x5, - uint256 x6, - uint256 x7, - uint256 x8, - uint256 x9, - uint256 x10 - ) returns (uint256 y1, uint256 y2, uint256 y3) { - a = 1; - } - - function manyParamsManyModifiers( - uint256 x1, - uint256 x2, - uint256 x3, - uint256 x4, - uint256 x5, - uint256 x6, - uint256 x7, - uint256 x8, - uint256 x9, - uint256 x10 - ) - public - modifier1 - modifier2 - modifier3 - modifier4 - modifier5 - modifier6 - modifier7 - modifier8 - modifier9 - modifier10 - { - a = 1; - } - - function manyParamsManyReturns( - uint256 x1, - uint256 x2, - uint256 x3, - uint256 x4, - uint256 x5, - uint256 x6, - uint256 x7, - uint256 x8, - uint256 x9, - uint256 x10 - ) - returns ( - uint256 y1, - uint256 y2, - uint256 y3, - uint256 y4, - uint256 y5, - uint256 y6, - uint256 y7, - uint256 y8, - uint256 y9, - uint256 y10 - ) - { - a = 1; - } - - function manyParamsManyModifiersManyReturns( - uint256 x1, - uint256 x2, - uint256 x3, - uint256 x4, - uint256 x5, - uint256 x6, - uint256 x7, - uint256 x8, - uint256 x9, - uint256 x10 - ) - modifier1 - modifier2 - modifier3 - modifier4 - modifier5 - modifier6 - modifier7 - modifier8 - modifier9 - modifier10 - returns ( - uint256 y1, - uint256 y2, - uint256 y3, - uint256 y4, - uint256 y5, - uint256 y6, - uint256 y7, - uint256 y8, - uint256 y9, - uint256 y10 - ) - { - a = 1; - } - - function modifierOrderCorrect01() - public - view - virtual - override - modifier1 - modifier2 - returns (uint256) - { - a = 1; - } - - function modifierOrderCorrect02() - private - pure - virtual - modifier1 - modifier2 - returns (string) - { - a = 1; - } - - function modifierOrderCorrect03() - external - payable - override - modifier1 - modifier2 - returns (address) - { - a = 1; - } - - function modifierOrderCorrect04() - internal - virtual - override - modifier1 - modifier2 - returns (uint256) - { - a = 1; - } - - function modifierOrderIncorrect01() - public - view - virtual - override - modifier1 - modifier2 - returns (uint256) - { - a = 1; - } - - function modifierOrderIncorrect02() - external - virtual - override - modifier1 - modifier2 - returns (uint256) - { - a = 1; - } - - function modifierOrderIncorrect03() - internal - pure - virtual - modifier1 - modifier2 - returns (uint256) - { - a = 1; - } - - function modifierOrderIncorrect04() - external - payable - override - modifier1 - modifier2 - returns (uint256) - { - a = 1; - } - - fallback() external payable virtual {} - receive() external payable virtual {} -} - -contract FunctionOverrides is - FunctionInterfaces, - FunctionDefinitions -{ - function noParamsNoModifiersNoReturns() override { - a = 1; - } - - function oneParam(uint256 x) - override( - FunctionInterfaces, - FunctionDefinitions, - SomeOtherFunctionContract, - SomeImport.AndAnotherFunctionContract - ) - { - a = 1; - } -} From e2a4055956ae86cca96a229b4c37b81bf3bde857 Mon Sep 17 00:00:00 2001 From: 0xrusowsky <0xrusowsky@proton.me> Date: Fri, 27 Jun 2025 16:05:36 +0200 Subject: [PATCH 14/31] wip: fn header --- crates/fmt-2/src/lib.rs | 1 - crates/fmt-2/src/pp/mod.rs | 4 ++++ crates/fmt-2/src/state.rs | 22 +++++++++++++------ .../fmt/testdata/FunctionDefinition/fmt.sol | 8 +++---- 4 files changed, 23 insertions(+), 12 deletions(-) diff --git a/crates/fmt-2/src/lib.rs b/crates/fmt-2/src/lib.rs index a025ef88c71b1..53d0242e0a87e 100644 --- a/crates/fmt-2/src/lib.rs +++ b/crates/fmt-2/src/lib.rs @@ -126,7 +126,6 @@ fn format_inner( if first_result.is_err() { return first_result; } - return first_result; let Some(first_formatted) = first_result.ok_ref() else { return first_result }; // Second pass formatting diff --git a/crates/fmt-2/src/pp/mod.rs b/crates/fmt-2/src/pp/mod.rs index 3e998f43b1d0b..b43929d2f1194 100644 --- a/crates/fmt-2/src/pp/mod.rs +++ b/crates/fmt-2/src/pp/mod.rs @@ -129,6 +129,10 @@ impl Printer { } } + pub(crate) fn current_indent(&self) -> usize { + self.indent + } + pub(crate) fn last_token(&self) -> Option<&Token> { self.last_token_still_buffered().or(self.last_printed.as_ref()) } diff --git a/crates/fmt-2/src/state.rs b/crates/fmt-2/src/state.rs index 3ba4c1000d4a6..467e5f61c20d9 100644 --- a/crates/fmt-2/src/state.rs +++ b/crates/fmt-2/src/state.rs @@ -637,14 +637,14 @@ impl<'ast> State<'_, 'ast> { } = *header; self.cbox(0); - self.ibox(0); + // self.ibox(0); + let fn_indent = self.s.current_indent(); self.word(kind.to_str()); if let Some(name) = name { self.nbsp(); self.print_ident(&name); } - self.print_parameter_list(parameters, parameters_span, ListFormat::Consistent); - self.end(); + self.print_parameter_list(parameters, parameters_span, ListFormat::Inline); // Cache attribute comments first, as they can be wrongly ordered. let (mut attrib_comments, mut pending) = (Vec::new(), None); @@ -717,6 +717,7 @@ impl<'ast> State<'_, 'ast> { } self.s.cbox(self.ind); + self.s.cbox(0); if let Some(v) = visibility { self.print_attribute(visibility_span.unwrap(), &mut map, &mut |s| s.word(v.to_str())); } @@ -755,6 +756,7 @@ impl<'ast> State<'_, 'ast> { self.word("returns "); self.print_parameter_list(returns, returns_span, ListFormat::Consistent); } + self.end(); if let Some(body) = body { self.space(); @@ -1736,7 +1738,9 @@ impl<'ast> State<'_, 'ast> { self.word("{}"); } } else { - self.word("{"); + if !attempt_omit_braces { + self.word("{"); + } self.s.cbox(self.ind); self.hardbreak_if_nonempty(); for stmt in block { @@ -1746,7 +1750,9 @@ impl<'ast> State<'_, 'ast> { self.print_comments_skip_ws(block.last().map_or(span.hi(), |b| get_block_span(b).hi())); self.s.offset(-self.ind); self.end(); - self.word("}"); + if !attempt_omit_braces { + self.word("}"); + } } } @@ -1947,17 +1953,19 @@ impl<'ast> State<'_, 'ast> { for cmnt in pre_comments { self.print_comment(cmnt); } - if !self.is_bol_or_only_ind() { + if !self.is_bol_or_only_ind() && !self.last_token_is_space() { self.space(); } + self.ibox(0); print_fn(self); + self.end(); for cmnt in post_comments { self.print_comment(cmnt); } } // Fallback for attributes with no comments None => { - if !self.is_beginning_of_line() { + if !self.is_bol_or_only_ind() && !self.last_token_is_space() { self.space(); } print_fn(self); diff --git a/crates/fmt/testdata/FunctionDefinition/fmt.sol b/crates/fmt/testdata/FunctionDefinition/fmt.sol index ae9e892aadb6f..9fae7dd044c4d 100644 --- a/crates/fmt/testdata/FunctionDefinition/fmt.sol +++ b/crates/fmt/testdata/FunctionDefinition/fmt.sol @@ -38,8 +38,8 @@ interface FunctionInterfaces { // y2 prefix uint256 y2, // y2 postfix // y3 prefix - uint256 y3 - ); // y3 postfix + uint256 y3 // y3 postfix + ); // function postfix /*////////////////////////////////////////////////////////////////////////// @@ -323,10 +323,10 @@ interface FunctionInterfaces { } contract FunctionDefinitions { - function() external {} + function f() external {} fallback() external {} - function() external payable {} + function f() external payable {} fallback() external payable {} receive() external payable {} From 38f6f7c03d8fc09a90a315a4e096f716502999d9 Mon Sep 17 00:00:00 2001 From: 0xrusowsky <0xrusowsky@proton.me> Date: Tue, 1 Jul 2025 08:21:49 +0200 Subject: [PATCH 15/31] fix: doc block comments + block braces --- Cargo.lock | 60 +++++---- Cargo.toml | 2 +- crates/fmt-2/src/comments.rs | 54 ++++++--- crates/fmt-2/src/pp/helpers.rs | 11 +- crates/fmt-2/src/state.rs | 209 +++++++++++++++++++++++--------- crates/fmt-2/tests/formatter.rs | 12 +- 6 files changed, 230 insertions(+), 118 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b1c22079b7d11..4fa9f7f9925bf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2793,7 +2793,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "117725a109d387c937a1533ce01b450cbde6b88abceea8473c4d7a85853cda3c" dependencies = [ "lazy_static", - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] @@ -3596,7 +3596,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cea14ef9355e3beab063703aa9dab15afd25f0667c341310c1e5274bb1d0da18" dependencies = [ "libc", - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] @@ -3705,7 +3705,7 @@ checksum = "0ce92ff622d6dadf7349484f42c93271a0d49b7cc4d466a936405bacbe10aa78" dependencies = [ "cfg-if", "rustix 1.0.7", - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] @@ -4270,8 +4270,7 @@ dependencies = [ [[package]] name = "foundry-compilers" version = "0.17.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72acadbe10bbcf1d2765155808a4b79744061ffba3ce78680ac9c86bd2222802" +source = "git+https://github.com/foundry-rs/compilers.git?branch=rusowsky%2Fbump-solar#c9c43ce1a64710eb22cfdbef0d361703ac3cf0f5" dependencies = [ "alloy-json-abi", "alloy-primitives", @@ -4284,7 +4283,7 @@ dependencies = [ "fs_extra", "futures-util", "home", - "itertools 0.14.0", + "itertools 0.13.0", "path-slash", "rand 0.8.5", "rayon", @@ -4307,8 +4306,7 @@ dependencies = [ [[package]] name = "foundry-compilers-artifacts" version = "0.17.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f1f4c29ce88d24d1bebc5708bd14104534b0edbc9ddf2e76037bdb29a51c1c9" +source = "git+https://github.com/foundry-rs/compilers.git?branch=rusowsky%2Fbump-solar#c9c43ce1a64710eb22cfdbef0d361703ac3cf0f5" dependencies = [ "foundry-compilers-artifacts-solc", "foundry-compilers-artifacts-vyper", @@ -4317,8 +4315,7 @@ dependencies = [ [[package]] name = "foundry-compilers-artifacts-solc" version = "0.17.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03f145ddd61f912cd8dc9cb57acaca6d771dc51ef23908bb98ad9c5859430679" +source = "git+https://github.com/foundry-rs/compilers.git?branch=rusowsky%2Fbump-solar#c9c43ce1a64710eb22cfdbef0d361703ac3cf0f5" dependencies = [ "alloy-json-abi", "alloy-primitives", @@ -4326,6 +4323,7 @@ dependencies = [ "futures-util", "path-slash", "rayon", + "regex", "semver 1.0.26", "serde", "serde_json", @@ -4339,8 +4337,7 @@ dependencies = [ [[package]] name = "foundry-compilers-artifacts-vyper" version = "0.17.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d553aebe632477cff64cfa20fa3dbd819d3a454a6dfd748fde9206915e3fe0d9" +source = "git+https://github.com/foundry-rs/compilers.git?branch=rusowsky%2Fbump-solar#c9c43ce1a64710eb22cfdbef0d361703ac3cf0f5" dependencies = [ "alloy-json-abi", "alloy-primitives", @@ -4354,8 +4351,7 @@ dependencies = [ [[package]] name = "foundry-compilers-core" version = "0.17.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "afbd404c3ebcba24d0cb4746f78908ac0d681e9afb16a638a15c13d8e324e848" +source = "git+https://github.com/foundry-rs/compilers.git?branch=rusowsky%2Fbump-solar#c9c43ce1a64710eb22cfdbef0d361703ac3cf0f5" dependencies = [ "alloy-primitives", "cfg-if", @@ -5509,7 +5505,7 @@ checksum = "e04d7f318608d35d4b61ddd75cbdaee86b023ebe2bd5a66ee0915f0bf93095a9" dependencies = [ "hermit-abi", "libc", - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] @@ -5572,7 +5568,7 @@ dependencies = [ "portable-atomic", "portable-atomic-util", "serde", - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] @@ -7282,7 +7278,7 @@ dependencies = [ "once_cell", "socket2", "tracing", - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] @@ -7959,7 +7955,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys 0.4.15", - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] @@ -7972,7 +7968,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys 0.9.4", - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] @@ -8617,7 +8613,7 @@ dependencies = [ [[package]] name = "solar-ast" version = "0.1.4" -source = "git+https://github.com/0xrusowsky/solar?branch=rusowsky%2Ffn-header-spans#919763125cc65669611bc66a915ea45da2093806" +source = "git+https://github.com/0xrusowsky/solar?branch=rusowsky%2Ffn-header-spans#0b82a856ad5961e2bb5219e321d63a9069cb55f5" dependencies = [ "alloy-primitives", "bumpalo", @@ -8635,7 +8631,7 @@ dependencies = [ [[package]] name = "solar-config" version = "0.1.4" -source = "git+https://github.com/0xrusowsky/solar?branch=rusowsky%2Ffn-header-spans#919763125cc65669611bc66a915ea45da2093806" +source = "git+https://github.com/0xrusowsky/solar?branch=rusowsky%2Ffn-header-spans#0b82a856ad5961e2bb5219e321d63a9069cb55f5" dependencies = [ "strum 0.27.1", ] @@ -8643,7 +8639,7 @@ dependencies = [ [[package]] name = "solar-data-structures" version = "0.1.4" -source = "git+https://github.com/0xrusowsky/solar?branch=rusowsky%2Ffn-header-spans#919763125cc65669611bc66a915ea45da2093806" +source = "git+https://github.com/0xrusowsky/solar?branch=rusowsky%2Ffn-header-spans#0b82a856ad5961e2bb5219e321d63a9069cb55f5" dependencies = [ "bumpalo", "index_vec", @@ -8657,7 +8653,7 @@ dependencies = [ [[package]] name = "solar-interface" version = "0.1.4" -source = "git+https://github.com/0xrusowsky/solar?branch=rusowsky%2Ffn-header-spans#919763125cc65669611bc66a915ea45da2093806" +source = "git+https://github.com/0xrusowsky/solar?branch=rusowsky%2Ffn-header-spans#0b82a856ad5961e2bb5219e321d63a9069cb55f5" dependencies = [ "annotate-snippets", "anstream", @@ -8667,7 +8663,7 @@ dependencies = [ "derive_more 2.0.1", "dunce", "inturn", - "itertools 0.12.1", + "itertools 0.13.0", "itoa", "match_cfg", "normalize-path", @@ -8678,7 +8674,7 @@ dependencies = [ "solar-config", "solar-data-structures", "solar-macros", - "thiserror 2.0.12", + "thiserror 1.0.69", "tracing", "unicode-width 0.2.0", ] @@ -8686,7 +8682,7 @@ dependencies = [ [[package]] name = "solar-macros" version = "0.1.4" -source = "git+https://github.com/0xrusowsky/solar?branch=rusowsky%2Ffn-header-spans#919763125cc65669611bc66a915ea45da2093806" +source = "git+https://github.com/0xrusowsky/solar?branch=rusowsky%2Ffn-header-spans#0b82a856ad5961e2bb5219e321d63a9069cb55f5" dependencies = [ "proc-macro2", "quote", @@ -8696,12 +8692,12 @@ dependencies = [ [[package]] name = "solar-parse" version = "0.1.4" -source = "git+https://github.com/0xrusowsky/solar?branch=rusowsky%2Ffn-header-spans#919763125cc65669611bc66a915ea45da2093806" +source = "git+https://github.com/0xrusowsky/solar?branch=rusowsky%2Ffn-header-spans#0b82a856ad5961e2bb5219e321d63a9069cb55f5" dependencies = [ "alloy-primitives", "bitflags 2.9.1", "bumpalo", - "itertools 0.12.1", + "itertools 0.13.0", "memchr", "num-bigint", "num-rational", @@ -8716,7 +8712,7 @@ dependencies = [ [[package]] name = "solar-sema" version = "0.1.4" -source = "git+https://github.com/0xrusowsky/solar?branch=rusowsky%2Ffn-header-spans#919763125cc65669611bc66a915ea45da2093806" +source = "git+https://github.com/0xrusowsky/solar?branch=rusowsky%2Ffn-header-spans#0b82a856ad5961e2bb5219e321d63a9069cb55f5" dependencies = [ "alloy-json-abi", "alloy-primitives", @@ -9011,7 +9007,7 @@ dependencies = [ "serde_json", "sha2 0.10.9", "tempfile", - "thiserror 2.0.12", + "thiserror 1.0.69", "url", "zip", ] @@ -9108,7 +9104,7 @@ dependencies = [ "getrandom 0.3.3", "once_cell", "rustix 1.0.7", - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] @@ -10309,7 +10305,7 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index abbe3ff099050..3f062275139cf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -417,5 +417,5 @@ figment = { git = "https://github.com/DaniPopes/Figment", branch = "less-mono" } # revm-inspectors = { git = "https://github.com/paradigmxyz/revm-inspectors.git", rev = "a625c04" } ## foundry -# foundry-compilers = { git = "https://github.com/foundry-rs/compilers.git", rev = "e4a9b04" } +foundry-compilers = { git = "https://github.com/foundry-rs/compilers.git", branch = "rusowsky/bump-solar" } # foundry-fork-db = { git = "https://github.com/foundry-rs/foundry-fork-db", rev = "811a61a" } diff --git a/crates/fmt-2/src/comments.rs b/crates/fmt-2/src/comments.rs index 1dabeeea717df..f3decc6da6aca 100644 --- a/crates/fmt-2/src/comments.rs +++ b/crates/fmt-2/src/comments.rs @@ -62,30 +62,54 @@ fn normalize_block_comment(s: &str, col: CharPos) -> &str { s } +/// Formats a doc block comment line so that they have the ` *` decorator. +fn format_doc_block_comment(line: &str) -> String { + if line.is_empty() { + return (" *").to_string(); + } + + if let Some((_, second)) = line.split_once("*") { + if second.is_empty() { + (" *").to_string() + } else { + format!(" *{second}") + } + } else { + format!(" * {}", line) + } +} + fn split_block_comment_into_lines(text: &str, is_doc: bool, col: CharPos) -> Vec { let mut res: Vec = vec![]; let mut lines = text.lines(); - res.extend(lines.next().map(|it| it.to_string())); + if let Some(line) = lines.next() { + let line = line.trim_end(); + if let Some((_, second)) = line.split_once("/**") { + res.push("/**".to_string()); + if !second.trim().is_empty() { + res.push(format!(" * {}", second.trim())); + } + } else { + res.push(line.to_string()); + } + } + for (pos, line) in lines.into_iter().delimited() { - let mut line = normalize_block_comment(line, col).to_string(); - // For regular block comments, just normalize whitespace + let line = normalize_block_comment(line, col).trim_end().to_string(); if !is_doc { res.push(line); + continue; } - // Doc block comment lines must have the ` *` decorator - else { - if !pos.is_last { - if line.is_empty() { - line = format!(" *"); - } else if line.starts_with("*") { - line = format!(" {line}"); - } else if !line.starts_with(" *") { - line = format!(" * {line}"); + + if !pos.is_last { + res.push(format_doc_block_comment(&line)); + } else { + if let Some((first, _)) = line.split_once("*/") { + if !first.trim().is_empty() { + res.push(format_doc_block_comment(first)) } - res.push(line); - } else if line.trim() == "*/" { - res.push(" */".to_string()) } + res.push(" */".to_string()) } } res diff --git a/crates/fmt-2/src/pp/helpers.rs b/crates/fmt-2/src/pp/helpers.rs index 6cb8f41217586..983d22f097c85 100644 --- a/crates/fmt-2/src/pp/helpers.rs +++ b/crates/fmt-2/src/pp/helpers.rs @@ -11,13 +11,14 @@ impl Printer { /// If there was a buffered break token, replaces it (ensures hardbreak) keeping the offset. pub fn hardbreak_if_not_bol(&mut self) { if !self.is_bol_or_only_ind() { - match self.last_token_still_buffered() { - Some(Token::Break(last)) => { - self.replace_last_token_still_buffered(Self::hardbreak_tok_offset(last.offset)) + if let Some(Token::Break(last)) = self.last_token_still_buffered() { + if last.offset != 0 { + self.replace_last_token_still_buffered(Self::hardbreak_tok_offset(last.offset)); + return; } - // Some(Token::Begin(_) | Token::End) => {} - _ => self.hardbreak(), } + self.hardbreak(); + return; } } diff --git a/crates/fmt-2/src/state.rs b/crates/fmt-2/src/state.rs index 467e5f61c20d9..84a6ec0c234ea 100644 --- a/crates/fmt-2/src/state.rs +++ b/crates/fmt-2/src/state.rs @@ -5,13 +5,13 @@ use super::{ }; use crate::{ iter::{IterDelimited, IteratorPosition}, - pp::{BreakToken, Printer}, + pp::BreakToken, FormatterConfig, InlineConfig, }; use foundry_config::fmt as config; -use itertools::{Either, Itertools}; +use itertools::{peek_nth, Either, Itertools}; use solar_parse::{ - ast::{self, token, yul, Span}, + ast::{self, token, yul, Span, Type, TypeKind}, interface::{BytePos, SourceMap}, Cursor, }; @@ -29,6 +29,24 @@ enum ListFormat { Inline, } +/// Formatting style for code blocks +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum BlockFormat { + Regular, + /// Attempts to fit all elements in one line, before breaking consistently. Flags whether to + /// use braces or not. + Compact(bool), + /// Prints blocks without braces. Flags whether the block starts at a new line or not. + /// Usefull for complex box/break setups. + NoBraces(bool), +} + +impl BlockFormat { + fn attempt_single_line(&self) -> bool { + matches!(self, Self::Compact(_)) + } +} + // TODO(dani): trailing comments should always be passed Some pub(super) struct State<'sess, 'ast> { @@ -141,7 +159,7 @@ impl<'sess> State<'sess, '_> { } } CommentStyle::Trailing => { - if !self.is_bol_or_only_ind() { + if !self.is_bol_or_only_ind() && !self.last_token_is_space() { self.nbsp(); } if cmnt.lines.len() == 1 { @@ -310,8 +328,12 @@ impl<'sess> State<'sess, '_> { if !is_last { self.word(","); } - let next_pos = if is_last { None } else { get_span(&values[i + 1]).map(Span::lo) }; - self.print_comments_skip_ws(next_pos.unwrap_or_else(|| bracket_span.hi())); + let next_pos = if is_last { None } else { get_span(&values[i + 1]).map(Span::lo) } + .unwrap_or_else(|| bracket_span.hi()); + if self.peek_comment_before(next_pos).is_some() { + self.space(); + self.print_comments_skip_ws(next_pos); + } if self.is_bol_or_only_ind() { if is_last { // if a trailing comment is printed at the very end, we have to manually adjust @@ -623,33 +645,36 @@ impl<'ast> State<'_, 'ast> { let ast::FunctionHeader { name, ref parameters, - parameters_span, - visibility_span, visibility, - state_mutability_span, state_mutability, ref modifiers, virtual_, ref override_, ref returns, - returns_span, .. } = *header; + self.cbox(0); + self.s.cbox(self.ind); + let fn_start_indent = self.s.current_indent(); + + // self.ibox(0); // self.ibox(0); - let fn_indent = self.s.current_indent(); self.word(kind.to_str()); if let Some(name) = name { self.nbsp(); self.print_ident(&name); } - self.print_parameter_list(parameters, parameters_span, ListFormat::Inline); + // let fn_start_indent = self.s.expected_current_indent(); + self.s.ibox(-self.ind); + self.print_parameter_list(parameters, parameters.span, ListFormat::Inline); + self.end(); // Cache attribute comments first, as they can be wrongly ordered. let (mut attrib_comments, mut pending) = (Vec::new(), None); for cmnt in self.comments.iter() { - if cmnt.pos() >= returns_span.lo() { + if cmnt.pos() >= returns.span.lo() { break; } @@ -673,14 +698,11 @@ impl<'ast> State<'_, 'ast> { let mut attributes: Vec> = Vec::new(); if let Some(v) = visibility { - attributes.push(AttributeInfo { - kind: AttributeKind::Visibility(v), - span: visibility_span.unwrap(), - }); + attributes.push(AttributeInfo { kind: AttributeKind::Visibility(*v), span: v.span }); } attributes.push(AttributeInfo { - kind: AttributeKind::StateMutability(state_mutability), - span: state_mutability_span, + kind: AttributeKind::StateMutability(*state_mutability), + span: state_mutability.span, }); if let Some(v) = virtual_ { attributes.push(AttributeInfo { kind: AttributeKind::Virtual, span: v }); @@ -702,7 +724,7 @@ impl<'ast> State<'_, 'ast> { let before_limit = attributes[a].span.lo(); let after_limit = - if !is_last { attributes[a + 1].span.lo() } else { returns_span.lo() }; + if !is_last { attributes[a + 1].span.lo() } else { returns.span.lo() }; let mut c = 0; while c < attrib_comments.len() { if attrib_comments[c].pos() <= before_limit { @@ -716,16 +738,16 @@ impl<'ast> State<'_, 'ast> { map.insert(before_limit, (before, after)); } - self.s.cbox(self.ind); + self.s.cbox(0); self.s.cbox(0); if let Some(v) = visibility { - self.print_attribute(visibility_span.unwrap(), &mut map, &mut |s| s.word(v.to_str())); + self.print_attribute(v.span, &mut map, &mut |s| s.word(v.to_str())); } - if state_mutability != ast::StateMutability::NonPayable { + if *state_mutability != ast::StateMutability::NonPayable { if !self.is_bol_or_only_ind() && !self.last_token_is_space() { self.space(); } - self.print_attribute(state_mutability_span, &mut map, &mut |s| { + self.print_attribute(state_mutability.span, &mut map, &mut |s| { s.word(state_mutability.to_str()) }); } @@ -754,17 +776,31 @@ impl<'ast> State<'_, 'ast> { self.space(); } self.word("returns "); - self.print_parameter_list(returns, returns_span, ListFormat::Consistent); + self.print_parameter_list(returns, returns.span, ListFormat::Consistent); } self.end(); if let Some(body) = body { - self.space(); - self.s.offset(-self.ind); - self.word(""); + let current_indent = self.s.current_indent(); + let new_line = current_indent > fn_start_indent; + if !new_line { + self.nbsp(); + self.word("{"); + self.end(); + if !body.is_empty() { + self.hardbreak(); + } + } else { + self.space(); + self.s.offset(-self.ind); + self.word("{"); + self.end(); + } self.end(); - self.print_block(body, body_span); + self.print_block_without_braces(body, body_span, new_line); + self.word("}"); } else { + self.end(); self.neverbreak(); self.s.offset(-self.ind); self.end(); @@ -798,7 +834,7 @@ impl<'ast> State<'_, 'ast> { let ast::ItemError { name, parameters } = err; self.word("error "); self.print_ident(name); - self.print_parameter_list(parameters, Span::DUMMY, ListFormat::Consistent); + self.print_parameter_list(parameters, parameters.span, ListFormat::Consistent); self.word(";"); } @@ -806,7 +842,7 @@ impl<'ast> State<'_, 'ast> { let ast::ItemEvent { name, parameters, anonymous } = event; self.word("event "); self.print_ident(name); - self.print_parameter_list(parameters, Span::DUMMY, ListFormat::Consistent); + self.print_parameter_list(parameters, parameters.span, ListFormat::Compact); if *anonymous { self.word(" anonymous"); } @@ -835,6 +871,10 @@ impl<'ast> State<'_, 'ast> { return; } + if matches!(ty.kind, TypeKind::Function(_)) { + self.zerobreak(); + self.cbox(0); + } self.print_ty(ty); if let Some(visibility) = visibility { self.nbsp(); @@ -865,6 +905,9 @@ impl<'ast> State<'_, 'ast> { self.neverbreak(); self.print_expr(initializer); } + if matches!(ty.kind, TypeKind::Function(_)) { + self.end(); + } } fn print_parameter_list( @@ -895,7 +938,7 @@ impl<'ast> State<'_, 'ast> { } fn print_ident(&mut self, ident: &ast::Ident) { - self.print_comments(ident.span.lo()); + self.print_comments_skip_ws(ident.span.lo()); self.word(ident.to_string()); } @@ -1129,6 +1172,7 @@ impl<'ast> State<'_, 'ast> { returns: _, }) => { // LEGACY: not implemented. + // TODO(rusowsky): print manually self.print_span(ty.span); } ast::TypeKind::Mapping(ast::TypeMapping { key, key_name, value, value_name }) => { @@ -1400,7 +1444,7 @@ impl<'ast> State<'_, 'ast> { span, |this, e| this.print_expr(e), get_span!(), - ListFormat::Consistent, + ListFormat::Compact, ); } ast::CallArgsKind::Named(named_args) => { @@ -1413,21 +1457,35 @@ impl<'ast> State<'_, 'ast> { fn print_named_args(&mut self, args: &'ast [ast::NamedArg<'ast>]) { self.word("{"); - self.s.cbox(self.ind); - self.braces_break(); - for (pos, ast::NamedArg { name, value }) in args.iter().delimited() { + self.s.ibox(self.ind); + let (should_print, pos) = match (args.first(), self.peek_comment()) { + (Some(arg), Some(cmnt)) => (cmnt.pos() < arg.name.span.lo(), arg.name.span.lo()), + _ => (false, BytePos::from_u32(0)), + }; + if should_print { + self.space(); + self.print_comments_skip_ws(pos); + } else { + self.braces_break(); + } + self.cbox(0); + + for (i, ast::NamedArg { name, value }) in args.iter().enumerate() { self.print_ident(name); self.word(": "); self.print_expr(value); - if pos.is_last { + if i == args.len() - 1 { self.braces_break(); } else { self.word(","); - self.space(); + if self.print_comments_skip_ws(args[i + 1].name.span.lo()).is_none() { + self.space(); + } } } self.s.offset(-self.ind); self.end(); + self.end(); self.word("}"); } @@ -1487,9 +1545,10 @@ impl<'ast> State<'_, 'ast> { ast::StmtKind::Expr(expr) => self.print_expr(expr), ast::StmtKind::For { init, cond, next, body } => { self.cbox(0); - self.ibox(0); + self.s.ibox(self.ind); self.word("for ("); self.zerobreak(); + self.s.cbox(0); if let Some(init) = init { self.print_stmt(init); } else { @@ -1505,8 +1564,11 @@ impl<'ast> State<'_, 'ast> { if let Some(next) = next { self.space(); self.print_expr(next); + } else { + self.zerobreak(); } - self.zerobreak(); + self.break_offset_if_not_bol(0, -self.ind); + self.end(); self.word(") "); self.neverbreak(); self.end(); @@ -1653,7 +1715,22 @@ impl<'ast> State<'_, 'ast> { } fn print_block(&mut self, block: &'ast [ast::Stmt<'ast>], span: Span) { - self.print_block_inner(block, Self::print_stmt, |b| b.span, span, false, false); + self.print_block_inner(block, BlockFormat::Regular, Self::print_stmt, |b| b.span, span); + } + + fn print_block_without_braces( + &mut self, + block: &'ast [ast::Stmt<'ast>], + span: Span, + new_line: bool, + ) { + self.print_block_inner( + block, + BlockFormat::NoBraces(new_line), + Self::print_stmt, + |b| b.span, + span, + ); } // Body of a if/loop. @@ -1665,11 +1742,10 @@ impl<'ast> State<'_, 'ast> { }; self.print_block_inner( stmts, + if attempt_single_line { BlockFormat::Compact(true) } else { BlockFormat::Regular }, Self::print_stmt, |b| b.span, stmt.span, - attempt_single_line, - true, ) } @@ -1681,27 +1757,44 @@ impl<'ast> State<'_, 'ast> { ) { self.print_block_inner( block, + if attempt_single_line { BlockFormat::Compact(false) } else { BlockFormat::Regular }, Self::print_yul_stmt, |b| b.span, span, - attempt_single_line, - false, ); } fn print_block_inner( &mut self, block: &'ast [T], + block_format: BlockFormat, mut print: impl FnMut(&mut Self, &'ast T), mut get_block_span: impl FnMut(&'ast T) -> Span, span: Span, - attempt_single_line: bool, - attempt_omit_braces: bool, ) { - // TODO(dani): might need to adjust span for `single_line_block` to include the if condition - if attempt_single_line && block.len() == 1 && self.single_line_block(span) { + // Braces are explicitly handled by the caller + if let BlockFormat::NoBraces(new_line) = block_format { + if new_line { + self.s.cbox(self.ind); + self.hardbreak_if_nonempty(); + } else { + self.s.cbox(self.ind); + } + for stmt in block { + print(self, stmt); + self.hardbreak_if_not_bol(); + } + self.print_comments_skip_ws(block.last().map_or(span.hi(), |b| get_block_span(b).hi())); + self.s.offset(-self.ind); + self.end(); + } + // Attempt to print in a single line + else if block_format.attempt_single_line() && + block.len() == 1 && + self.single_line_block(span) + { self.s.cbox(self.ind); - if attempt_omit_braces { + if matches!(block_format, BlockFormat::Compact(true)) { self.scan_break(BreakToken { pre_break: Some('{'), ..Default::default() }); } else { self.word("{"); @@ -1709,7 +1802,7 @@ impl<'ast> State<'_, 'ast> { } print(self, &block[0]); self.print_comments_skip_ws(get_block_span(&block[0]).hi()); - if attempt_omit_braces { + if matches!(block_format, BlockFormat::Compact(true)) { self.s.scan_break(BreakToken { post_break: Some('}'), ..Default::default() }); self.s.offset(-self.ind); } else { @@ -1719,7 +1812,7 @@ impl<'ast> State<'_, 'ast> { } self.end(); } - // Special handling for empty blocks, as they could have comments. + // Empty blocks with comments require special attention else if block.is_empty() { if let Some(comment) = self.peek_comment() { if !matches!(comment.style, CommentStyle::Mixed) { @@ -1737,10 +1830,10 @@ impl<'ast> State<'_, 'ast> { } else { self.word("{}"); } - } else { - if !attempt_omit_braces { - self.word("{"); - } + } + // Regular blocks + else { + self.word("{"); self.s.cbox(self.ind); self.hardbreak_if_nonempty(); for stmt in block { @@ -1750,9 +1843,7 @@ impl<'ast> State<'_, 'ast> { self.print_comments_skip_ws(block.last().map_or(span.hi(), |b| get_block_span(b).hi())); self.s.offset(-self.ind); self.end(); - if !attempt_omit_braces { - self.word("}"); - } + self.word("}"); } } diff --git a/crates/fmt-2/tests/formatter.rs b/crates/fmt-2/tests/formatter.rs index d5dac0b381620..6a917eebdbbdf 100644 --- a/crates/fmt-2/tests/formatter.rs +++ b/crates/fmt-2/tests/formatter.rs @@ -152,14 +152,14 @@ macro_rules! fmt_tests { fmt_tests! { #[ignore = "annotations are not valid Solidity"] Annotation, - ArrayExpressions, + ArrayExpressions, // TODO BlockComments, BlockCommentsFunction, ConditionalOperatorExpression, ConstructorDefinition, ConstructorModifierStyle, - ContractDefinition, - DocComments, + ContractDefinition, // TODO + DocComments, // TODO: wrap comments DoWhileStatement, EmitStatement, EnumDefinition, @@ -167,10 +167,10 @@ fmt_tests! { ErrorDefinition, EventDefinition, ForStatement, - FunctionCall, + FunctionCall, // TODO: fix PP so that comments aren't accounted for when breaking lines FunctionCallArgsStatement, - FunctionDefinition, - FunctionDefinitionWithFunctionReturns, + FunctionDefinition, // TODO: fix fn block braces + FunctionDefinitionWithFunctionReturns, // TODO: impl return fn FunctionType, HexUnderscore, IfStatement, From 6d178bb6178556929b17df3fc8463298d9a404a4 Mon Sep 17 00:00:00 2001 From: 0xrusowsky <0xrusowsky@proton.me> Date: Wed, 2 Jul 2025 11:20:13 +0200 Subject: [PATCH 16/31] refactor state to organize helpers --- Cargo.lock | 18 +- Cargo.toml | 8 +- crates/fmt-2/src/comments.rs | 11 +- crates/fmt-2/src/pp/convenience.rs | 43 ++-- crates/fmt-2/src/state.rs | 356 +++++++++++++++++------------ crates/fmt-2/tests/formatter.rs | 2 +- 6 files changed, 247 insertions(+), 191 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4fa9f7f9925bf..02484e96f6547 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8613,7 +8613,7 @@ dependencies = [ [[package]] name = "solar-ast" version = "0.1.4" -source = "git+https://github.com/0xrusowsky/solar?branch=rusowsky%2Ffn-header-spans#0b82a856ad5961e2bb5219e321d63a9069cb55f5" +source = "git+https://github.com/paradigmxyz/solar?branch=main#48d1d85dfbdaf27dc75f7844ff52c3a4a062b70b" dependencies = [ "alloy-primitives", "bumpalo", @@ -8631,7 +8631,7 @@ dependencies = [ [[package]] name = "solar-config" version = "0.1.4" -source = "git+https://github.com/0xrusowsky/solar?branch=rusowsky%2Ffn-header-spans#0b82a856ad5961e2bb5219e321d63a9069cb55f5" +source = "git+https://github.com/paradigmxyz/solar?branch=main#48d1d85dfbdaf27dc75f7844ff52c3a4a062b70b" dependencies = [ "strum 0.27.1", ] @@ -8639,7 +8639,7 @@ dependencies = [ [[package]] name = "solar-data-structures" version = "0.1.4" -source = "git+https://github.com/0xrusowsky/solar?branch=rusowsky%2Ffn-header-spans#0b82a856ad5961e2bb5219e321d63a9069cb55f5" +source = "git+https://github.com/paradigmxyz/solar?branch=main#48d1d85dfbdaf27dc75f7844ff52c3a4a062b70b" dependencies = [ "bumpalo", "index_vec", @@ -8653,7 +8653,7 @@ dependencies = [ [[package]] name = "solar-interface" version = "0.1.4" -source = "git+https://github.com/0xrusowsky/solar?branch=rusowsky%2Ffn-header-spans#0b82a856ad5961e2bb5219e321d63a9069cb55f5" +source = "git+https://github.com/paradigmxyz/solar?branch=main#48d1d85dfbdaf27dc75f7844ff52c3a4a062b70b" dependencies = [ "annotate-snippets", "anstream", @@ -8663,7 +8663,7 @@ dependencies = [ "derive_more 2.0.1", "dunce", "inturn", - "itertools 0.13.0", + "itertools 0.10.5", "itoa", "match_cfg", "normalize-path", @@ -8682,7 +8682,7 @@ dependencies = [ [[package]] name = "solar-macros" version = "0.1.4" -source = "git+https://github.com/0xrusowsky/solar?branch=rusowsky%2Ffn-header-spans#0b82a856ad5961e2bb5219e321d63a9069cb55f5" +source = "git+https://github.com/paradigmxyz/solar?branch=main#48d1d85dfbdaf27dc75f7844ff52c3a4a062b70b" dependencies = [ "proc-macro2", "quote", @@ -8692,12 +8692,12 @@ dependencies = [ [[package]] name = "solar-parse" version = "0.1.4" -source = "git+https://github.com/0xrusowsky/solar?branch=rusowsky%2Ffn-header-spans#0b82a856ad5961e2bb5219e321d63a9069cb55f5" +source = "git+https://github.com/paradigmxyz/solar?branch=main#48d1d85dfbdaf27dc75f7844ff52c3a4a062b70b" dependencies = [ "alloy-primitives", "bitflags 2.9.1", "bumpalo", - "itertools 0.13.0", + "itertools 0.10.5", "memchr", "num-bigint", "num-rational", @@ -8712,7 +8712,7 @@ dependencies = [ [[package]] name = "solar-sema" version = "0.1.4" -source = "git+https://github.com/0xrusowsky/solar?branch=rusowsky%2Ffn-header-spans#0b82a856ad5961e2bb5219e321d63a9069cb55f5" +source = "git+https://github.com/paradigmxyz/solar?branch=main#48d1d85dfbdaf27dc75f7844ff52c3a4a062b70b" dependencies = [ "alloy-json-abi", "alloy-primitives", diff --git a/Cargo.toml b/Cargo.toml index 3f062275139cf..a83cc4b73be64 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -364,10 +364,10 @@ idna_adapter = "=1.1.0" zip-extract = "=0.2.1" [patch.crates-io] -solar-parse = { git = "https://github.com/0xrusowsky/solar", branch = "rusowsky/fn-header-spans" } -solar-sema = { git = "https://github.com/0xrusowsky/solar", branch = "rusowsky/fn-header-spans" } -solar-ast = { git = "https://github.com/0xrusowsky/solar", branch = "rusowsky/fn-header-spans" } -solar-interface = { git = "https://github.com/0xrusowsky/solar", branch = "rusowsky/fn-header-spans" } +solar-parse = { git = "https://github.com/paradigmxyz/solar", branch = "main" } +solar-sema = { git = "https://github.com/paradigmxyz/solar", branch = "main" } +solar-ast = { git = "https://github.com/paradigmxyz/solar", branch = "main" } +solar-interface = { git = "https://github.com/paradigmxyz/solar", branch = "main" } figment = { git = "https://github.com/DaniPopes/Figment", branch = "less-mono" } diff --git a/crates/fmt-2/src/comments.rs b/crates/fmt-2/src/comments.rs index f3decc6da6aca..68eeb8329e9cd 100644 --- a/crates/fmt-2/src/comments.rs +++ b/crates/fmt-2/src/comments.rs @@ -79,6 +79,7 @@ fn format_doc_block_comment(line: &str) -> String { } } +/// Splits a block comment into lines, ensuring that each line is properly formatted. fn split_block_comment_into_lines(text: &str, is_doc: bool, col: CharPos) -> Vec { let mut res: Vec = vec![]; let mut lines = text.lines(); @@ -100,7 +101,6 @@ fn split_block_comment_into_lines(text: &str, is_doc: bool, col: CharPos) -> Vec res.push(line); continue; } - if !pos.is_last { res.push(format_doc_block_comment(&line)); } else { @@ -173,9 +173,10 @@ fn gather_comments(sf: &SourceFile) -> Vec { !matches!(text[pos + token.len as usize..].chars().next(), Some('\r' | '\n')); let style = match (code_to_the_left, code_to_the_right) { (false, false) => CommentStyle::Isolated, - // Unlike with `Trailing` comments, which are always printed with a hardbreak, - // `Mixed` comments should be followed by a space and defer breaks to the - // printer. Because of that, non-isolated code blocks are labeled as mixed. + // NOTE(rusowsky): unlike with `Trailing` comments, which are always printed + // with a hardbreak, `Mixed` comments should be followed by a space and defer + // breaks to the printer. Because of that, non-isolated code blocks are labeled + // as mixed. _ => CommentStyle::Mixed, }; let kind = CommentKind::Block; @@ -198,7 +199,7 @@ fn gather_comments(sf: &SourceFile) -> Vec { } else { CommentStyle::Isolated }, - lines: vec![token_text.to_string()], + lines: vec![token_text.trim_end().to_string()], span, }); } diff --git a/crates/fmt-2/src/pp/convenience.rs b/crates/fmt-2/src/pp/convenience.rs index 7aa8b57aa1d43..368ae8c5cf796 100644 --- a/crates/fmt-2/src/pp/convenience.rs +++ b/crates/fmt-2/src/pp/convenience.rs @@ -54,11 +54,18 @@ impl Printer { self.spaces(SIZE_INFINITY as usize); } + pub fn last_token_is_neverbreak(&self) -> bool { + if let Some(token) = self.last_token() { + return token.is_neverbreak(); + } + + false + } + pub fn last_token_is_hardbreak(&self) -> bool { if let Some(token) = self.last_token() { return token.is_hardbreak(); } - false } @@ -68,7 +75,6 @@ impl Printer { return true; } } - let res = self.out.ends_with(" "); res } @@ -165,32 +171,25 @@ impl Printer { } impl Token { + pub(crate) fn is_neverbreak(&self) -> bool { + if let Self::Break(BreakToken { never_break, .. }) = *self { + return never_break; + } + false + } + pub(crate) fn is_hardbreak(&self) -> bool { - if let Self::Break(BreakToken { - offset, - blank_space, - pre_break: _, - post_break: _, - if_nonempty: _, - never_break, - }) = *self - { - offset == 0 && blank_space == SIZE_INFINITY as usize && !never_break - } else { - false + if let Self::Break(BreakToken { offset, blank_space, never_break, .. }) = *self { + return offset == 0 && blank_space == SIZE_INFINITY as usize && !never_break; } + false } pub(crate) fn is_space(&self) -> bool { match self { - Self::Break(BreakToken { - offset, - blank_space, - pre_break: _, - post_break: _, - if_nonempty: _, - never_break: _, - }) => *offset == 0 && *blank_space == 1, + Self::Break(BreakToken { offset, blank_space, .. }) => { + *offset == 0 && *blank_space == 1 + } Self::String(s) => s.ends_with(' '), _ => false, } diff --git a/crates/fmt-2/src/state.rs b/crates/fmt-2/src/state.rs index 84a6ec0c234ea..44205c9315a24 100644 --- a/crates/fmt-2/src/state.rs +++ b/crates/fmt-2/src/state.rs @@ -1,52 +1,19 @@ -use super::{ +use crate::{ comment::{Comment, CommentStyle}, comments::Comments, - pp::{self, Token}, -}; -use crate::{ iter::{IterDelimited, IteratorPosition}, - pp::BreakToken, + pp::{self, BreakToken, Token}, FormatterConfig, InlineConfig, }; use foundry_config::fmt as config; -use itertools::{peek_nth, Either, Itertools}; +use itertools::{Either, Itertools}; use solar_parse::{ - ast::{self, token, yul, Span, Type, TypeKind}, + ast::{self, token, yul, Span}, interface::{BytePos, SourceMap}, Cursor, }; use std::{borrow::Cow, collections::HashMap}; -/// Formatting style for comma-separated lists -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -enum ListFormat { - /// Breaks all elements if any break. - Consistent, - /// Attempts to fit all elements in one line, before breaking consistently. - Compact, - /// If the list contains just one element, it will print unboxed (will not break). - /// Otherwise, will break consistently. - Inline, -} - -/// Formatting style for code blocks -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -enum BlockFormat { - Regular, - /// Attempts to fit all elements in one line, before breaking consistently. Flags whether to - /// use braces or not. - Compact(bool), - /// Prints blocks without braces. Flags whether the block starts at a new line or not. - /// Usefull for complex box/break setups. - NoBraces(bool), -} - -impl BlockFormat { - fn attempt_single_line(&self) -> bool { - matches!(self, Self::Compact(_)) - } -} - // TODO(dani): trailing comments should always be passed Some pub(super) struct State<'sess, 'ast> { @@ -127,10 +94,9 @@ impl<'sess> State<'sess, '_> { fn print_comment(&mut self, mut cmnt: Comment) { match cmnt.style { CommentStyle::Mixed => { - // TODO(dani): ? - if !self.is_bol_or_only_ind() { + let never_break = self.last_token_is_neverbreak(); + if !self.is_bol_or_only_ind() && !never_break { self.zerobreak(); - // self.space(); } if let Some(last) = cmnt.lines.pop() { self.ibox(0); @@ -145,7 +111,9 @@ impl<'sess> State<'sess, '_> { self.end(); } - self.zerobreak(); + if never_break { + self.neverbreak(); + } } CommentStyle::Isolated => { self.hardbreak_if_not_bol(); @@ -307,8 +275,20 @@ impl<'sess> State<'sess, '_> { } self.s.cbox(self.ind); - let first_pos = get_span(&values[0]).map(Span::lo); - let skip_break = self.print_trailing_comment(bracket_span.lo(), first_pos); + let skip_break = { + if let Some(first_pos) = get_span(&values[0]).map(Span::lo) { + if let Some(CommentStyle::Trailing) = + self.peek_comment_before(first_pos).map(|cmnt| cmnt.style) + { + self.nbsp(); + self.print_trailing_comment(bracket_span.lo(), Some(first_pos)) + } else { + false + } + } else { + false + } + }; if !skip_break { self.zerobreak(); } @@ -647,7 +627,6 @@ impl<'ast> State<'_, 'ast> { ref parameters, visibility, state_mutability, - ref modifiers, virtual_, ref override_, ref returns, @@ -658,96 +637,31 @@ impl<'ast> State<'_, 'ast> { self.s.cbox(self.ind); let fn_start_indent = self.s.current_indent(); - // self.ibox(0); - - // self.ibox(0); + // Print fn name and params self.word(kind.to_str()); if let Some(name) = name { self.nbsp(); self.print_ident(&name); } - // let fn_start_indent = self.s.expected_current_indent(); self.s.ibox(-self.ind); self.print_parameter_list(parameters, parameters.span, ListFormat::Inline); self.end(); - // Cache attribute comments first, as they can be wrongly ordered. - let (mut attrib_comments, mut pending) = (Vec::new(), None); - for cmnt in self.comments.iter() { - if cmnt.pos() >= returns.span.lo() { - break; - } - - match pending { - Some(ref p) => pending = Some(p + 1), - None => pending = Some(0), - } - } - while let Some(p) = pending { - if p == 0 { - pending = None; - } else { - pending = Some(p - 1); - } - let cmnt = self.next_comment().unwrap(); - if cmnt.style == CommentStyle::BlankLine { - continue; - } - attrib_comments.push(cmnt); - } - - let mut attributes: Vec> = Vec::new(); - if let Some(v) = visibility { - attributes.push(AttributeInfo { kind: AttributeKind::Visibility(*v), span: v.span }); - } - attributes.push(AttributeInfo { - kind: AttributeKind::StateMutability(*state_mutability), - span: state_mutability.span, - }); - if let Some(v) = virtual_ { - attributes.push(AttributeInfo { kind: AttributeKind::Virtual, span: v }); - } - if let Some(o) = override_ { - attributes.push(AttributeInfo { kind: AttributeKind::Override(o), span: o.span }); - } - for m in modifiers.iter() { - attributes.push(AttributeInfo { kind: AttributeKind::Modifier(m), span: m.span() }); - } - attributes.sort_by_key(|attr| attr.span.lo()); - - // Claim comments for each source-ordered attribute - let mut map: HashMap, Vec)> = HashMap::new(); - for a in 0..attributes.len() { - let is_last = a == attributes.len() - 1; - let mut before = Vec::new(); - let mut after = Vec::new(); - - let before_limit = attributes[a].span.lo(); - let after_limit = - if !is_last { attributes[a + 1].span.lo() } else { returns.span.lo() }; - let mut c = 0; - while c < attrib_comments.len() { - if attrib_comments[c].pos() <= before_limit { - before.push(attrib_comments.remove(c)); - } else if (after.is_empty() || is_last) && attrib_comments[c].pos() <= after_limit { - after.push(attrib_comments.remove(c)); - } else { - c += 1; - } - } - map.insert(before_limit, (before, after)); - } + // Map attributes to their corresponding comments + let (attributes, mut map) = + AttributeCommentMapper::new(header.returns.span.lo()).build(self, header); self.s.cbox(0); self.s.cbox(0); + // Print fn attributes in correct order if let Some(v) = visibility { - self.print_attribute(v.span, &mut map, &mut |s| s.word(v.to_str())); + self.print_fn_attribute(v.span, &mut map, &mut |s| s.word(v.to_str())); } if *state_mutability != ast::StateMutability::NonPayable { if !self.is_bol_or_only_ind() && !self.last_token_is_space() { self.space(); } - self.print_attribute(state_mutability.span, &mut map, &mut |s| { + self.print_fn_attribute(state_mutability.span, &mut map, &mut |s| { s.word(state_mutability.to_str()) }); } @@ -755,18 +669,18 @@ impl<'ast> State<'_, 'ast> { if !self.is_bol_or_only_ind() && !self.last_token_is_space() { self.space(); } - self.print_attribute(virtual_, &mut map, &mut |s| s.word("virtual")); + self.print_fn_attribute(virtual_, &mut map, &mut |s| s.word("virtual")); } if let Some(override_) = override_ { if !self.is_bol_or_only_ind() && !self.last_token_is_space() { self.space(); } - self.print_attribute(override_.span, &mut map, &mut |s| s.print_override(override_)); + self.print_fn_attribute(override_.span, &mut map, &mut |s| s.print_override(override_)); } for m in attributes.iter().filter(|a| matches!(a.kind, AttributeKind::Modifier(_))) { if let AttributeKind::Modifier(modifier) = m.kind { let is_base = self.is_modifier_a_base_contract(kind, modifier); - self.print_attribute(m.span, &mut map, &mut |s| { + self.print_fn_attribute(m.span, &mut map, &mut |s| { s.print_modifier_call(modifier, is_base) }); } @@ -780,6 +694,7 @@ impl<'ast> State<'_, 'ast> { } self.end(); + // Print fn body if let Some(body) = body { let current_indent = self.s.current_indent(); let new_line = current_indent > fn_start_indent; @@ -871,10 +786,6 @@ impl<'ast> State<'_, 'ast> { return; } - if matches!(ty.kind, TypeKind::Function(_)) { - self.zerobreak(); - self.cbox(0); - } self.print_ty(ty); if let Some(visibility) = visibility { self.nbsp(); @@ -905,9 +816,6 @@ impl<'ast> State<'_, 'ast> { self.neverbreak(); self.print_expr(initializer); } - if matches!(ty.kind, TypeKind::Function(_)) { - self.end(); - } } fn print_parameter_list( @@ -919,6 +827,7 @@ impl<'ast> State<'_, 'ast> { self.print_tuple(parameters, span, Self::print_var, get_span!(), format); } + // NOTE(rusowsky): is this needed? fn print_docs(&mut self, docs: &'ast ast::DocComments<'ast>) { // Handled with `self.comments`. let _ = docs; @@ -1166,14 +1075,30 @@ impl<'ast> State<'_, 'ast> { } } ast::TypeKind::Function(ast::TypeFunction { - parameters: _, - visibility: _, - state_mutability: _, - returns: _, + parameters, + visibility, + state_mutability, + returns, }) => { - // LEGACY: not implemented. - // TODO(rusowsky): print manually - self.print_span(ty.span); + self.cbox(0); + self.word("function"); + self.print_parameter_list(parameters, parameters.span, ListFormat::Inline); + self.space(); + + if let Some(v) = visibility { + self.word(v.to_str()); + self.nbsp(); + } + if !matches!(**state_mutability, ast::StateMutability::NonPayable) { + self.word(state_mutability.to_str()); + self.nbsp(); + } + if !returns.is_empty() { + self.word("returns"); + self.nbsp(); + self.print_parameter_list(returns, returns.span, ListFormat::Consistent); + } + self.end(); } ast::TypeKind::Mapping(ast::TypeMapping { key, key_name, value, value_name }) => { self.word("mapping("); @@ -1581,6 +1506,7 @@ impl<'ast> State<'_, 'ast> { self.print_if_no_else(cond, then); let mut els_opt = els_opt.as_deref(); while let Some(els) = els_opt { + self.print_comments_skip_ws(els.span.lo()); if self.ends_with('}') { self.nbsp(); } else { @@ -1699,7 +1625,7 @@ impl<'ast> State<'_, 'ast> { cond.span, Self::print_expr, get_span!(), - ListFormat::Consistent, + ListFormat::Compact, ); } @@ -2033,7 +1959,7 @@ impl<'ast> State<'_, 'ast> { } } - fn print_attribute( + fn print_fn_attribute( &mut self, span: Span, map: &mut HashMap, Vec)>, @@ -2065,6 +1991,151 @@ impl<'ast> State<'_, 'ast> { } } +// -- HELPERS ----------------------------------------------------------------- +// TODO(rusowsky): move to its own file + +/// Formatting style for comma-separated lists +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub(crate) enum ListFormat { + /// Breaks all elements if any break. + Consistent, + /// Attempts to fit all elements in one line, before breaking consistently. + Compact, + /// If the list contains just one element, it will print unboxed (will not break). + /// Otherwise, will break consistently. + Inline, +} + +/// Formatting style for code blocks +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub(crate) enum BlockFormat { + Regular, + /// Attempts to fit all elements in one line, before breaking consistently. Flags whether to + /// use braces or not. + Compact(bool), + /// Prints blocks without braces. Flags whether the block starts at a new line or not. + /// Usefull for complex box/break setups. + NoBraces(bool), +} + +impl BlockFormat { + pub(crate) fn attempt_single_line(&self) -> bool { + matches!(self, Self::Compact(_)) + } +} + +#[derive(Debug, Clone)] +pub(crate) enum AttributeKind<'a> { + Visibility(ast::Visibility), + StateMutability(ast::StateMutability), + Virtual, + Override(&'a ast::Override<'a>), + Modifier(&'a ast::Modifier<'a>), +} + +#[derive(Debug, Clone)] +pub(crate) struct AttributeInfo<'a> { + pub(crate) kind: AttributeKind<'a>, + pub(crate) span: Span, +} + +/// Helper struct to map attributes to their associated comments in function headers. +pub(crate) struct AttributeCommentMapper<'ast> { + limit_pos: BytePos, + comments: Vec, + attributes: Vec>, +} + +impl<'ast> AttributeCommentMapper<'ast> { + pub(crate) fn new(limit_pos: BytePos) -> Self { + Self { limit_pos, comments: Vec::new(), attributes: Vec::new() } + } + + pub(crate) fn build( + mut self, + state: &mut State<'_, 'ast>, + header: &'ast ast::FunctionHeader<'ast>, + ) -> (Vec>, HashMap, Vec)>) { + self.collect_attributes(header); + self.cache_comments(state); + self.map() + } + + fn map(mut self) -> (Vec>, HashMap, Vec)>) { + let mut map = HashMap::new(); + for a in 0..self.attributes.len() { + let is_last = a == self.attributes.len() - 1; + let mut before = Vec::new(); + let mut after = Vec::new(); + + let before_limit = self.attributes[a].span.lo(); + let after_limit = + if !is_last { self.attributes[a + 1].span.lo() } else { self.limit_pos }; + + let mut c = 0; + while c < self.comments.len() { + if self.comments[c].pos() <= before_limit { + before.push(self.comments.remove(c)); + } else if (after.is_empty() || is_last) && self.comments[c].pos() <= after_limit { + after.push(self.comments.remove(c)); + } else { + c += 1; + } + } + map.insert(before_limit, (before, after)); + } + + (self.attributes, map) + } + + fn collect_attributes(&mut self, header: &'ast ast::FunctionHeader<'ast>) { + if let Some(v) = header.visibility { + self.attributes + .push(AttributeInfo { kind: AttributeKind::Visibility(*v), span: v.span }); + } + self.attributes.push(AttributeInfo { + kind: AttributeKind::StateMutability(*header.state_mutability), + span: header.state_mutability.span, + }); + if let Some(v) = header.virtual_ { + self.attributes.push(AttributeInfo { kind: AttributeKind::Virtual, span: v }); + } + if let Some(ref o) = header.override_ { + self.attributes.push(AttributeInfo { kind: AttributeKind::Override(o), span: o.span }); + } + for m in header.modifiers.iter() { + self.attributes + .push(AttributeInfo { kind: AttributeKind::Modifier(m), span: m.span() }); + } + self.attributes.sort_by_key(|attr| attr.span.lo()); + } + + fn cache_comments(&mut self, state: &mut State<'_, 'ast>) { + let mut pending = None; + for cmnt in state.comments.iter() { + if cmnt.pos() >= self.limit_pos { + break; + } + match pending { + Some(ref p) => pending = Some(p + 1), + None => pending = Some(0), + } + } + while let Some(p) = pending { + if p == 0 { + pending = None; + } else { + pending = Some(p - 1); + } + let cmnt = state.next_comment().unwrap(); + if cmnt.style == CommentStyle::BlankLine { + continue; + } + self.comments.push(cmnt); + } + } +} + fn stmt_needs_semi<'ast>(stmt: &'ast ast::StmtKind<'ast>) -> bool { match stmt { ast::StmtKind::Assembly { .. } | @@ -2087,18 +2158,3 @@ fn stmt_needs_semi<'ast>(stmt: &'ast ast::StmtKind<'ast>) -> bool { ast::StmtKind::Placeholder { .. } => true, } } - -#[derive(Debug, Clone)] -enum AttributeKind<'a> { - Visibility(ast::Visibility), - StateMutability(ast::StateMutability), - Virtual, - Override(&'a ast::Override<'a>), - Modifier(&'a ast::Modifier<'a>), -} - -#[derive(Debug, Clone)] -struct AttributeInfo<'a> { - kind: AttributeKind<'a>, - span: Span, -} diff --git a/crates/fmt-2/tests/formatter.rs b/crates/fmt-2/tests/formatter.rs index 6a917eebdbbdf..b5006a8d55bdc 100644 --- a/crates/fmt-2/tests/formatter.rs +++ b/crates/fmt-2/tests/formatter.rs @@ -170,7 +170,7 @@ fmt_tests! { FunctionCall, // TODO: fix PP so that comments aren't accounted for when breaking lines FunctionCallArgsStatement, FunctionDefinition, // TODO: fix fn block braces - FunctionDefinitionWithFunctionReturns, // TODO: impl return fn + FunctionDefinitionWithFunctionReturns, FunctionType, HexUnderscore, IfStatement, From 66f9c6cbe6cc4828bac13415e87d7c3919e23d42 Mon Sep 17 00:00:00 2001 From: 0xrusowsky <0xrusowsky@proton.me> Date: Wed, 2 Jul 2025 21:39:32 +0200 Subject: [PATCH 17/31] fix commasep with initial trailing cmnt --- crates/fmt-2/src/comment.rs | 15 ++ crates/fmt-2/src/pp/helpers.rs | 1 + crates/fmt-2/src/state.rs | 314 +++++++++++++++++++++----------- crates/fmt-2/tests/formatter.rs | 104 +++++------ 4 files changed, 277 insertions(+), 157 deletions(-) diff --git a/crates/fmt-2/src/comment.rs b/crates/fmt-2/src/comment.rs index 96e79c8cfaf60..77268e653da37 100644 --- a/crates/fmt-2/src/comment.rs +++ b/crates/fmt-2/src/comment.rs @@ -17,6 +17,21 @@ pub enum CommentStyle { BlankLine, } +impl CommentStyle { + pub fn is_mixed(&self) -> bool { + matches!(self, Self::Mixed) + } + pub fn is_trailing(&self) -> bool { + matches!(self, Self::Trailing) + } + pub fn is_isolated(&self) -> bool { + matches!(self, Self::Isolated) + } + pub fn is_blank(&self) -> bool { + matches!(self, Self::BlankLine) + } +} + #[derive(Clone, Debug)] pub struct Comment { pub lines: Vec, diff --git a/crates/fmt-2/src/pp/helpers.rs b/crates/fmt-2/src/pp/helpers.rs index 983d22f097c85..fbda028ab9f69 100644 --- a/crates/fmt-2/src/pp/helpers.rs +++ b/crates/fmt-2/src/pp/helpers.rs @@ -12,6 +12,7 @@ impl Printer { pub fn hardbreak_if_not_bol(&mut self) { if !self.is_bol_or_only_ind() { if let Some(Token::Break(last)) = self.last_token_still_buffered() { + println!("> last: {last:?}"); if last.offset != 0 { self.replace_last_token_still_buffered(Self::hardbreak_tok_offset(last.offset)); return; diff --git a/crates/fmt-2/src/state.rs b/crates/fmt-2/src/state.rs index 44205c9315a24..e22dbb4e2d036 100644 --- a/crates/fmt-2/src/state.rs +++ b/crates/fmt-2/src/state.rs @@ -68,14 +68,23 @@ impl<'sess> State<'sess, '_> { /// Returns `Some` with the style of the last comment printed, or `None` if no comment was /// printed. fn print_comments(&mut self, pos: BytePos) -> Option { - self.print_comments_inner(pos, false) + self.print_comments_inner(pos, false, false) } fn print_comments_skip_ws(&mut self, pos: BytePos) -> Option { - self.print_comments_inner(pos, true) + self.print_comments_inner(pos, true, false) } - fn print_comments_inner(&mut self, pos: BytePos, skip_ws: bool) -> Option { + fn print_cmnts_with_space_skip_ws(&mut self, pos: BytePos) -> Option { + self.print_comments_inner(pos, true, true) + } + + fn print_comments_inner( + &mut self, + pos: BytePos, + skip_ws: bool, + mixed_with_space: bool, + ) -> Option { let mut last_style = None; while let Some(cmnt) = self.peek_comment() { if cmnt.pos() >= pos { @@ -86,17 +95,23 @@ impl<'sess> State<'sess, '_> { continue; } last_style = Some(cmnt.style); - self.print_comment(cmnt); + self.print_comment(cmnt, mixed_with_space); } last_style } - fn print_comment(&mut self, mut cmnt: Comment) { + fn print_comment(&mut self, mut cmnt: Comment, with_space: bool) { + // println!("{cmnt:?}"); + // println!(" > bol? {}\n", self.is_bol_or_only_ind()); match cmnt.style { CommentStyle::Mixed => { let never_break = self.last_token_is_neverbreak(); if !self.is_bol_or_only_ind() && !never_break { - self.zerobreak(); + if with_space { + self.space() + } else { + self.zerobreak(); + } } if let Some(last) = cmnt.lines.pop() { self.ibox(0); @@ -127,7 +142,8 @@ impl<'sess> State<'sess, '_> { } } CommentStyle::Trailing => { - if !self.is_bol_or_only_ind() && !self.last_token_is_space() { + // if !self.is_bol_or_only_ind() && !self.last_token_is_space() { + if !self.is_bol_or_only_ind() { self.nbsp(); } if cmnt.lines.len() == 1 { @@ -180,7 +196,7 @@ impl<'sess> State<'sess, '_> { fn print_trailing_comment(&mut self, span_pos: BytePos, next_pos: Option) -> bool { if let Some(cmnt) = self.comments.trailing_comment(self.sm, span_pos, next_pos) { - self.print_comment(cmnt); + self.print_comment(cmnt, false); return true; } @@ -194,7 +210,7 @@ impl<'sess> State<'sess, '_> { self.hardbreak(); } while let Some(cmnt) = self.next_comment() { - self.print_comment(cmnt); + self.print_comment(cmnt, false); } } @@ -224,7 +240,8 @@ impl<'sess> State<'sess, '_> { fn print_tuple<'a, T, P, S>( &mut self, values: &'a [T], - span: Span, + pos_lo: BytePos, + pos_hi: BytePos, mut print: P, mut get_span: S, format: ListFormat, @@ -245,7 +262,7 @@ impl<'sess> State<'sess, '_> { // Otherwise, use commasep self.word("("); - self.commasep(values, span, print, get_span, matches!(format, ListFormat::Compact)); + self.commasep(values, pos_lo, pos_hi, print, get_span, format); self.word(")"); } @@ -255,17 +272,18 @@ impl<'sess> State<'sess, '_> { S: FnMut(&T) -> Option, { self.word("["); - self.commasep(values, span, print, get_span, false); + self.commasep(values, span.lo(), span.hi(), print, get_span, ListFormat::Consistent(false)); self.word("]"); } fn commasep<'a, T, P, S>( &mut self, values: &'a [T], - bracket_span: Span, + pos_lo: BytePos, + pos_hi: BytePos, mut print: P, mut get_span: S, - compact: bool, + format: ListFormat, ) where P: FnMut(&mut Self, &'a T), S: FnMut(&T) -> Option, @@ -275,45 +293,63 @@ impl<'sess> State<'sess, '_> { } self.s.cbox(self.ind); - let skip_break = { - if let Some(first_pos) = get_span(&values[0]).map(Span::lo) { - if let Some(CommentStyle::Trailing) = - self.peek_comment_before(first_pos).map(|cmnt| cmnt.style) - { - self.nbsp(); - self.print_trailing_comment(bracket_span.lo(), Some(first_pos)) - } else { - false - } - } else { - false + let mut skip_break = false; + if let Some(first_pos) = get_span(&values[0]).map(Span::lo) { + if format.breaks_comments() && self.peek_comment_before(first_pos).is_some() { + // If cmnts should break + comment before the 1st item, force hardbreak. + self.hardbreak(); } - }; + skip_break = self.print_trailing_comment(pos_lo, Some(first_pos)); + if !skip_break && format.breaks_comments() { + skip_break = self.peek_comment_before(first_pos).is_some(); + } + } if !skip_break { self.zerobreak(); } - if compact { + if format.is_compact() { self.s.cbox(0); } let mut skip_break = false; for (i, value) in values.iter().enumerate() { let is_last = i == values.len() - 1; - self.s.ibox(0); let span = get_span(value); - if let Some(span) = span { - self.print_comments_skip_ws(span.lo()); + + if !format.breaks_comments() { + // If comments can be inlined: box() + self.s.ibox(0); + if let Some(span) = span { + self.print_comments_skip_ws(span.lo()); + } + } else { + // Otherwise: box() + if let Some(span) = span { + if self.print_comments_skip_ws(span.lo()).map_or(false, |cmnt| cmnt.is_mixed()) + { + self.hardbreak(); // trailing and isolated comments already hardbreak + } + } + self.s.ibox(0); } + print(self, value); if !is_last { self.word(","); } let next_pos = if is_last { None } else { get_span(&values[i + 1]).map(Span::lo) } - .unwrap_or_else(|| bracket_span.hi()); - if self.peek_comment_before(next_pos).is_some() { - self.space(); - self.print_comments_skip_ws(next_pos); + .unwrap_or(pos_hi); + if format.breaks_comments() { + if self.peek_comment_before(next_pos).map_or(false, |cmnt| cmnt.style.is_mixed()) { + self.hardbreak(); // trailing and isolated comments already hardbreak + } + } else if !self + .peek_comment_before(next_pos) + .map_or(true, |cmnt| cmnt.style.is_trailing()) + { + self.space(); // trailing already adds a non-breaking space } + self.print_comments_skip_ws(next_pos); if self.is_bol_or_only_ind() { if is_last { // if a trailing comment is printed at the very end, we have to manually adjust @@ -328,7 +364,7 @@ impl<'sess> State<'sess, '_> { } } - if compact { + if format.is_compact() { self.end(); } if !skip_break { @@ -417,7 +453,7 @@ impl<'ast> State<'_, 'ast> { ast::ItemKind::Event(event) => self.print_event(event), } self.print_comments(span.hi()); - self.print_trailing_comment(span.hi(), None); + // self.print_trailing_comment(span.hi(), None); self.hardbreak_if_not_bol(); } @@ -604,6 +640,9 @@ impl<'ast> State<'_, 'ast> { } self.print_trailing_comment(ident.span.hi(), None); self.hardbreak_if_not_bol(); + if !pos.is_last { + self.hardbreak(); + } } self.print_comments_skip_ws(span.hi()); self.s.offset(-self.ind); @@ -690,7 +729,7 @@ impl<'ast> State<'_, 'ast> { self.space(); } self.word("returns "); - self.print_parameter_list(returns, returns.span, ListFormat::Consistent); + self.print_parameter_list(returns, returns.span, ListFormat::Consistent(false)); } self.end(); @@ -700,7 +739,7 @@ impl<'ast> State<'_, 'ast> { let new_line = current_indent > fn_start_indent; if !new_line { self.nbsp(); - self.word("{"); + // self.word("{"); self.end(); if !body.is_empty() { self.hardbreak(); @@ -708,12 +747,13 @@ impl<'ast> State<'_, 'ast> { } else { self.space(); self.s.offset(-self.ind); - self.word("{"); + // self.word("{"); self.end(); } self.end(); - self.print_block_without_braces(body, body_span, new_line); - self.word("}"); + // self.print_block_without_braces(body, body_span, new_line); + self.print_block(body, body_span); + // self.word("}"); } else { self.end(); self.neverbreak(); @@ -749,7 +789,7 @@ impl<'ast> State<'_, 'ast> { let ast::ItemError { name, parameters } = err; self.word("error "); self.print_ident(name); - self.print_parameter_list(parameters, parameters.span, ListFormat::Consistent); + self.print_parameter_list(parameters, parameters.span, ListFormat::Consistent(false)); self.word(";"); } @@ -757,7 +797,7 @@ impl<'ast> State<'_, 'ast> { let ast::ItemEvent { name, parameters, anonymous } = event; self.word("event "); self.print_ident(name); - self.print_parameter_list(parameters, parameters.span, ListFormat::Compact); + self.print_parameter_list(parameters, parameters.span, ListFormat::Compact(false)); if *anonymous { self.word(" anonymous"); } @@ -786,6 +826,7 @@ impl<'ast> State<'_, 'ast> { return; } + self.cbox(0); self.print_ty(ty); if let Some(visibility) = visibility { self.nbsp(); @@ -796,6 +837,7 @@ impl<'ast> State<'_, 'ast> { self.word(mutability.to_str()); } if let Some(data_location) = data_location { + // TODO: make `Spanned` and print comments up to the span self.nbsp(); self.word(data_location.to_str()); } @@ -816,6 +858,7 @@ impl<'ast> State<'_, 'ast> { self.neverbreak(); self.print_expr(initializer); } + self.end(); } fn print_parameter_list( @@ -824,7 +867,7 @@ impl<'ast> State<'_, 'ast> { span: Span, format: ListFormat, ) { - self.print_tuple(parameters, span, Self::print_var, get_span!(), format); + self.print_tuple(parameters, span.lo(), span.hi(), Self::print_var, get_span!(), format); } // NOTE(rusowsky): is this needed? @@ -852,12 +895,15 @@ impl<'ast> State<'_, 'ast> { } fn print_path(&mut self, path: &'ast ast::PathSlice) { + self.s.cbox(self.ind); for (pos, ident) in path.segments().iter().delimited() { self.print_ident(ident); if !pos.is_last { + self.zerobreak(); self.word("."); } } + self.end(); } // TODO: Yul literals are slightly different than normal solidity ones @@ -1096,7 +1142,7 @@ impl<'ast> State<'_, 'ast> { if !returns.is_empty() { self.word("returns"); self.nbsp(); - self.print_parameter_list(returns, returns.span, ListFormat::Consistent); + self.print_parameter_list(returns, returns.span, ListFormat::Consistent(false)); } self.end(); } @@ -1131,10 +1177,11 @@ impl<'ast> State<'_, 'ast> { } self.print_tuple( paths, - *span, + span.lo(), + span.hi(), |this, path| this.print_path(path), get_span!(()), - ListFormat::Consistent, + ListFormat::Consistent(false), ); } } @@ -1191,39 +1238,38 @@ impl<'ast> State<'_, 'ast> { self.print_expr(expr); self.word("["); self.s.cbox(self.ind); - self.zerobreak(); let mut skip_break = false; match kind { ast::IndexKind::Index(expr) => { if let Some(expr) = expr { + self.zerobreak(); self.print_expr(expr); } } ast::IndexKind::Range(expr0, expr1) => { if let Some(expr0) = expr0 { - self.print_comments(expr0.span.lo()); + if self.print_comments(expr0.span.lo()).map_or(true, |s| s.is_mixed()) { + self.zerobreak(); + } self.print_expr(expr0); + } else { + self.zerobreak(); } self.word(":"); if let Some(expr1) = expr1 { self.s.ibox(self.ind); - if self.peek_comment_before(expr1.span.lo()).is_some() { - self.space(); - } else if expr0.is_some() { + if expr0.is_some() { self.zerobreak(); } - self.print_comments(expr1.span.lo()); + self.print_cmnts_with_space_skip_ws(expr1.span.lo()); self.print_expr(expr1); } - let mut style = None; - if let Some(cmnt) = self.peek_comment_before(span.hi()) { - if matches!(cmnt.style, CommentStyle::Mixed) { - self.space(); - } - style = self.print_comments(span.hi()); + let mut is_trailing = false; + if let Some(style) = self.print_cmnts_with_space_skip_ws(span.hi()) { skip_break = true; + is_trailing = style.is_trailing(); } // Manually revert indentation if there is `expr1` and/or comments. @@ -1232,7 +1278,7 @@ impl<'ast> State<'_, 'ast> { self.end(); // if a trailing comment is printed at the very end, we have to manually // adjust the offset to avoid having a double break. - if !matches!(style.unwrap(), CommentStyle::Trailing) { + if !is_trailing { self.break_offset_if_not_bol(0, -self.ind); } } else if skip_break { @@ -1258,6 +1304,11 @@ impl<'ast> State<'_, 'ast> { } ast::ExprKind::Member(expr, ident) => { self.print_expr(expr); + if self.print_trailing_comment(expr.span.hi(), Some(ident.span.lo())) { + // if a trailing comment is printed at the very end, we have to manually adjust + // the offset to avoid having a double break. + self.break_offset_if_not_bol(0, self.ind); + } self.word("."); self.print_ident(ident); } @@ -1308,23 +1359,25 @@ impl<'ast> State<'_, 'ast> { } ast::ExprKind::Tuple(exprs) => self.print_tuple( exprs, - span, + span.lo(), + span.hi(), |this, expr| { if let Some(expr) = expr { this.print_expr(expr); } }, |e| e.as_deref().map(|e| e.span), - ListFormat::Consistent, + ListFormat::Consistent(false), ), ast::ExprKind::TypeCall(ty) => { self.word("type"); self.print_tuple( std::slice::from_ref(ty), - span, + span.lo(), + span.hi(), Self::print_ty, get_span!(), - ListFormat::Consistent, + ListFormat::Consistent(false), ); } ast::ExprKind::Type(ty) => self.print_ty(ty), @@ -1366,10 +1419,11 @@ impl<'ast> State<'_, 'ast> { ast::CallArgsKind::Unnamed(exprs) => { self.print_tuple( exprs, - span, + span.lo(), + span.hi(), |this, e| this.print_expr(e), get_span!(), - ListFormat::Compact, + ListFormat::Compact(true), ); } ast::CallArgsKind::Named(named_args) => { @@ -1432,10 +1486,11 @@ impl<'ast> State<'_, 'ast> { if !flags.is_empty() { self.print_tuple( flags, - span, + span.lo(), + span.hi(), Self::print_ast_str_lit, get_span!(), - ListFormat::Consistent, + ListFormat::Consistent(false), ); } self.print_yul_block(block, span, false); @@ -1444,14 +1499,15 @@ impl<'ast> State<'_, 'ast> { ast::StmtKind::DeclMulti(vars, expr) => { self.print_tuple( vars, - span, + span.lo(), + span.hi(), |this, var| { if let Some(var) = var { this.print_var(var); } }, |v| v.as_ref().map(|v| v.span), - ListFormat::Consistent, + ListFormat::Consistent(false), ); self.word(" = "); self.neverbreak(); @@ -1462,9 +1518,9 @@ impl<'ast> State<'_, 'ast> { ast::StmtKind::Continue => self.word("continue"), ast::StmtKind::DoWhile(stmt, cond) => { self.word("do "); - self.print_stmt_as_block(stmt, false); + self.print_stmt_as_block(stmt, BlockFormat::Regular); self.nbsp(); - self.print_if_cond("while", cond); + self.print_if_cond("while", cond, cond.span.hi()); } ast::StmtKind::Emit(path, args) => self.print_emit_revert("emit", path, args), ast::StmtKind::Expr(expr) => self.print_expr(expr), @@ -1497,7 +1553,7 @@ impl<'ast> State<'_, 'ast> { self.word(") "); self.neverbreak(); self.end(); - self.print_stmt_as_block(body, false); + self.print_stmt_as_block(body, BlockFormat::Regular); self.end(); } ast::StmtKind::If(cond, then, els_opt) => { @@ -1519,7 +1575,7 @@ impl<'ast> State<'_, 'ast> { els_opt = els.as_deref(); continue; } else { - self.print_stmt_as_block(els, true); + self.print_stmt_as_block(els, BlockFormat::Compact(true)); } break; } @@ -1550,7 +1606,7 @@ impl<'ast> State<'_, 'ast> { } if !args.is_empty() { self.word("returns "); - self.print_parameter_list(args, *try_span, ListFormat::Compact); + self.print_parameter_list(args, *try_span, ListFormat::Compact(false)); self.nbsp(); } self.print_block(block, *try_span); @@ -1574,9 +1630,11 @@ impl<'ast> State<'_, 'ast> { if !pos.is_first || !skip_ind { self.handle_try_catch_indent(&mut should_break, block.is_empty(), pos); } - self.ibox(0); + self.s.cbox(self.ind); + self.neverbreak(); self.print_comments(catch_span.lo()); self.word("catch "); + self.neverbreak(); if !args.is_empty() { self.print_comments(args[0].span.lo()); if let Some(name) = name { @@ -1585,8 +1643,10 @@ impl<'ast> State<'_, 'ast> { self.print_parameter_list(args, *catch_span, ListFormat::Inline); self.nbsp(); } + self.s.cbox(-self.ind); self.print_block(block, *catch_span); self.end(); + self.end(); } } self.end(); @@ -1597,10 +1657,12 @@ impl<'ast> State<'_, 'ast> { } ast::StmtKind::While(cond, stmt) => { self.ibox(0); - self.print_if_cond("while", cond); + self.print_if_cond("while", cond, stmt.span.lo()); self.nbsp(); self.end(); - self.print_stmt_as_block(stmt, true); + // TODO: handle braces manually + // self.print_stmt_as_block(stmt, BlockFormat::NoBraces(false)); + self.print_stmt_as_block(stmt, BlockFormat::Compact(true)); } ast::StmtKind::Placeholder => self.word("_"), } @@ -1612,20 +1674,21 @@ impl<'ast> State<'_, 'ast> { } fn print_if_no_else(&mut self, cond: &'ast ast::Expr<'ast>, then: &'ast ast::Stmt<'ast>) { - self.print_if_cond("if", cond); + self.print_if_cond("if", cond, then.span.lo()); self.nbsp(); self.end(); - self.print_stmt_as_block(then, true); + self.print_stmt_as_block(then, BlockFormat::Compact(true)); } - fn print_if_cond(&mut self, kw: &'static str, cond: &'ast ast::Expr<'ast>) { + fn print_if_cond(&mut self, kw: &'static str, cond: &'ast ast::Expr<'ast>, pos_hi: BytePos) { self.word_nbsp(kw); self.print_tuple( std::slice::from_ref(cond), - cond.span, + cond.span.lo(), + pos_hi, Self::print_expr, get_span!(), - ListFormat::Compact, + ListFormat::Compact(true), ); } @@ -1660,19 +1723,33 @@ impl<'ast> State<'_, 'ast> { } // Body of a if/loop. - fn print_stmt_as_block(&mut self, stmt: &'ast ast::Stmt<'ast>, attempt_single_line: bool) { + fn print_stmt_as_block(&mut self, stmt: &'ast ast::Stmt<'ast>, format: BlockFormat) { let stmts = if let ast::StmtKind::Block(stmts) = &stmt.kind { stmts } else { std::slice::from_ref(stmt) }; + // TODO: figure out: + // > why stmts are not indented + // > why braces are not always printed? + if matches!(format, BlockFormat::NoBraces(_)) { + self.word("{"); + self.zerobreak(); + self.s.cbox(self.ind); + } self.print_block_inner( stmts, - if attempt_single_line { BlockFormat::Compact(true) } else { BlockFormat::Regular }, + format, + // if attempt_single_line { BlockFormat::Compact(true) } else { BlockFormat::Regular }, Self::print_stmt, |b| b.span, stmt.span, - ) + ); + if matches!(format, BlockFormat::NoBraces(_)) { + self.s.offset(-self.ind); + self.end(); + self.word("}"); + } } fn print_yul_block( @@ -1701,18 +1778,13 @@ impl<'ast> State<'_, 'ast> { // Braces are explicitly handled by the caller if let BlockFormat::NoBraces(new_line) = block_format { if new_line { - self.s.cbox(self.ind); self.hardbreak_if_nonempty(); - } else { - self.s.cbox(self.ind); } for stmt in block { print(self, stmt); self.hardbreak_if_not_bol(); } self.print_comments_skip_ws(block.last().map_or(span.hi(), |b| get_block_span(b).hi())); - self.s.offset(-self.ind); - self.end(); } // Attempt to print in a single line else if block_format.attempt_single_line() && @@ -1741,7 +1813,7 @@ impl<'ast> State<'_, 'ast> { // Empty blocks with comments require special attention else if block.is_empty() { if let Some(comment) = self.peek_comment() { - if !matches!(comment.style, CommentStyle::Mixed) { + if !comment.style.is_mixed() { self.word("{}"); self.print_comments_skip_ws(span.hi()); } else { @@ -1802,10 +1874,11 @@ impl<'ast> State<'_, 'ast> { yul::StmtKind::AssignMulti(paths, expr_call) => { self.commasep( paths, - stmt.span, + stmt.span.lo(), + stmt.span.hi(), |this, path| this.print_path(path), get_span!(()), - false, + ListFormat::Consistent(false), ); self.word(" := "); self.neverbreak(); @@ -1868,15 +1941,23 @@ impl<'ast> State<'_, 'ast> { self.print_ident(name); self.print_tuple( parameters, - span, + span.lo(), + span.hi(), Self::print_ident, get_span!(), - ListFormat::Consistent, + ListFormat::Consistent(false), ); self.nbsp(); if !returns.is_empty() { self.word("-> "); - self.commasep(returns, stmt.span, Self::print_ident, get_span!(), false); + self.commasep( + returns, + stmt.span.lo(), + stmt.span.hi(), + Self::print_ident, + get_span!(), + ListFormat::Consistent(false), + ); self.nbsp(); } self.end(); @@ -1886,7 +1967,14 @@ impl<'ast> State<'_, 'ast> { yul::StmtKind::VarDecl(idents, expr) => { self.ibox(0); self.word("let "); - self.commasep(idents, stmt.span, Self::print_ident, get_span!(), false); + self.commasep( + idents, + stmt.span.lo(), + stmt.span.hi(), + Self::print_ident, + get_span!(), + ListFormat::Consistent(false), + ); if let Some(expr) = expr { self.word(" := "); self.neverbreak(); @@ -1920,10 +2008,11 @@ impl<'ast> State<'_, 'ast> { self.print_ident(name); self.print_tuple( arguments, - Span::DUMMY, + Span::DUMMY.lo(), + Span::DUMMY.hi(), Self::print_yul_expr, get_span!(), - ListFormat::Consistent, + ListFormat::Consistent(false), ); } @@ -1968,7 +2057,7 @@ impl<'ast> State<'_, 'ast> { match map.remove(&span.lo()) { Some((pre_comments, post_comments)) => { for cmnt in pre_comments { - self.print_comment(cmnt); + self.print_comment(cmnt, false); } if !self.is_bol_or_only_ind() && !self.last_token_is_space() { self.space(); @@ -1977,7 +2066,7 @@ impl<'ast> State<'_, 'ast> { print_fn(self); self.end(); for cmnt in post_comments { - self.print_comment(cmnt); + self.print_comment(cmnt, false); } } // Fallback for attributes with no comments @@ -1998,14 +2087,29 @@ impl<'ast> State<'_, 'ast> { #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub(crate) enum ListFormat { /// Breaks all elements if any break. - Consistent, + Consistent(bool), /// Attempts to fit all elements in one line, before breaking consistently. - Compact, + /// The boolean indicates whether mixed comments should force a break. + Compact(bool), /// If the list contains just one element, it will print unboxed (will not break). /// Otherwise, will break consistently. Inline, } +impl ListFormat { + pub(crate) fn breaks_comments(&self) -> bool { + match self { + Self::Consistent(yes) => *yes, + Self::Compact(yes) => *yes, + Self::Inline => false, + } + } + + pub(crate) fn is_compact(&self) -> bool { + matches!(self, Self::Compact(_)) + } +} + /// Formatting style for code blocks #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub(crate) enum BlockFormat { diff --git a/crates/fmt-2/tests/formatter.rs b/crates/fmt-2/tests/formatter.rs index b5006a8d55bdc..1c6ae79a9f0f6 100644 --- a/crates/fmt-2/tests/formatter.rs +++ b/crates/fmt-2/tests/formatter.rs @@ -152,56 +152,56 @@ macro_rules! fmt_tests { fmt_tests! { #[ignore = "annotations are not valid Solidity"] Annotation, - ArrayExpressions, // TODO - BlockComments, - BlockCommentsFunction, - ConditionalOperatorExpression, - ConstructorDefinition, - ConstructorModifierStyle, - ContractDefinition, // TODO - DocComments, // TODO: wrap comments - DoWhileStatement, - EmitStatement, - EnumDefinition, - EnumVariants, - ErrorDefinition, - EventDefinition, - ForStatement, - FunctionCall, // TODO: fix PP so that comments aren't accounted for when breaking lines - FunctionCallArgsStatement, - FunctionDefinition, // TODO: fix fn block braces - FunctionDefinitionWithFunctionReturns, - FunctionType, - HexUnderscore, - IfStatement, - IfStatement2, - ImportDirective, - InlineDisable, - IntTypes, - LiteralExpression, - MappingType, - ModifierDefinition, - NamedFunctionCallExpression, - NumberLiteralUnderscore, - OperatorExpressions, - PragmaDirective, - Repros, - ReturnStatement, - RevertNamedArgsStatement, - RevertStatement, - SimpleComments, - SortedImports, - StatementBlock, - StructDefinition, - ThisExpression, - TrailingComma, - TryStatement, - TypeDefinition, - UnitExpression, - UsingDirective, - VariableAssignment, - VariableDefinition, - WhileStatement, - Yul, - YulStrings, + ArrayExpressions, // TODO: print cmnt before memory kw once span is available (solar). Is the rest acceptable? + BlockComments, // FIX: empty line after doc cmnt and before constructor + BlockCommentsFunction, // OK + ConditionalOperatorExpression, // FIX: fn block braces + ConstructorDefinition, // OK + ConstructorModifierStyle, // OK + ContractDefinition, // TODO: check boxes, panics + DocComments, // FIX: idempotency (comment-related) + DoWhileStatement, // OK? (once block braces are fixed).. is it acceptable? + EmitStatement, // OK + EnumDefinition, // OK + EnumVariants, // OK + ErrorDefinition, // OK + EventDefinition, // OK? i think it works as intended.. is it acceptable? + ForStatement, // OK? (once block braces are fixed).. is it acceptable? + FunctionCall, // TODO: ask Dani how to enhance PP so that trailing comments aren't accounted for when breaking lines + FunctionCallArgsStatement, // FIX: fn block braces. Is the rest acceptable? + FunctionDefinition, // TODO: fix fn block braces + function postfix trailing cmnt + FunctionDefinitionWithFunctionReturns, // OK? is it acceptable? + FunctionType, // OK? is it acceptable? + HexUnderscore, // OK (once block braces are fixed) + IfStatement, // TODO: figure out correct comment placements + IfStatement2, // OK (once block braces are fixed) + ImportDirective, // OK + InlineDisable, // FIX: invalid output + IntTypes, // OK (once block braces are fixed) + LiteralExpression, // FIX: idempotency (comment-related) + MappingTyple, // TODO: comments + ModifierDefinition, // OK + NamedFunctionCallExpression, // FIX: idempotency (comment-related) + NumberLiteralUnderscore, // OK (once block braces are fixed) + OperatorExpressions, // FIX: idempotency (comment-related) + PragmaDirective, // OK + Repros, // TODO: check boxes, panics + ReturnStatement, // FIX: idempotency (comment-related) + RevertNamedArgsStatement, // TODO: comments + RevertStatement, // FIX: idempotency (comment-related) + SimpleComments, // FIX: idempotency (comment-related) + SortedImports, // FIX: sorting order + StatementBlock, // OK (once block braces are fixed) + StructDefinition, // OK + ThisExpression, // OK + TrailingComma, // OK (solar error) + TryStatement, // OK but kinda hacky. Is the fmt for the new test case acceptable? + TypeDefinition, // OK + UnitExpression, // FIX: idempotency (comment-related) + UsingDirective, // OK + VariableAssignment, // FIX: variable assignment + VariableDefinition, // FIX: variable assignment + declaration + WhileStatement, // FIX: indentation of the condition + Yul, // FIX: idemptency (comment-related) + YulStrings, // OK (once block braces are fixed) } From 7ba3c5febfec5ff3fd4e15a6f06cea9fc20f2514 Mon Sep 17 00:00:00 2001 From: 0xrusowsky <0xrusowsky@proton.me> Date: Thu, 3 Jul 2025 09:54:39 +0200 Subject: [PATCH 18/31] fix: improve contract fmt --- crates/fmt-2/src/state.rs | 146 +++++++++++++++++--------------- crates/fmt-2/tests/formatter.rs | 4 +- 2 files changed, 80 insertions(+), 70 deletions(-) diff --git a/crates/fmt-2/src/state.rs b/crates/fmt-2/src/state.rs index e22dbb4e2d036..e0960da12c84f 100644 --- a/crates/fmt-2/src/state.rs +++ b/crates/fmt-2/src/state.rs @@ -101,12 +101,10 @@ impl<'sess> State<'sess, '_> { } fn print_comment(&mut self, mut cmnt: Comment, with_space: bool) { - // println!("{cmnt:?}"); - // println!(" > bol? {}\n", self.is_bol_or_only_ind()); match cmnt.style { CommentStyle::Mixed => { let never_break = self.last_token_is_neverbreak(); - if !self.is_bol_or_only_ind() && !never_break { + if !never_break && !self.is_bol_or_only_ind() { if with_space { self.space() } else { @@ -293,45 +291,40 @@ impl<'sess> State<'sess, '_> { } self.s.cbox(self.ind); - let mut skip_break = false; + let mut skip_first_break = false; if let Some(first_pos) = get_span(&values[0]).map(Span::lo) { if format.breaks_comments() && self.peek_comment_before(first_pos).is_some() { // If cmnts should break + comment before the 1st item, force hardbreak. self.hardbreak(); } - skip_break = self.print_trailing_comment(pos_lo, Some(first_pos)); - if !skip_break && format.breaks_comments() { - skip_break = self.peek_comment_before(first_pos).is_some(); - } + skip_first_break = self.print_trailing_comment(pos_lo, Some(first_pos)); + skip_first_break = self + .peek_comment_before(first_pos) + .map_or(skip_first_break, |_| format.breaks_comments()); } - if !skip_break { + if !skip_first_break { self.zerobreak(); } if format.is_compact() { self.s.cbox(0); } - let mut skip_break = false; + let mut skip_last_break = false; for (i, value) in values.iter().enumerate() { let is_last = i == values.len() - 1; let span = get_span(value); - if !format.breaks_comments() { - // If comments can be inlined: box() - self.s.ibox(0); - if let Some(span) = span { - self.print_comments_skip_ws(span.lo()); - } - } else { - // Otherwise: box() - if let Some(span) = span { - if self.print_comments_skip_ws(span.lo()).map_or(false, |cmnt| cmnt.is_mixed()) - { - self.hardbreak(); // trailing and isolated comments already hardbreak - } + // box() + if let Some(span) = span { + if self + .print_cmnts_with_space_skip_ws(span.lo()) + .map_or(false, |cmnt| cmnt.is_mixed()) && + format.breaks_comments() + { + self.hardbreak(); // trailing and isolated comments already hardbreak } - self.s.ibox(0); } + self.s.ibox(0); print(self, value); if !is_last { @@ -339,25 +332,19 @@ impl<'sess> State<'sess, '_> { } let next_pos = if is_last { None } else { get_span(&values[i + 1]).map(Span::lo) } .unwrap_or(pos_hi); - if format.breaks_comments() { - if self.peek_comment_before(next_pos).map_or(false, |cmnt| cmnt.style.is_mixed()) { - self.hardbreak(); // trailing and isolated comments already hardbreak - } - } else if !self - .peek_comment_before(next_pos) - .map_or(true, |cmnt| cmnt.style.is_trailing()) + if !is_last && + format.breaks_comments() && + self.peek_comment_before(next_pos).map_or(false, |cmnt| cmnt.style.is_mixed()) { - self.space(); // trailing already adds a non-breaking space + self.hardbreak(); // trailing and isolated comments already hardbreak + } + self.print_cmnts_with_space_skip_ws(next_pos); + if is_last && self.is_bol_or_only_ind() { + // if a trailing comment is printed at the very end, we have to manually adjust + // the offset to avoid having a double break. + self.break_offset_if_not_bol(0, -self.ind); + skip_last_break = true; } - self.print_comments_skip_ws(next_pos); - if self.is_bol_or_only_ind() { - if is_last { - // if a trailing comment is printed at the very end, we have to manually adjust - // the offset to avoid having a double break. - self.break_offset_if_not_bol(0, -self.ind); - skip_break = true; - } - }; self.end(); if !is_last && !self.is_bol_or_only_ind() { self.space(); @@ -367,7 +354,7 @@ impl<'sess> State<'sess, '_> { if format.is_compact() { self.end(); } - if !skip_break { + if !skip_last_break { self.zerobreak(); self.s.offset(-self.ind); } @@ -597,11 +584,14 @@ impl<'ast> State<'_, 'ast> { if self.config.contract_new_lines && comment != Some(CommentStyle::BlankLine) { self.hardbreak(); } + self.s.offset(-self.ind); } else { - self.print_comments(span.hi()); - self.zerobreak(); + if self.print_comments(span.hi()).is_some() { + self.zerobreak(); + } else if self.config.bracket_spacing { + self.nbsp(); + }; } - self.s.offset(-self.ind); self.end(); self.word("}"); @@ -665,7 +655,7 @@ impl<'ast> State<'_, 'ast> { name, ref parameters, visibility, - state_mutability, + state_mutability: sm, virtual_, ref override_, ref returns, @@ -683,45 +673,56 @@ impl<'ast> State<'_, 'ast> { self.print_ident(&name); } self.s.ibox(-self.ind); - self.print_parameter_list(parameters, parameters.span, ListFormat::Inline); + self.print_parameter_list(parameters, parameters.span, ListFormat::Compact(true)); self.end(); // Map attributes to their corresponding comments let (attributes, mut map) = AttributeCommentMapper::new(header.returns.span.lo()).build(self, header); + if !(attributes.len() == 1 && attributes[0].is_non_payable()) { + self.space(); + } + self.s.cbox(0); self.s.cbox(0); // Print fn attributes in correct order + let mut is_first = true; if let Some(v) = visibility { - self.print_fn_attribute(v.span, &mut map, &mut |s| s.word(v.to_str())); + self.print_fn_attribute(v.span, &mut map, &mut |s| s.word(v.to_str()), is_first); + is_first = false; } - if *state_mutability != ast::StateMutability::NonPayable { + if *sm != ast::StateMutability::NonPayable { if !self.is_bol_or_only_ind() && !self.last_token_is_space() { self.space(); } - self.print_fn_attribute(state_mutability.span, &mut map, &mut |s| { - s.word(state_mutability.to_str()) - }); + self.print_fn_attribute(sm.span, &mut map, &mut |s| s.word(sm.to_str()), is_first); + is_first = false; } - if let Some(virtual_) = virtual_ { + if let Some(v) = virtual_ { if !self.is_bol_or_only_ind() && !self.last_token_is_space() { self.space(); } - self.print_fn_attribute(virtual_, &mut map, &mut |s| s.word("virtual")); + self.print_fn_attribute(v, &mut map, &mut |s| s.word("virtual"), is_first); + is_first = false; } - if let Some(override_) = override_ { + if let Some(o) = override_ { if !self.is_bol_or_only_ind() && !self.last_token_is_space() { self.space(); } - self.print_fn_attribute(override_.span, &mut map, &mut |s| s.print_override(override_)); + self.print_fn_attribute(o.span, &mut map, &mut |s| s.print_override(o), is_first); + is_first = false; } for m in attributes.iter().filter(|a| matches!(a.kind, AttributeKind::Modifier(_))) { if let AttributeKind::Modifier(modifier) = m.kind { let is_base = self.is_modifier_a_base_contract(kind, modifier); - self.print_fn_attribute(m.span, &mut map, &mut |s| { - s.print_modifier_call(modifier, is_base) - }); + self.print_fn_attribute( + m.span, + &mut map, + &mut |s| s.print_modifier_call(modifier, is_base), + is_first, + ); + is_first = false; } } if !returns.is_empty() { @@ -731,6 +732,7 @@ impl<'ast> State<'_, 'ast> { self.word("returns "); self.print_parameter_list(returns, returns.span, ListFormat::Consistent(false)); } + self.print_comments_skip_ws(body_span.lo()); self.end(); // Print fn body @@ -762,6 +764,7 @@ impl<'ast> State<'_, 'ast> { self.word(";"); } self.end(); + self.print_trailing_comment(body_span.hi(), None); } fn is_modifier_a_base_contract( @@ -2053,25 +2056,26 @@ impl<'ast> State<'_, 'ast> { span: Span, map: &mut HashMap, Vec)>, print_fn: &mut dyn FnMut(&mut Self), + is_first: bool, ) { match map.remove(&span.lo()) { Some((pre_comments, post_comments)) => { for cmnt in pre_comments { self.print_comment(cmnt, false); } - if !self.is_bol_or_only_ind() && !self.last_token_is_space() { + if !is_first && !self.is_bol_or_only_ind() && !self.last_token_is_space() { self.space(); } self.ibox(0); print_fn(self); - self.end(); for cmnt in post_comments { - self.print_comment(cmnt, false); + self.print_comment(cmnt, true); } + self.end(); } // Fallback for attributes with no comments None => { - if !self.is_bol_or_only_ind() && !self.last_token_is_space() { + if !is_first && !self.is_bol_or_only_ind() && !self.last_token_is_space() { self.space(); } print_fn(self); @@ -2129,20 +2133,26 @@ impl BlockFormat { } #[derive(Debug, Clone)] -pub(crate) enum AttributeKind<'a> { +pub(crate) enum AttributeKind<'ast> { Visibility(ast::Visibility), StateMutability(ast::StateMutability), Virtual, - Override(&'a ast::Override<'a>), - Modifier(&'a ast::Modifier<'a>), + Override(&'ast ast::Override<'ast>), + Modifier(&'ast ast::Modifier<'ast>), } #[derive(Debug, Clone)] -pub(crate) struct AttributeInfo<'a> { - pub(crate) kind: AttributeKind<'a>, +pub(crate) struct AttributeInfo<'ast> { + pub(crate) kind: AttributeKind<'ast>, pub(crate) span: Span, } +impl<'ast> AttributeInfo<'ast> { + fn is_non_payable(&self) -> bool { + matches!(self.kind, AttributeKind::StateMutability(ast::StateMutability::NonPayable)) + } +} + /// Helper struct to map attributes to their associated comments in function headers. pub(crate) struct AttributeCommentMapper<'ast> { limit_pos: BytePos, diff --git a/crates/fmt-2/tests/formatter.rs b/crates/fmt-2/tests/formatter.rs index 1c6ae79a9f0f6..56393f018de18 100644 --- a/crates/fmt-2/tests/formatter.rs +++ b/crates/fmt-2/tests/formatter.rs @@ -155,10 +155,10 @@ fmt_tests! { ArrayExpressions, // TODO: print cmnt before memory kw once span is available (solar). Is the rest acceptable? BlockComments, // FIX: empty line after doc cmnt and before constructor BlockCommentsFunction, // OK - ConditionalOperatorExpression, // FIX: fn block braces + ConditionalOperatorExpression, // OK (once block braces are fixed) ConstructorDefinition, // OK ConstructorModifierStyle, // OK - ContractDefinition, // TODO: check boxes, panics + ContractDefinition, // OKish DocComments, // FIX: idempotency (comment-related) DoWhileStatement, // OK? (once block braces are fixed).. is it acceptable? EmitStatement, // OK From 729e250e5369783cb25fbdbde9c23fcc50f64df0 Mon Sep 17 00:00:00 2001 From: 0xrusowsky <0xrusowsky@proton.me> Date: Thu, 3 Jul 2025 11:29:25 +0200 Subject: [PATCH 19/31] fix: block comments + contract definition --- crates/fmt-2/src/comments.rs | 51 ++++++++++++++++++++++----------- crates/fmt-2/src/state.rs | 51 ++++++++++++++++++++++++--------- crates/fmt-2/tests/formatter.rs | 4 +-- 3 files changed, 75 insertions(+), 31 deletions(-) diff --git a/crates/fmt-2/src/comments.rs b/crates/fmt-2/src/comments.rs index 68eeb8329e9cd..bbe6e9033d7b1 100644 --- a/crates/fmt-2/src/comments.rs +++ b/crates/fmt-2/src/comments.rs @@ -33,25 +33,23 @@ fn all_whitespace(s: &str, col: CharPos) -> Option { Some(idx) } -/// Returns `Some(k)` where `k` is the byte offset of the first non-whitespace char preceded by a -/// whitespace. Returns `k = 0` if `s` starts with a non-whitespace char. If `s` only contains -/// whitespaces, returns `None`. +/// Returns `Some(k)` where `k` is the byte offset of the first non-whitespace char. Returns `k = 0` +/// if `s` starts with a non-whitespace char. If `s` only contains whitespaces, returns `None`. fn first_non_whitespace(s: &str) -> Option { let mut len = 0; for (i, ch) in s.char_indices() { if ch.is_whitespace() { len = ch.len_utf8() } else { - return if i == 0 { Some(0) } else { Some(i - len) }; + return if i == 0 { Some(0) } else { Some(i + 1 - len) }; } } None } /// Returns a slice of `s` with a whitespace prefix removed based on `col`. If the first `col` chars -/// of `s` are all whitespace, returns a slice starting after that prefix. Otherwise, -/// returns a slice that leaves at most one leading whitespace char. -fn normalize_block_comment(s: &str, col: CharPos) -> &str { +/// of `s` are all whitespace, returns a slice starting after that prefix. +fn normalize_block_comment_ws(s: &str, col: CharPos) -> &str { let len = s.len(); if let Some(col) = all_whitespace(s, col) { return if col < len { &s[col..] } else { "" }; @@ -85,10 +83,20 @@ fn split_block_comment_into_lines(text: &str, is_doc: bool, col: CharPos) -> Vec let mut lines = text.lines(); if let Some(line) = lines.next() { let line = line.trim_end(); + // Ensure first line of a doc comment only has the `/**` decorator if let Some((_, second)) = line.split_once("/**") { res.push("/**".to_string()); if !second.trim().is_empty() { - res.push(format!(" * {}", second.trim())); + let line = normalize_block_comment_ws(second, col).trim_end(); + // Ensure last line of a doc comment only has the `*/` decorator + if let Some((first, _)) = line.split_once("*/") { + if !first.trim().is_empty() { + res.push(format_doc_block_comment(first.trim_end())); + } + res.push(" */".to_string()); + } else { + res.push(format_doc_block_comment(line.trim_end())); + } } } else { res.push(line.to_string()); @@ -96,7 +104,7 @@ fn split_block_comment_into_lines(text: &str, is_doc: bool, col: CharPos) -> Vec } for (pos, line) in lines.into_iter().delimited() { - let line = normalize_block_comment(line, col).trim_end().to_string(); + let line = normalize_block_comment_ws(line, col).trim_end().to_string(); if !is_doc { res.push(line); continue; @@ -106,10 +114,10 @@ fn split_block_comment_into_lines(text: &str, is_doc: bool, col: CharPos) -> Vec } else { if let Some((first, _)) = line.split_once("*/") { if !first.trim().is_empty() { - res.push(format_doc_block_comment(first)) + res.push(format_doc_block_comment(first)); } } - res.push(" */".to_string()) + res.push(" */".to_string()); } } res @@ -230,13 +238,12 @@ impl Comments { pub fn iter(&self) -> impl Iterator { self.comments.as_slice().iter() } - - pub fn trailing_comment( - &mut self, + pub fn peek_trailing_comment( + &self, sm: &SourceMap, span_pos: BytePos, next_pos: Option, - ) -> Option { + ) -> Option<&Comment> { if let Some(cmnt) = self.peek() { if cmnt.style != CommentStyle::Trailing { return None; @@ -245,10 +252,22 @@ impl Comments { let comment_line = sm.lookup_char_pos(cmnt.pos()); let next = next_pos.unwrap_or_else(|| cmnt.pos() + BytePos(1)); if span_pos < cmnt.pos() && cmnt.pos() < next && span_line.line == comment_line.line { - return Some(self.next().unwrap()); + return Some(cmnt); } } None } + + pub fn trailing_comment( + &mut self, + sm: &SourceMap, + span_pos: BytePos, + next_pos: Option, + ) -> Option { + match self.peek_trailing_comment(sm, span_pos, next_pos) { + Some(_) => self.next(), + None => None, + } + } } diff --git a/crates/fmt-2/src/state.rs b/crates/fmt-2/src/state.rs index e0960da12c84f..42e19744d9f9d 100644 --- a/crates/fmt-2/src/state.rs +++ b/crates/fmt-2/src/state.rs @@ -91,7 +91,7 @@ impl<'sess> State<'sess, '_> { break; } let cmnt = self.next_comment().unwrap(); - if skip_ws && cmnt.style == CommentStyle::BlankLine { + if skip_ws && cmnt.style.is_blank() { continue; } last_style = Some(cmnt.style); @@ -192,6 +192,17 @@ impl<'sess> State<'sess, '_> { self.comments.next() } + fn peek_trailing_comment<'b>( + &'b self, + span_pos: BytePos, + next_pos: Option, + ) -> Option<&'b Comment> + where + 'sess: 'b, + { + self.comments.peek_trailing_comment(self.sm, span_pos, next_pos) + } + fn print_trailing_comment(&mut self, span_pos: BytePos, next_pos: Option) -> bool { if let Some(cmnt) = self.comments.trailing_comment(self.sm, span_pos, next_pos) { self.print_comment(cmnt, false); @@ -573,16 +584,18 @@ impl<'ast> State<'_, 'ast> { self.word("{"); if !body.is_empty() { self.hardbreak(); - let comment = self.print_comments(body[0].span.lo()); - if self.config.contract_new_lines && comment != Some(CommentStyle::BlankLine) { - self.hardbreak(); + if let Some(cmnt) = self.print_comments(body[0].span.lo()) { + if self.config.contract_new_lines && !cmnt.is_blank() { + self.hardbreak(); + } } for item in body.iter() { self.print_item(item); } - let comment = self.print_comments(span.hi()); - if self.config.contract_new_lines && comment != Some(CommentStyle::BlankLine) { - self.hardbreak(); + if let Some(cmnt) = self.print_comments(span.hi()) { + if self.config.contract_new_lines && !cmnt.is_blank() { + self.hardbreak(); + } } self.s.offset(-self.ind); } else { @@ -764,7 +777,13 @@ impl<'ast> State<'_, 'ast> { self.word(";"); } self.end(); - self.print_trailing_comment(body_span.hi(), None); + + // trailing comments after the fn body are wapped + if self.peek_trailing_comment(body_span.hi(), None).is_some() { + self.hardbreak(); + self.hardbreak(); + self.print_trailing_comment(body_span.hi(), None); + } } fn is_modifier_a_base_contract( @@ -1816,17 +1835,23 @@ impl<'ast> State<'_, 'ast> { // Empty blocks with comments require special attention else if block.is_empty() { if let Some(comment) = self.peek_comment() { - if !comment.style.is_mixed() { + if comment.style.is_trailing() { self.word("{}"); self.print_comments_skip_ws(span.hi()); } else { - self.s.cbox(self.ind); self.word("{"); - self.space(); + self.s.cbox(self.ind); self.print_comments_skip_ws(span.hi()); - self.zerobreak(); - self.word("}"); + // manually adjust offset to ensure that the closing brace is properly indented. + // if the last cmnt was breaking, we replace offset to avoid e a double // + // break. + if self.is_bol_or_only_ind() { + self.break_offset_if_not_bol(0, -self.ind); + } else { + self.s.break_offset(0, -self.ind); + } self.end(); + self.word("}"); } } else { self.word("{}"); diff --git a/crates/fmt-2/tests/formatter.rs b/crates/fmt-2/tests/formatter.rs index 56393f018de18..3f7d47300273b 100644 --- a/crates/fmt-2/tests/formatter.rs +++ b/crates/fmt-2/tests/formatter.rs @@ -158,8 +158,8 @@ fmt_tests! { ConditionalOperatorExpression, // OK (once block braces are fixed) ConstructorDefinition, // OK ConstructorModifierStyle, // OK - ContractDefinition, // OKish - DocComments, // FIX: idempotency (comment-related) + ContractDefinition, // OKish (FIX: extra blank line before constructors that have a preceeding cmnt + fn param alignement) + DocComments, // OK (once block braces are fixed) DoWhileStatement, // OK? (once block braces are fixed).. is it acceptable? EmitStatement, // OK EnumDefinition, // OK From 3c518a13444fb6a5f82022b9737155d271c7e26f Mon Sep 17 00:00:00 2001 From: 0xrusowsky <0xrusowsky@proton.me> Date: Thu, 3 Jul 2025 12:09:57 +0200 Subject: [PATCH 20/31] fix: wrap trailing comments --- crates/fmt-2/src/state.rs | 25 +++++++++++++++++++------ crates/fmt-2/tests/formatter.rs | 2 +- crates/fmt/testdata/DocComments/fmt.sol | 1 + 3 files changed, 21 insertions(+), 7 deletions(-) diff --git a/crates/fmt-2/src/state.rs b/crates/fmt-2/src/state.rs index 42e19744d9f9d..70dea93f2a713 100644 --- a/crates/fmt-2/src/state.rs +++ b/crates/fmt-2/src/state.rs @@ -129,6 +129,9 @@ impl<'sess> State<'sess, '_> { } } CommentStyle::Isolated => { + println!("{cmnt:?}"); + println!(" > BOL? {}", self.is_bol_or_only_ind()); + println!(""); self.hardbreak_if_not_bol(); for line in cmnt.lines { // Don't print empty lines because they will end up as trailing @@ -583,9 +586,14 @@ impl<'ast> State<'_, 'ast> { self.word("{"); if !body.is_empty() { - self.hardbreak(); - if let Some(cmnt) = self.print_comments(body[0].span.lo()) { - if self.config.contract_new_lines && !cmnt.is_blank() { + if self.peek_comment_before(body[0].span.lo()).is_some() { + if self.config.contract_new_lines { + self.hardbreak(); + } + self.print_comments(body[0].span.lo()); + } else { + self.hardbreak(); + if self.config.contract_new_lines { self.hardbreak(); } } @@ -606,6 +614,9 @@ impl<'ast> State<'_, 'ast> { }; } self.end(); + if self.config.contract_new_lines { + self.hardbreak_if_nonempty(); + } self.word("}"); self.contract = None; @@ -778,10 +789,12 @@ impl<'ast> State<'_, 'ast> { } self.end(); - // trailing comments after the fn body are wapped if self.peek_trailing_comment(body_span.hi(), None).is_some() { - self.hardbreak(); - self.hardbreak(); + // trailing comments after the fn body are isolated + if self.config.wrap_comments { + self.hardbreak(); + self.hardbreak(); + } self.print_trailing_comment(body_span.hi(), None); } } diff --git a/crates/fmt-2/tests/formatter.rs b/crates/fmt-2/tests/formatter.rs index 3f7d47300273b..bdb6f798eae0c 100644 --- a/crates/fmt-2/tests/formatter.rs +++ b/crates/fmt-2/tests/formatter.rs @@ -153,7 +153,7 @@ fmt_tests! { #[ignore = "annotations are not valid Solidity"] Annotation, ArrayExpressions, // TODO: print cmnt before memory kw once span is available (solar). Is the rest acceptable? - BlockComments, // FIX: empty line after doc cmnt and before constructor + BlockComments, // OK (once block braces are fixed) BlockCommentsFunction, // OK ConditionalOperatorExpression, // OK (once block braces are fixed) ConstructorDefinition, // OK diff --git a/crates/fmt/testdata/DocComments/fmt.sol b/crates/fmt/testdata/DocComments/fmt.sol index 4248f0fe587da..8076a11169356 100644 --- a/crates/fmt/testdata/DocComments/fmt.sol +++ b/crates/fmt/testdata/DocComments/fmt.sol @@ -1,3 +1,4 @@ +// config: wrap_comments = true pragma solidity ^0.8.13; /// @title A Hello world example From d4c9403ea30a1b734175f24201eb756e7744771d Mon Sep 17 00:00:00 2001 From: 0xrusowsky <0xrusowsky@proton.me> Date: Thu, 3 Jul 2025 16:20:05 +0200 Subject: [PATCH 21/31] fix fn alingment --- crates/fmt-2/src/state.rs | 128 +++++++++++++++----------------- crates/fmt-2/tests/formatter.rs | 12 +-- 2 files changed, 64 insertions(+), 76 deletions(-) diff --git a/crates/fmt-2/src/state.rs b/crates/fmt-2/src/state.rs index 70dea93f2a713..bedcee253f5fa 100644 --- a/crates/fmt-2/src/state.rs +++ b/crates/fmt-2/src/state.rs @@ -129,9 +129,6 @@ impl<'sess> State<'sess, '_> { } } CommentStyle::Isolated => { - println!("{cmnt:?}"); - println!(" > BOL? {}", self.is_bol_or_only_ind()); - println!(""); self.hardbreak_if_not_bol(); for line in cmnt.lines { // Don't print empty lines because they will end up as trailing @@ -311,10 +308,10 @@ impl<'sess> State<'sess, '_> { // If cmnts should break + comment before the 1st item, force hardbreak. self.hardbreak(); } - skip_first_break = self.print_trailing_comment(pos_lo, Some(first_pos)); - skip_first_break = self - .peek_comment_before(first_pos) - .map_or(skip_first_break, |_| format.breaks_comments()); + if self.peek_comment_before(first_pos).is_some() { + skip_first_break = true; + } + self.print_trailing_comment(pos_lo, Some(first_pos)); } if !skip_first_break { self.zerobreak(); @@ -688,7 +685,7 @@ impl<'ast> State<'_, 'ast> { self.cbox(0); self.s.cbox(self.ind); - let fn_start_indent = self.s.current_indent(); + // let fn_start_indent = self.s.current_indent(); // Print fn name and params self.word(kind.to_str()); @@ -696,44 +693,32 @@ impl<'ast> State<'_, 'ast> { self.nbsp(); self.print_ident(&name); } - self.s.ibox(-self.ind); - self.print_parameter_list(parameters, parameters.span, ListFormat::Compact(true)); + // TODO(ask dani): unable to keep single param from breaking brackets when the attributes do + // break. + self.s.cbox(-self.ind); + self.print_parameter_list(parameters, parameters.span, ListFormat::Consistent(true)); self.end(); // Map attributes to their corresponding comments let (attributes, mut map) = AttributeCommentMapper::new(header.returns.span.lo()).build(self, header); - if !(attributes.len() == 1 && attributes[0].is_non_payable()) { - self.space(); - } - - self.s.cbox(0); - self.s.cbox(0); // Print fn attributes in correct order + self.s.cbox(0); let mut is_first = true; if let Some(v) = visibility { self.print_fn_attribute(v.span, &mut map, &mut |s| s.word(v.to_str()), is_first); is_first = false; } if *sm != ast::StateMutability::NonPayable { - if !self.is_bol_or_only_ind() && !self.last_token_is_space() { - self.space(); - } self.print_fn_attribute(sm.span, &mut map, &mut |s| s.word(sm.to_str()), is_first); is_first = false; } if let Some(v) = virtual_ { - if !self.is_bol_or_only_ind() && !self.last_token_is_space() { - self.space(); - } self.print_fn_attribute(v, &mut map, &mut |s| s.word("virtual"), is_first); is_first = false; } if let Some(o) = override_ { - if !self.is_bol_or_only_ind() && !self.last_token_is_space() { - self.space(); - } self.print_fn_attribute(o.span, &mut map, &mut |s| s.print_override(o), is_first); is_first = false; } @@ -760,22 +745,25 @@ impl<'ast> State<'_, 'ast> { self.end(); // Print fn body + // TODO(ask dani): print manually-handling braces? (requires current indentation) if let Some(body) = body { - let current_indent = self.s.current_indent(); - let new_line = current_indent > fn_start_indent; - if !new_line { - self.nbsp(); - // self.word("{"); - self.end(); - if !body.is_empty() { - self.hardbreak(); - } - } else { - self.space(); - self.s.offset(-self.ind); - // self.word("{"); - self.end(); - } + // let current_indent = self.s.current_indent(); + // let new_line = current_indent > fn_start_indent; + // if !new_line { + // self.nbsp(); + // self.word("{"); + // self.end(); + // if !body.is_empty() { + // self.hardbreak(); + // } + // } else { + // self.space(); + // self.s.offset(-self.ind); + // self.word("{"); + // self.end(); + // } + self.end(); + self.space(); self.end(); // self.print_block_without_braces(body, body_span, new_line); self.print_block(body, body_span); @@ -787,7 +775,6 @@ impl<'ast> State<'_, 'ast> { self.end(); self.word(";"); } - self.end(); if self.peek_trailing_comment(body_span.hi(), None).is_some() { // trailing comments after the fn body are isolated @@ -1717,6 +1704,7 @@ impl<'ast> State<'_, 'ast> { fn print_if_cond(&mut self, kw: &'static str, cond: &'ast ast::Expr<'ast>, pos_hi: BytePos) { self.word_nbsp(kw); + // TODO(ask dani): how to prevent expressions from always breaking the parenthesis self.print_tuple( std::slice::from_ref(cond), cond.span.lo(), @@ -1764,27 +1752,7 @@ impl<'ast> State<'_, 'ast> { } else { std::slice::from_ref(stmt) }; - // TODO: figure out: - // > why stmts are not indented - // > why braces are not always printed? - if matches!(format, BlockFormat::NoBraces(_)) { - self.word("{"); - self.zerobreak(); - self.s.cbox(self.ind); - } - self.print_block_inner( - stmts, - format, - // if attempt_single_line { BlockFormat::Compact(true) } else { BlockFormat::Regular }, - Self::print_stmt, - |b| b.span, - stmt.span, - ); - if matches!(format, BlockFormat::NoBraces(_)) { - self.s.offset(-self.ind); - self.end(); - self.word("}"); - } + self.print_block_inner(stmts, format, Self::print_stmt, |b| b.span, stmt.span); } fn print_yul_block( @@ -2101,7 +2069,7 @@ impl<'ast> State<'_, 'ast> { for cmnt in pre_comments { self.print_comment(cmnt, false); } - if !is_first && !self.is_bol_or_only_ind() && !self.last_token_is_space() { + if !self.is_bol_or_only_ind() { self.space(); } self.ibox(0); @@ -2111,7 +2079,7 @@ impl<'ast> State<'_, 'ast> { } self.end(); } - // Fallback for attributes with no comments + // Fallback for attributes not in the map (should never happen) None => { if !is_first && !self.is_bol_or_only_ind() && !self.last_token_is_space() { self.space(); @@ -2179,18 +2147,38 @@ pub(crate) enum AttributeKind<'ast> { Modifier(&'ast ast::Modifier<'ast>), } +impl<'ast> AttributeKind<'ast> { + fn is_visibility(&self) -> bool { + matches!(self, Self::Visibility(_)) + } + + fn is_state_mutability(&self) -> bool { + matches!(self, Self::StateMutability(_)) + } + + fn is_non_payable(&self) -> bool { + matches!(self, Self::StateMutability(ast::StateMutability::NonPayable)) + } + + fn is_virtual(&self) -> bool { + matches!(self, Self::Virtual) + } + + fn is_override(&self) -> bool { + matches!(self, Self::Override(_)) + } + + fn is_modifier(&self) -> bool { + matches!(self, Self::Modifier(_)) + } +} + #[derive(Debug, Clone)] pub(crate) struct AttributeInfo<'ast> { pub(crate) kind: AttributeKind<'ast>, pub(crate) span: Span, } -impl<'ast> AttributeInfo<'ast> { - fn is_non_payable(&self) -> bool { - matches!(self.kind, AttributeKind::StateMutability(ast::StateMutability::NonPayable)) - } -} - /// Helper struct to map attributes to their associated comments in function headers. pub(crate) struct AttributeCommentMapper<'ast> { limit_pos: BytePos, diff --git a/crates/fmt-2/tests/formatter.rs b/crates/fmt-2/tests/formatter.rs index bdb6f798eae0c..8c3a52b273dbc 100644 --- a/crates/fmt-2/tests/formatter.rs +++ b/crates/fmt-2/tests/formatter.rs @@ -158,8 +158,8 @@ fmt_tests! { ConditionalOperatorExpression, // OK (once block braces are fixed) ConstructorDefinition, // OK ConstructorModifierStyle, // OK - ContractDefinition, // OKish (FIX: extra blank line before constructors that have a preceeding cmnt + fn param alignement) - DocComments, // OK (once block braces are fixed) + ContractDefinition, // OKish (FIX: fn param alignement) + DocComments, // OK (once block braces are fixed). TODO: Ask dani how to wrap comments. DoWhileStatement, // OK? (once block braces are fixed).. is it acceptable? EmitStatement, // OK EnumDefinition, // OK @@ -169,11 +169,11 @@ fmt_tests! { ForStatement, // OK? (once block braces are fixed).. is it acceptable? FunctionCall, // TODO: ask Dani how to enhance PP so that trailing comments aren't accounted for when breaking lines FunctionCallArgsStatement, // FIX: fn block braces. Is the rest acceptable? - FunctionDefinition, // TODO: fix fn block braces + function postfix trailing cmnt - FunctionDefinitionWithFunctionReturns, // OK? is it acceptable? - FunctionType, // OK? is it acceptable? + FunctionDefinition, // TODO: fix fn block braces + prevent oneParam fn from breaking when attributes break + FunctionDefinitionWithFunctionReturns, // OK? (once block braces are fixed) is it acceptable? + FunctionType, // OK? (once block braces are fixed) is it acceptable? HexUnderscore, // OK (once block braces are fixed) - IfStatement, // TODO: figure out correct comment placements + IfStatement, // TODO: figure out correct comment placements + condition parenthesis wrapping + support config line wrapping IfStatement2, // OK (once block braces are fixed) ImportDirective, // OK InlineDisable, // FIX: invalid output From b06a67b19492ed130f32cf2318979603dc3adeaf Mon Sep 17 00:00:00 2001 From: 0xrusowsky <0xrusowsky@proton.me> Date: Thu, 3 Jul 2025 16:20:37 +0200 Subject: [PATCH 22/31] fix: rmv unecessary check --- crates/fmt-2/src/state.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/fmt-2/src/state.rs b/crates/fmt-2/src/state.rs index bedcee253f5fa..32ab3f98a526a 100644 --- a/crates/fmt-2/src/state.rs +++ b/crates/fmt-2/src/state.rs @@ -2081,7 +2081,7 @@ impl<'ast> State<'_, 'ast> { } // Fallback for attributes not in the map (should never happen) None => { - if !is_first && !self.is_bol_or_only_ind() && !self.last_token_is_space() { + if !is_first && !self.is_bol_or_only_ind() { self.space(); } print_fn(self); From c156ebf33d252c2e3a61fa554a779ea88f5e82cc Mon Sep 17 00:00:00 2001 From: 0xrusowsky <0xrusowsky@proton.me> Date: Fri, 4 Jul 2025 12:14:08 +0200 Subject: [PATCH 23/31] working fn headers!!! --- crates/fmt-2/src/state.rs | 141 +++++++++++++++++++------------------- 1 file changed, 71 insertions(+), 70 deletions(-) diff --git a/crates/fmt-2/src/state.rs b/crates/fmt-2/src/state.rs index 32ab3f98a526a..d45ee1cef3c8a 100644 --- a/crates/fmt-2/src/state.rs +++ b/crates/fmt-2/src/state.rs @@ -101,6 +101,11 @@ impl<'sess> State<'sess, '_> { } fn print_comment(&mut self, mut cmnt: Comment, with_space: bool) { + // // DEBUG + // if !cmnt.style.is_blank() { + // println!("{cmnt:?}"); + // println!(" > BOL? {}\n", self.is_bol_or_only_ind()); + // } match cmnt.style { CommentStyle::Mixed => { let never_break = self.last_token_is_neverbreak(); @@ -263,8 +268,7 @@ impl<'sess> State<'sess, '_> { if let Some(span) = get_span(&values[0]) { self.print_comments(span.lo()); } - self.word("("); - print(self, &values[0]); + self.word(")"); return; } @@ -301,8 +305,13 @@ impl<'sess> State<'sess, '_> { return; } + // TODO(rusowsky): this should be false for expressions, as they have sub-items and break + // themselves (see IF, WHILE, DO-WHILE) + let is_single_without_cmnts = + values.len() == 1 && self.peek_comment_before(pos_hi).is_none(); + self.s.cbox(self.ind); - let mut skip_first_break = false; + let mut skip_first_break = is_single_without_cmnts; if let Some(first_pos) = get_span(&values[0]).map(Span::lo) { if format.breaks_comments() && self.peek_comment_before(first_pos).is_some() { // If cmnts should break + comment before the 1st item, force hardbreak. @@ -320,12 +329,10 @@ impl<'sess> State<'sess, '_> { self.s.cbox(0); } - let mut skip_last_break = false; + let mut skip_last_break = is_single_without_cmnts; for (i, value) in values.iter().enumerate() { let is_last = i == values.len() - 1; let span = get_span(value); - - // box() if let Some(span) = span { if self .print_cmnts_with_space_skip_ws(span.lo()) @@ -683,9 +690,7 @@ impl<'ast> State<'_, 'ast> { .. } = *header; - self.cbox(0); self.s.cbox(self.ind); - // let fn_start_indent = self.s.current_indent(); // Print fn name and params self.word(kind.to_str()); @@ -693,8 +698,6 @@ impl<'ast> State<'_, 'ast> { self.nbsp(); self.print_ident(&name); } - // TODO(ask dani): unable to keep single param from breaking brackets when the attributes do - // break. self.s.cbox(-self.ind); self.print_parameter_list(parameters, parameters.span, ListFormat::Consistent(true)); self.end(); @@ -719,7 +722,12 @@ impl<'ast> State<'_, 'ast> { is_first = false; } if let Some(o) = override_ { - self.print_fn_attribute(o.span, &mut map, &mut |s| s.print_override(o), is_first); + self.print_fn_attribute( + o.span, + &mut map, + &mut |s| s.print_override(o), + is_first && o.paths.is_empty(), + ); is_first = false; } for m in attributes.iter().filter(|a| matches!(a.kind, AttributeKind::Modifier(_))) { @@ -742,37 +750,27 @@ impl<'ast> State<'_, 'ast> { self.print_parameter_list(returns, returns.span, ListFormat::Consistent(false)); } self.print_comments_skip_ws(body_span.lo()); - self.end(); // Print fn body - // TODO(ask dani): print manually-handling braces? (requires current indentation) if let Some(body) = body { - // let current_indent = self.s.current_indent(); - // let new_line = current_indent > fn_start_indent; - // if !new_line { - // self.nbsp(); - // self.word("{"); - // self.end(); - // if !body.is_empty() { - // self.hardbreak(); - // } - // } else { - // self.space(); - // self.s.offset(-self.ind); - // self.word("{"); - // self.end(); - // } - self.end(); self.space(); - self.end(); - // self.print_block_without_braces(body, body_span, new_line); - self.print_block(body, body_span); - // self.word("}"); + self.s.offset(-self.ind); + if !body.is_empty() { + self.word("{"); + self.end(); + self.end(); + self.print_block_without_braces(body, body_span, self.ind); + self.word("}"); + } else { + // it may have comments + self.end(); + self.end(); + self.print_block(body, body_span); + } } else { self.end(); - self.neverbreak(); - self.s.offset(-self.ind); self.end(); + self.neverbreak(); self.word(";"); } @@ -1658,7 +1656,7 @@ impl<'ast> State<'_, 'ast> { self.word("catch "); self.neverbreak(); if !args.is_empty() { - self.print_comments(args[0].span.lo()); + self.print_cmnts_with_space_skip_ws(args[0].span.lo()); if let Some(name) = name { self.print_ident(name); } @@ -1682,8 +1680,6 @@ impl<'ast> State<'_, 'ast> { self.print_if_cond("while", cond, stmt.span.lo()); self.nbsp(); self.end(); - // TODO: handle braces manually - // self.print_stmt_as_block(stmt, BlockFormat::NoBraces(false)); self.print_stmt_as_block(stmt, BlockFormat::Compact(true)); } ast::StmtKind::Placeholder => self.word("_"), @@ -1704,7 +1700,7 @@ impl<'ast> State<'_, 'ast> { fn print_if_cond(&mut self, kw: &'static str, cond: &'ast ast::Expr<'ast>, pos_hi: BytePos) { self.word_nbsp(kw); - // TODO(ask dani): how to prevent expressions from always breaking the parenthesis + // TODO(ask dani): prevent exprs from always breaking the brackets (i.e. While test) self.print_tuple( std::slice::from_ref(cond), cond.span.lo(), @@ -1734,11 +1730,11 @@ impl<'ast> State<'_, 'ast> { &mut self, block: &'ast [ast::Stmt<'ast>], span: Span, - new_line: bool, + offset: isize, ) { self.print_block_inner( block, - BlockFormat::NoBraces(new_line), + BlockFormat::NoBraces(offset), Self::print_stmt, |b| b.span, span, @@ -1778,22 +1774,8 @@ impl<'ast> State<'_, 'ast> { mut get_block_span: impl FnMut(&'ast T) -> Span, span: Span, ) { - // Braces are explicitly handled by the caller - if let BlockFormat::NoBraces(new_line) = block_format { - if new_line { - self.hardbreak_if_nonempty(); - } - for stmt in block { - print(self, stmt); - self.hardbreak_if_not_bol(); - } - self.print_comments_skip_ws(block.last().map_or(span.hi(), |b| get_block_span(b).hi())); - } // Attempt to print in a single line - else if block_format.attempt_single_line() && - block.len() == 1 && - self.single_line_block(span) - { + if block_format.attempt_single_line() && block.len() == 1 && self.single_line_block(span) { self.s.cbox(self.ind); if matches!(block_format, BlockFormat::Compact(true)) { self.scan_break(BreakToken { pre_break: Some('{'), ..Default::default() }); @@ -1812,9 +1794,11 @@ impl<'ast> State<'_, 'ast> { self.word("}"); } self.end(); + return; } + // Empty blocks with comments require special attention - else if block.is_empty() { + if block.is_empty() { if let Some(comment) = self.peek_comment() { if comment.style.is_trailing() { self.word("{}"); @@ -1822,7 +1806,7 @@ impl<'ast> State<'_, 'ast> { } else { self.word("{"); self.s.cbox(self.ind); - self.print_comments_skip_ws(span.hi()); + self.print_cmnts_with_space_skip_ws(span.hi()); // manually adjust offset to ensure that the closing brace is properly indented. // if the last cmnt was breaking, we replace offset to avoid e a double // // break. @@ -1837,19 +1821,32 @@ impl<'ast> State<'_, 'ast> { } else { self.word("{}"); } + return; } - // Regular blocks - else { + + if let BlockFormat::NoBraces(offset) = block_format { + // Braces are explicitly handled by the caller + if !block.is_empty() { + self.zerobreak(); + self.s.offset(offset); + } + self.s.cbox(self.ind); + } else { + // Regular blocks self.word("{"); self.s.cbox(self.ind); self.hardbreak_if_nonempty(); - for stmt in block { - print(self, stmt); - self.hardbreak_if_not_bol(); - } - self.print_comments_skip_ws(block.last().map_or(span.hi(), |b| get_block_span(b).hi())); - self.s.offset(-self.ind); - self.end(); + } + for stmt in block { + print(self, stmt); + self.hardbreak_if_not_bol(); + } + self.print_cmnts_with_space_skip_ws( + block.last().map_or(span.hi(), |b| get_block_span(b).hi()), + ); + self.s.offset(-self.ind); + self.end(); + if block_format.with_braces() { self.word("}"); } } @@ -2127,12 +2124,16 @@ pub(crate) enum BlockFormat { /// Attempts to fit all elements in one line, before breaking consistently. Flags whether to /// use braces or not. Compact(bool), - /// Prints blocks without braces. Flags whether the block starts at a new line or not. - /// Usefull for complex box/break setups. - NoBraces(bool), + /// Doesn't print braces. Flags the offset that should be applied before opening the block box. + /// Usefull when the caller needs to manually handle the braces. + NoBraces(isize), } impl BlockFormat { + pub(crate) fn with_braces(&self) -> bool { + !matches!(self, Self::NoBraces(_)) + } + pub(crate) fn attempt_single_line(&self) -> bool { matches!(self, Self::Compact(_)) } From a6afa141718606362942d311cd45134564553f8b Mon Sep 17 00:00:00 2001 From: 0xrusowsky <0xrusowsky@proton.me> Date: Fri, 4 Jul 2025 17:53:54 +0200 Subject: [PATCH 24/31] block with comments at the beginning --- crates/fmt-2/src/pp/convenience.rs | 4 +- crates/fmt-2/src/pp/helpers.rs | 1 - crates/fmt-2/src/state.rs | 192 +++++++++++++++++++---------- crates/fmt-2/tests/formatter.rs | 44 +++---- 4 files changed, 154 insertions(+), 87 deletions(-) diff --git a/crates/fmt-2/src/pp/convenience.rs b/crates/fmt-2/src/pp/convenience.rs index 368ae8c5cf796..c364ee1c1d19c 100644 --- a/crates/fmt-2/src/pp/convenience.rs +++ b/crates/fmt-2/src/pp/convenience.rs @@ -179,8 +179,8 @@ impl Token { } pub(crate) fn is_hardbreak(&self) -> bool { - if let Self::Break(BreakToken { offset, blank_space, never_break, .. }) = *self { - return offset == 0 && blank_space == SIZE_INFINITY as usize && !never_break; + if let Self::Break(BreakToken { blank_space, never_break, .. }) = *self { + return blank_space == SIZE_INFINITY as usize && !never_break; } false } diff --git a/crates/fmt-2/src/pp/helpers.rs b/crates/fmt-2/src/pp/helpers.rs index fbda028ab9f69..983d22f097c85 100644 --- a/crates/fmt-2/src/pp/helpers.rs +++ b/crates/fmt-2/src/pp/helpers.rs @@ -12,7 +12,6 @@ impl Printer { pub fn hardbreak_if_not_bol(&mut self) { if !self.is_bol_or_only_ind() { if let Some(Token::Break(last)) = self.last_token_still_buffered() { - println!("> last: {last:?}"); if last.offset != 0 { self.replace_last_token_still_buffered(Self::hardbreak_tok_offset(last.offset)); return; diff --git a/crates/fmt-2/src/state.rs b/crates/fmt-2/src/state.rs index d45ee1cef3c8a..86a012f8d00c5 100644 --- a/crates/fmt-2/src/state.rs +++ b/crates/fmt-2/src/state.rs @@ -68,31 +68,46 @@ impl<'sess> State<'sess, '_> { /// Returns `Some` with the style of the last comment printed, or `None` if no comment was /// printed. fn print_comments(&mut self, pos: BytePos) -> Option { - self.print_comments_inner(pos, false, false) + self.print_comments_inner(pos, None, false) } fn print_comments_skip_ws(&mut self, pos: BytePos) -> Option { - self.print_comments_inner(pos, true, false) + self.print_comments_inner(pos, Some(Skip::All), false) + } + + fn print_comments_skip_ws_inner(&mut self, pos: BytePos, skip: Skip) -> Option { + self.print_comments_inner(pos, Some(skip), false) } fn print_cmnts_with_space_skip_ws(&mut self, pos: BytePos) -> Option { - self.print_comments_inner(pos, true, true) + self.print_comments_inner(pos, Some(Skip::All), true) } fn print_comments_inner( &mut self, pos: BytePos, - skip_ws: bool, + skip_ws: Option, mixed_with_space: bool, ) -> Option { let mut last_style = None; + let mut all_blank = true; while let Some(cmnt) = self.peek_comment() { if cmnt.pos() >= pos { break; } let cmnt = self.next_comment().unwrap(); - if skip_ws && cmnt.style.is_blank() { - continue; + if cmnt.style.is_blank() { + match skip_ws { + Some(Skip::All) => continue, + Some(Skip::First) => { + if all_blank { + continue; + } + } + None => (), + } + } else { + all_blank = false; } last_style = Some(cmnt.style); self.print_comment(cmnt, mixed_with_space); @@ -118,15 +133,12 @@ impl<'sess> State<'sess, '_> { } if let Some(last) = cmnt.lines.pop() { self.ibox(0); - for line in cmnt.lines { self.word(line); self.hardbreak(); } - self.word(last); self.space(); - self.end(); } if never_break { @@ -145,7 +157,6 @@ impl<'sess> State<'sess, '_> { } } CommentStyle::Trailing => { - // if !self.is_bol_or_only_ind() && !self.last_token_is_space() { if !self.is_bol_or_only_ind() { self.nbsp(); } @@ -190,7 +201,7 @@ impl<'sess> State<'sess, '_> { where 'sess: 'b, { - self.comments.peek().filter(|c| c.pos() < pos) + self.comments.iter().take_while(|c| c.pos() < pos).find(|c| !c.style.is_blank()) } fn next_comment(&mut self) -> Option { @@ -265,8 +276,24 @@ impl<'sess> State<'sess, '_> { { // Format single-item inline lists directly without boxes if values.len() == 1 && matches!(format, ListFormat::Inline) { + self.word("("); if let Some(span) = get_span(&values[0]) { - self.print_comments(span.lo()); + self.s.cbox(self.ind); + let mut skip_break = true; + if self.peek_comment_before(span.hi()).is_some() { + self.hardbreak(); + skip_break = false; + } + self.print_cmnts_with_space_skip_ws(span.lo()); + print(self, &values[0]); + if !self.print_trailing_comment(span.hi(), None) && skip_break { + self.neverbreak(); + } else { + self.break_offset_if_not_bol(0, -self.ind); + } + self.end(); + } else { + print(self, &values[0]); } self.word(")"); @@ -313,11 +340,11 @@ impl<'sess> State<'sess, '_> { self.s.cbox(self.ind); let mut skip_first_break = is_single_without_cmnts; if let Some(first_pos) = get_span(&values[0]).map(Span::lo) { - if format.breaks_comments() && self.peek_comment_before(first_pos).is_some() { - // If cmnts should break + comment before the 1st item, force hardbreak. - self.hardbreak(); - } if self.peek_comment_before(first_pos).is_some() { + if format.breaks_comments() { + // If cmnts should break + comment before the 1st item, force hardbreak. + self.hardbreak(); + } skip_first_break = true; } self.print_trailing_comment(pos_lo, Some(first_pos)); @@ -435,15 +462,24 @@ macro_rules! get_span { impl<'ast> State<'_, 'ast> { pub fn print_source_unit(&mut self, source_unit: &'ast ast::SourceUnit<'ast>) { for item in source_unit.items.iter() { - self.print_item(item); + self.print_item(item, false); } self.print_remaining_comments(); } - fn print_item(&mut self, item: &'ast ast::Item<'ast>) { + fn print_item(&mut self, item: &'ast ast::Item<'ast>, skip_ws: bool) { let ast::Item { ref docs, span, ref kind } = *item; self.print_docs(docs); - self.print_comments(span.lo()); + let add_zero_break = if skip_ws { + self.print_cmnts_with_space_skip_ws(span.lo()) + } else { + self.print_comments(span.lo()) + } + .is_some_and(|cmnt| cmnt.is_mixed()); + if add_zero_break { + self.zerobreak(); + } + match kind { ast::ItemKind::Pragma(pragma) => self.print_pragma(pragma), ast::ItemKind::Import(import) => self.print_import(import), @@ -458,7 +494,7 @@ impl<'ast> State<'_, 'ast> { ast::ItemKind::Event(event) => self.print_event(event), } self.print_comments(span.hi()); - // self.print_trailing_comment(span.hi(), None); + self.print_trailing_comment(span.hi(), None); self.hardbreak_if_not_bol(); } @@ -590,36 +626,33 @@ impl<'ast> State<'_, 'ast> { self.word("{"); if !body.is_empty() { - if self.peek_comment_before(body[0].span.lo()).is_some() { - if self.config.contract_new_lines { - self.hardbreak(); - } - self.print_comments(body[0].span.lo()); - } else { + self.hardbreak(); + if self.config.contract_new_lines { self.hardbreak(); - if self.config.contract_new_lines { - self.hardbreak(); - } } - for item in body.iter() { - self.print_item(item); + if self.peek_comment_before(body[0].span.lo()).is_some() { + self.print_comments_skip_ws_inner(body[0].span.lo(), Skip::First); + } + for (pos, item) in body.iter().delimited() { + self.print_item(item, pos.is_first); } - if let Some(cmnt) = self.print_comments(span.hi()) { + if let Some(cmnt) = self.print_comments_skip_ws(span.hi()) { if self.config.contract_new_lines && !cmnt.is_blank() { self.hardbreak(); } } self.s.offset(-self.ind); + self.end(); + if self.config.contract_new_lines { + self.hardbreak_if_nonempty(); + } } else { - if self.print_comments(span.hi()).is_some() { + if self.print_comments_skip_ws(span.hi()).is_some() { self.zerobreak(); } else if self.config.bracket_spacing { self.nbsp(); }; - } - self.end(); - if self.config.contract_new_lines { - self.hardbreak_if_nonempty(); + self.end(); } self.word("}"); @@ -749,25 +782,31 @@ impl<'ast> State<'_, 'ast> { self.word("returns "); self.print_parameter_list(returns, returns.span, ListFormat::Consistent(false)); } - self.print_comments_skip_ws(body_span.lo()); // Print fn body if let Some(body) = body { - self.space(); - self.s.offset(-self.ind); - if !body.is_empty() { - self.word("{"); - self.end(); - self.end(); - self.print_block_without_braces(body, body_span, self.ind); - self.word("}"); + if self.peek_comment_before(body_span.lo()).map_or(true, |cmnt| cmnt.style.is_mixed()) { + if attributes.len() == 1 && returns.is_empty() { + self.nbsp(); + self.zerobreak(); + } else { + self.space(); + } + self.s.offset(-self.ind); + self.print_comments_skip_ws(body_span.lo()); } else { - // it may have comments - self.end(); - self.end(); - self.print_block(body, body_span); + self.zerobreak(); + self.s.offset(-self.ind); + self.print_comments_skip_ws(body_span.lo()); + self.s.offset(-self.ind); } + self.word("{"); + self.end(); + self.end(); + self.print_block_without_braces(body, body_span, self.ind); + self.word("}"); } else { + self.print_cmnts_with_space_skip_ws(body_span.lo()); self.end(); self.end(); self.neverbreak(); @@ -1799,17 +1838,33 @@ impl<'ast> State<'_, 'ast> { // Empty blocks with comments require special attention if block.is_empty() { - if let Some(comment) = self.peek_comment() { - if comment.style.is_trailing() { + // Trailing comments are printed after the block + if self.peek_comment_before(span.hi()).map_or(true, |cmnt| cmnt.style.is_trailing()) { + if self.config.bracket_spacing { + if block_format.with_braces() { + self.word("{ }"); + } else { + self.nbsp(); + } + } else if block_format.with_braces() { self.word("{}"); - self.print_comments_skip_ws(span.hi()); + } + self.print_comments_skip_ws(span.hi()); + } + // Other comments are printed inside the block + else { + if let BlockFormat::NoBraces(offset) = block_format { + // self.zerobreak(); + // self.s.offset(offset); + self.s.cbox(offset); } else { self.word("{"); self.s.cbox(self.ind); - self.print_cmnts_with_space_skip_ws(span.hi()); + } + self.print_cmnts_with_space_skip_ws(span.hi()); + if block_format.with_braces() { // manually adjust offset to ensure that the closing brace is properly indented. - // if the last cmnt was breaking, we replace offset to avoid e a double // - // break. + // if the last cmnt was breaking, we replace offset to avoid e a double break. if self.is_bol_or_only_ind() { self.break_offset_if_not_bol(0, -self.ind); } else { @@ -1817,25 +1872,33 @@ impl<'ast> State<'_, 'ast> { } self.end(); self.word("}"); + } else { + self.end(); } - } else { - self.word("{}"); } return; } if let BlockFormat::NoBraces(offset) = block_format { - // Braces are explicitly handled by the caller - if !block.is_empty() { + if self.peek_comment_before(get_block_span(&block[0]).lo()).is_some() { + self.hardbreak(); + self.break_offset_if_not_bol(0, offset); + self.print_comments_skip_ws(get_block_span(&block[0]).lo()); + self.s.offset(offset); + } else { self.zerobreak(); self.s.offset(offset); } self.s.cbox(self.ind); } else { - // Regular blocks self.word("{"); self.s.cbox(self.ind); - self.hardbreak_if_nonempty(); + if self + .print_comments_skip_ws(get_block_span(&block[0]).lo()) + .map_or(true, |cmnt| cmnt.is_mixed()) + { + self.hardbreak_if_nonempty(); + } } for stmt in block { print(self, stmt); @@ -2299,3 +2362,8 @@ fn stmt_needs_semi<'ast>(stmt: &'ast ast::StmtKind<'ast>) -> bool { ast::StmtKind::Placeholder { .. } => true, } } + +pub enum Skip { + First, + All, +} diff --git a/crates/fmt-2/tests/formatter.rs b/crates/fmt-2/tests/formatter.rs index 8c3a52b273dbc..8b7fccdd9e640 100644 --- a/crates/fmt-2/tests/formatter.rs +++ b/crates/fmt-2/tests/formatter.rs @@ -153,37 +153,37 @@ fmt_tests! { #[ignore = "annotations are not valid Solidity"] Annotation, ArrayExpressions, // TODO: print cmnt before memory kw once span is available (solar). Is the rest acceptable? - BlockComments, // OK (once block braces are fixed) + BlockComments, // OK BlockCommentsFunction, // OK - ConditionalOperatorExpression, // OK (once block braces are fixed) + ConditionalOperatorExpression, // OK ConstructorDefinition, // OK ConstructorModifierStyle, // OK - ContractDefinition, // OKish (FIX: fn param alignement) - DocComments, // OK (once block braces are fixed). TODO: Ask dani how to wrap comments. - DoWhileStatement, // OK? (once block braces are fixed).. is it acceptable? + ContractDefinition, // OK? Is it acceptable? + DocComments, // OK (basics). TODO: wrapp comments + DoWhileStatement, // OK? TODO: try to improve nested conditional expressions EmitStatement, // OK EnumDefinition, // OK EnumVariants, // OK ErrorDefinition, // OK - EventDefinition, // OK? i think it works as intended.. is it acceptable? - ForStatement, // OK? (once block braces are fixed).. is it acceptable? - FunctionCall, // TODO: ask Dani how to enhance PP so that trailing comments aren't accounted for when breaking lines - FunctionCallArgsStatement, // FIX: fn block braces. Is the rest acceptable? - FunctionDefinition, // TODO: fix fn block braces + prevent oneParam fn from breaking when attributes break - FunctionDefinitionWithFunctionReturns, // OK? (once block braces are fixed) is it acceptable? - FunctionType, // OK? (once block braces are fixed) is it acceptable? - HexUnderscore, // OK (once block braces are fixed) + EventDefinition, // OK + ForStatement, // OK? TODO: try to improve nested conditional expressions + FunctionCall, // OK: TODO: enhance PP so that trailing comments aren't accounted for when breaking lines after a simcolon. + FunctionCallArgsStatement, // OK? Is it acceptable? + FunctionDefinition, // OK? Is it acceptable? + FunctionDefinitionWithFunctionReturns, // OK + FunctionType, // OK? is it acceptable? + HexUnderscore, // OK IfStatement, // TODO: figure out correct comment placements + condition parenthesis wrapping + support config line wrapping - IfStatement2, // OK (once block braces are fixed) + IfStatement2, // OK ImportDirective, // OK InlineDisable, // FIX: invalid output - IntTypes, // OK (once block braces are fixed) + IntTypes, // OK LiteralExpression, // FIX: idempotency (comment-related) - MappingTyple, // TODO: comments + MappingType, // TODO: comments ModifierDefinition, // OK NamedFunctionCallExpression, // FIX: idempotency (comment-related) - NumberLiteralUnderscore, // OK (once block braces are fixed) - OperatorExpressions, // FIX: idempotency (comment-related) + NumberLiteralUnderscore, // OK + OperatorExpressions, // OKish: TODO: improve nested conditional expressions PragmaDirective, // OK Repros, // TODO: check boxes, panics ReturnStatement, // FIX: idempotency (comment-related) @@ -191,17 +191,17 @@ fmt_tests! { RevertStatement, // FIX: idempotency (comment-related) SimpleComments, // FIX: idempotency (comment-related) SortedImports, // FIX: sorting order - StatementBlock, // OK (once block braces are fixed) + StatementBlock, // OK StructDefinition, // OK ThisExpression, // OK TrailingComma, // OK (solar error) - TryStatement, // OK but kinda hacky. Is the fmt for the new test case acceptable? + TryStatement, // OK? is it acceptable? TypeDefinition, // OK UnitExpression, // FIX: idempotency (comment-related) UsingDirective, // OK VariableAssignment, // FIX: variable assignment VariableDefinition, // FIX: variable assignment + declaration - WhileStatement, // FIX: indentation of the condition + WhileStatement, // OKish: TODO: improve nested conditional expressions Yul, // FIX: idemptency (comment-related) - YulStrings, // OK (once block braces are fixed) + YulStrings, // OK } From 16aa8840506c9bc692466c5b86eb4a19c732584c Mon Sep 17 00:00:00 2001 From: 0xrusowsky <0xrusowsky@proton.me> Date: Sun, 6 Jul 2025 09:48:46 +0200 Subject: [PATCH 25/31] bump solar --- Cargo.lock | 42 ++++---- crates/fmt-2/src/state.rs | 196 +++++++++++++++++++++++++++++++------- 2 files changed, 182 insertions(+), 56 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 02484e96f6547..3e481646bdb40 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2793,7 +2793,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "117725a109d387c937a1533ce01b450cbde6b88abceea8473c4d7a85853cda3c" dependencies = [ "lazy_static", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -3596,7 +3596,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cea14ef9355e3beab063703aa9dab15afd25f0667c341310c1e5274bb1d0da18" dependencies = [ "libc", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -3705,7 +3705,7 @@ checksum = "0ce92ff622d6dadf7349484f42c93271a0d49b7cc4d466a936405bacbe10aa78" dependencies = [ "cfg-if", "rustix 1.0.7", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -5505,7 +5505,7 @@ checksum = "e04d7f318608d35d4b61ddd75cbdaee86b023ebe2bd5a66ee0915f0bf93095a9" dependencies = [ "hermit-abi", "libc", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -5568,7 +5568,7 @@ dependencies = [ "portable-atomic", "portable-atomic-util", "serde", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -7278,7 +7278,7 @@ dependencies = [ "once_cell", "socket2", "tracing", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -7955,7 +7955,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys 0.4.15", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -7968,7 +7968,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys 0.9.4", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -8613,7 +8613,7 @@ dependencies = [ [[package]] name = "solar-ast" version = "0.1.4" -source = "git+https://github.com/paradigmxyz/solar?branch=main#48d1d85dfbdaf27dc75f7844ff52c3a4a062b70b" +source = "git+https://github.com/paradigmxyz/solar?branch=main#5c12ea15fae1eabbf1a06eaafd3181107aeb187c" dependencies = [ "alloy-primitives", "bumpalo", @@ -8631,7 +8631,7 @@ dependencies = [ [[package]] name = "solar-config" version = "0.1.4" -source = "git+https://github.com/paradigmxyz/solar?branch=main#48d1d85dfbdaf27dc75f7844ff52c3a4a062b70b" +source = "git+https://github.com/paradigmxyz/solar?branch=main#5c12ea15fae1eabbf1a06eaafd3181107aeb187c" dependencies = [ "strum 0.27.1", ] @@ -8639,7 +8639,7 @@ dependencies = [ [[package]] name = "solar-data-structures" version = "0.1.4" -source = "git+https://github.com/paradigmxyz/solar?branch=main#48d1d85dfbdaf27dc75f7844ff52c3a4a062b70b" +source = "git+https://github.com/paradigmxyz/solar?branch=main#5c12ea15fae1eabbf1a06eaafd3181107aeb187c" dependencies = [ "bumpalo", "index_vec", @@ -8653,7 +8653,7 @@ dependencies = [ [[package]] name = "solar-interface" version = "0.1.4" -source = "git+https://github.com/paradigmxyz/solar?branch=main#48d1d85dfbdaf27dc75f7844ff52c3a4a062b70b" +source = "git+https://github.com/paradigmxyz/solar?branch=main#5c12ea15fae1eabbf1a06eaafd3181107aeb187c" dependencies = [ "annotate-snippets", "anstream", @@ -8663,7 +8663,7 @@ dependencies = [ "derive_more 2.0.1", "dunce", "inturn", - "itertools 0.10.5", + "itertools 0.12.1", "itoa", "match_cfg", "normalize-path", @@ -8674,7 +8674,7 @@ dependencies = [ "solar-config", "solar-data-structures", "solar-macros", - "thiserror 1.0.69", + "thiserror 2.0.12", "tracing", "unicode-width 0.2.0", ] @@ -8682,7 +8682,7 @@ dependencies = [ [[package]] name = "solar-macros" version = "0.1.4" -source = "git+https://github.com/paradigmxyz/solar?branch=main#48d1d85dfbdaf27dc75f7844ff52c3a4a062b70b" +source = "git+https://github.com/paradigmxyz/solar?branch=main#5c12ea15fae1eabbf1a06eaafd3181107aeb187c" dependencies = [ "proc-macro2", "quote", @@ -8692,12 +8692,12 @@ dependencies = [ [[package]] name = "solar-parse" version = "0.1.4" -source = "git+https://github.com/paradigmxyz/solar?branch=main#48d1d85dfbdaf27dc75f7844ff52c3a4a062b70b" +source = "git+https://github.com/paradigmxyz/solar?branch=main#5c12ea15fae1eabbf1a06eaafd3181107aeb187c" dependencies = [ "alloy-primitives", "bitflags 2.9.1", "bumpalo", - "itertools 0.10.5", + "itertools 0.12.1", "memchr", "num-bigint", "num-rational", @@ -8712,7 +8712,7 @@ dependencies = [ [[package]] name = "solar-sema" version = "0.1.4" -source = "git+https://github.com/paradigmxyz/solar?branch=main#48d1d85dfbdaf27dc75f7844ff52c3a4a062b70b" +source = "git+https://github.com/paradigmxyz/solar?branch=main#5c12ea15fae1eabbf1a06eaafd3181107aeb187c" dependencies = [ "alloy-json-abi", "alloy-primitives", @@ -9007,7 +9007,7 @@ dependencies = [ "serde_json", "sha2 0.10.9", "tempfile", - "thiserror 1.0.69", + "thiserror 2.0.12", "url", "zip", ] @@ -9104,7 +9104,7 @@ dependencies = [ "getrandom 0.3.3", "once_cell", "rustix 1.0.7", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -10305,7 +10305,7 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" dependencies = [ - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] diff --git a/crates/fmt-2/src/state.rs b/crates/fmt-2/src/state.rs index 86a012f8d00c5..3f3b3540ae218 100644 --- a/crates/fmt-2/src/state.rs +++ b/crates/fmt-2/src/state.rs @@ -26,6 +26,7 @@ pub(super) struct State<'sess, 'ast> { inline_config: InlineConfig, contract: Option<&'ast ast::ItemContract<'ast>>, + stmt_fmt: Option, } impl std::ops::Deref for State<'_, '_> { @@ -60,6 +61,7 @@ impl<'sess> State<'sess, '_> { inline_config, config, contract: None, + stmt_fmt: None, } } @@ -204,6 +206,15 @@ impl<'sess> State<'sess, '_> { self.comments.iter().take_while(|c| c.pos() < pos).find(|c| !c.style.is_blank()) } + fn peek_comment_between<'b>(&'b self, pos_lo: BytePos, pos_hi: BytePos) -> Option<&'b Comment> + where + 'sess: 'b, + { + self.comments + .iter() + .take_while(|c| pos_lo < c.pos() && c.pos() < pos_hi) + .find(|c| !c.style.is_blank()) + } fn next_comment(&mut self) -> Option { self.comments.next() } @@ -270,6 +281,7 @@ impl<'sess> State<'sess, '_> { mut print: P, mut get_span: S, format: ListFormat, + is_binary_expr: bool, ) where P: FnMut(&mut Self, &'a T), S: FnMut(&T) -> Option, @@ -302,7 +314,7 @@ impl<'sess> State<'sess, '_> { // Otherwise, use commasep self.word("("); - self.commasep(values, pos_lo, pos_hi, print, get_span, format); + self.commasep(values, pos_lo, pos_hi, print, get_span, format, is_binary_expr); self.word(")"); } @@ -312,7 +324,15 @@ impl<'sess> State<'sess, '_> { S: FnMut(&T) -> Option, { self.word("["); - self.commasep(values, span.lo(), span.hi(), print, get_span, ListFormat::Consistent(false)); + self.commasep( + values, + span.lo(), + span.hi(), + print, + get_span, + ListFormat::Consistent(false), + false, + ); self.word("]"); } @@ -324,6 +344,7 @@ impl<'sess> State<'sess, '_> { mut print: P, mut get_span: S, format: ListFormat, + is_binary_expr: bool, ) where P: FnMut(&mut Self, &'a T), S: FnMut(&T) -> Option, @@ -335,7 +356,7 @@ impl<'sess> State<'sess, '_> { // TODO(rusowsky): this should be false for expressions, as they have sub-items and break // themselves (see IF, WHILE, DO-WHILE) let is_single_without_cmnts = - values.len() == 1 && self.peek_comment_before(pos_hi).is_none(); + !is_binary_expr && values.len() == 1 && self.peek_comment_before(pos_hi).is_none(); self.s.cbox(self.ind); let mut skip_first_break = is_single_without_cmnts; @@ -736,8 +757,10 @@ impl<'ast> State<'_, 'ast> { self.end(); // Map attributes to their corresponding comments - let (attributes, mut map) = - AttributeCommentMapper::new(header.returns.span.lo()).build(self, header); + let (attributes, mut map) = AttributeCommentMapper::new( + returns.as_ref().map_or(body_span.lo(), |ret| ret.span.lo()), + ) + .build(self, header); // Print fn attributes in correct order self.s.cbox(0); @@ -746,9 +769,11 @@ impl<'ast> State<'_, 'ast> { self.print_fn_attribute(v.span, &mut map, &mut |s| s.word(v.to_str()), is_first); is_first = false; } - if *sm != ast::StateMutability::NonPayable { - self.print_fn_attribute(sm.span, &mut map, &mut |s| s.word(sm.to_str()), is_first); - is_first = false; + if let Some(sm) = sm { + if !matches!(*sm, ast::StateMutability::NonPayable) { + self.print_fn_attribute(sm.span, &mut map, &mut |s| s.word(sm.to_str()), is_first); + is_first = false; + } } if let Some(v) = virtual_ { self.print_fn_attribute(v, &mut map, &mut |s| s.word("virtual"), is_first); @@ -775,18 +800,22 @@ impl<'ast> State<'_, 'ast> { is_first = false; } } - if !returns.is_empty() { - if !self.is_bol_or_only_ind() && !self.last_token_is_space() { - self.space(); + let mut empty_returns = true; + if let Some(ret) = returns { + if !ret.is_empty() { + if !self.is_bol_or_only_ind() && !self.last_token_is_space() { + self.space(); + } + self.word("returns "); + self.print_parameter_list(ret, ret.span, ListFormat::Consistent(false)); + empty_returns = false; } - self.word("returns "); - self.print_parameter_list(returns, returns.span, ListFormat::Consistent(false)); } // Print fn body if let Some(body) = body { if self.peek_comment_before(body_span.lo()).map_or(true, |cmnt| cmnt.style.is_mixed()) { - if attributes.len() == 1 && returns.is_empty() { + if attributes.len() == 1 && empty_returns { self.nbsp(); self.zerobreak(); } else { @@ -926,7 +955,15 @@ impl<'ast> State<'_, 'ast> { span: Span, format: ListFormat, ) { - self.print_tuple(parameters, span.lo(), span.hi(), Self::print_var, get_span!(), format); + self.print_tuple( + parameters, + span.lo(), + span.hi(), + Self::print_var, + get_span!(), + format, + false, + ); } // NOTE(rusowsky): is this needed? @@ -1194,14 +1231,18 @@ impl<'ast> State<'_, 'ast> { self.word(v.to_str()); self.nbsp(); } - if !matches!(**state_mutability, ast::StateMutability::NonPayable) { - self.word(state_mutability.to_str()); - self.nbsp(); + if let Some(sm) = state_mutability { + if !matches!(**sm, ast::StateMutability::NonPayable) { + self.word(sm.to_str()); + self.nbsp(); + } } - if !returns.is_empty() { - self.word("returns"); - self.nbsp(); - self.print_parameter_list(returns, returns.span, ListFormat::Consistent(false)); + if let Some(ret) = returns { + if !ret.is_empty() { + self.word("returns"); + self.nbsp(); + self.print_parameter_list(ret, ret.span, ListFormat::Consistent(false)); + } } self.end(); } @@ -1241,6 +1282,7 @@ impl<'ast> State<'_, 'ast> { |this, path| this.print_path(path), get_span!(()), ListFormat::Consistent(false), + false, ); } } @@ -1267,7 +1309,7 @@ impl<'ast> State<'_, 'ast> { } ast::ExprKind::Assign(lhs, Some(bin_op), rhs) | ast::ExprKind::Binary(lhs, bin_op, rhs) => { - self.s.ibox(self.ind); + self.s.cbox(self.ind); self.s.ibox(-self.ind); self.print_expr(lhs); self.end(); @@ -1427,6 +1469,7 @@ impl<'ast> State<'_, 'ast> { }, |e| e.as_deref().map(|e| e.span), ListFormat::Consistent(false), + is_binary_expr(&expr.kind), ), ast::ExprKind::TypeCall(ty) => { self.word("type"); @@ -1437,6 +1480,7 @@ impl<'ast> State<'_, 'ast> { Self::print_ty, get_span!(), ListFormat::Consistent(false), + false, ); } ast::ExprKind::Type(ty) => self.print_ty(ty), @@ -1483,6 +1527,7 @@ impl<'ast> State<'_, 'ast> { |this, e| this.print_expr(e), get_span!(), ListFormat::Compact(true), + false, ); } ast::CallArgsKind::Named(named_args) => { @@ -1550,6 +1595,7 @@ impl<'ast> State<'_, 'ast> { Self::print_ast_str_lit, get_span!(), ListFormat::Consistent(false), + false, ); } self.print_yul_block(block, span, false); @@ -1567,6 +1613,7 @@ impl<'ast> State<'_, 'ast> { }, |v| v.as_ref().map(|v| v.span), ListFormat::Consistent(false), + false, ); self.word(" = "); self.neverbreak(); @@ -1618,7 +1665,8 @@ impl<'ast> State<'_, 'ast> { ast::StmtKind::If(cond, then, els_opt) => { self.cbox(0); self.ibox(0); - self.print_if_no_else(cond, then); + let (fmt, cached) = self.get_block_format(then, els_opt); + self.print_if_no_else(cond, then, fmt); let mut els_opt = els_opt.as_deref(); while let Some(els) = els_opt { self.print_comments_skip_ws(els.span.lo()); @@ -1630,15 +1678,19 @@ impl<'ast> State<'_, 'ast> { self.ibox(0); self.word("else "); if let ast::StmtKind::If(cond, then, els) = &els.kind { - self.print_if_no_else(cond, then); + self.print_if_no_else(cond, then, fmt); els_opt = els.as_deref(); continue; } else { - self.print_stmt_as_block(els, BlockFormat::Compact(true)); + self.print_stmt_as_block(els, fmt); + self.end(); } break; } self.end(); + if !cached { + self.stmt_fmt = None; + } } ast::StmtKind::Return(expr) => { self.word("return"); @@ -1719,7 +1771,11 @@ impl<'ast> State<'_, 'ast> { self.print_if_cond("while", cond, stmt.span.lo()); self.nbsp(); self.end(); - self.print_stmt_as_block(stmt, BlockFormat::Compact(true)); + let (fmt, cached) = self.get_block_format(stmt, &None); + self.print_stmt_as_block(stmt, fmt); + if !cached { + self.stmt_fmt = None; + } } ast::StmtKind::Placeholder => self.word("_"), } @@ -1727,14 +1783,21 @@ impl<'ast> State<'_, 'ast> { self.word(";"); } self.print_comments(stmt.span.hi()); - self.print_trailing_comment(stmt.span.hi(), None); } - fn print_if_no_else(&mut self, cond: &'ast ast::Expr<'ast>, then: &'ast ast::Stmt<'ast>) { + fn print_if_no_else( + &mut self, + cond: &'ast ast::Expr<'ast>, + then: &'ast ast::Stmt<'ast>, + fmt: BlockFormat, + ) { + // NOTE(rusowsky): unless we add bracket spans to solar, using `the.span.lo()` consumes + // "cmnt12" of the IfStatement test inside the preceeding clause self.print_if_cond("if", cond, then.span.lo()); + // self.print_if_cond("if", cond, cond.span.hi()); self.nbsp(); self.end(); - self.print_stmt_as_block(then, BlockFormat::Compact(true)); + self.print_stmt_as_block(then, fmt); } fn print_if_cond(&mut self, kw: &'static str, cond: &'ast ast::Expr<'ast>, pos_hi: BytePos) { @@ -1747,6 +1810,7 @@ impl<'ast> State<'_, 'ast> { Self::print_expr, get_span!(), ListFormat::Compact(true), + is_binary_expr(&cond.kind), ); } @@ -1902,7 +1966,11 @@ impl<'ast> State<'_, 'ast> { } for stmt in block { print(self, stmt); - self.hardbreak_if_not_bol(); + println!("last is hardbreak? {:?}", self.last_token_is_hardbreak()); + println!(" > {:?}", self.last_token()); + if !self.print_trailing_comment(get_block_span(stmt).hi(), None) { + self.hardbreak_if_not_bol(); + } } self.print_cmnts_with_space_skip_ws( block.last().map_or(span.hi(), |b| get_block_span(b).hi()), @@ -1948,6 +2016,7 @@ impl<'ast> State<'_, 'ast> { |this, path| this.print_path(path), get_span!(()), ListFormat::Consistent(false), + false, ); self.word(" := "); self.neverbreak(); @@ -2015,6 +2084,7 @@ impl<'ast> State<'_, 'ast> { Self::print_ident, get_span!(), ListFormat::Consistent(false), + false, ); self.nbsp(); if !returns.is_empty() { @@ -2026,6 +2096,7 @@ impl<'ast> State<'_, 'ast> { Self::print_ident, get_span!(), ListFormat::Consistent(false), + false, ); self.nbsp(); } @@ -2043,6 +2114,7 @@ impl<'ast> State<'_, 'ast> { Self::print_ident, get_span!(), ListFormat::Consistent(false), + false, ); if let Some(expr) = expr { self.word(" := "); @@ -2082,6 +2154,7 @@ impl<'ast> State<'_, 'ast> { Self::print_yul_expr, get_span!(), ListFormat::Consistent(false), + false, ); } @@ -2148,6 +2221,55 @@ impl<'ast> State<'_, 'ast> { } } } + + fn get_block_format( + &mut self, + then: &'ast ast::Stmt<'ast>, + els_opt: &'ast Option<&'ast mut ast::Stmt<'ast>>, + ) -> (BlockFormat, bool) { + if let Some(fmt) = self.stmt_fmt { + return (fmt, true); + } + + let fmt = if self.break_if_block(then) || + els_opt.as_ref().is_some_and(|els| self.break_if_block(els)) + { + BlockFormat::Regular + } else { + BlockFormat::Compact(true) + }; + + self.stmt_fmt = Some(fmt); + (fmt, false) + } + + fn break_if_block(&self, stmt: &'ast ast::Stmt<'ast>) -> bool { + if matches!(self.config.single_line_statement_blocks, config::SingleLineBlockStyle::Multi) { + return true; + } + + let range = stmt.span.to_range(); + if range.end - range.start > self.config.line_length { + return true; + } + + if let ast::StmtKind::Block(stmts) = &stmt.kind { + if stmts.len() > 1 || + self.peek_comment_between(stmt.span.lo(), stmt.span.hi()).is_some() + { + return true; + } + } + + match &stmt.kind { + ast::StmtKind::If(..) | + ast::StmtKind::For { .. } | + ast::StmtKind::While(..) | + ast::StmtKind::DoWhile(..) | + ast::StmtKind::UncheckedBlock(..) => true, + _ => false, + } + } } // -- HELPERS ----------------------------------------------------------------- @@ -2297,10 +2419,10 @@ impl<'ast> AttributeCommentMapper<'ast> { self.attributes .push(AttributeInfo { kind: AttributeKind::Visibility(*v), span: v.span }); } - self.attributes.push(AttributeInfo { - kind: AttributeKind::StateMutability(*header.state_mutability), - span: header.state_mutability.span, - }); + if let Some(sm) = header.state_mutability { + self.attributes + .push(AttributeInfo { kind: AttributeKind::StateMutability(*sm), span: sm.span }); + } if let Some(v) = header.virtual_ { self.attributes.push(AttributeInfo { kind: AttributeKind::Virtual, span: v }); } @@ -2363,6 +2485,10 @@ fn stmt_needs_semi<'ast>(stmt: &'ast ast::StmtKind<'ast>) -> bool { } } +fn is_binary_expr<'ast>(expr_kind: &'ast ast::ExprKind<'ast>) -> bool { + matches!(expr_kind, ast::ExprKind::Binary(..)) +} + pub enum Skip { First, All, From a7382ef67e1b8c0ddb363e839afa1659ba14a268 Mon Sep 17 00:00:00 2001 From: 0xrusowsky <0xrusowsky@proton.me> Date: Fri, 11 Jul 2025 13:09:16 +0200 Subject: [PATCH 26/31] inline if statements based on user config --- crates/fmt-2/src/pp/convenience.rs | 15 ++ crates/fmt-2/src/pp/helpers.rs | 2 +- crates/fmt-2/src/pp/mod.rs | 6 +- crates/fmt-2/src/state.rs | 380 ++++++++++++++++++++--------- crates/fmt-2/tests/formatter.rs | 2 +- 5 files changed, 289 insertions(+), 116 deletions(-) diff --git a/crates/fmt-2/src/pp/convenience.rs b/crates/fmt-2/src/pp/convenience.rs index c364ee1c1d19c..c1b6c0a382b80 100644 --- a/crates/fmt-2/src/pp/convenience.rs +++ b/crates/fmt-2/src/pp/convenience.rs @@ -62,6 +62,13 @@ impl Printer { false } + pub fn last_token_is_break(&self) -> bool { + if let Some(token) = self.last_token() { + return matches!(token, Token::Break(_)); + } + false + } + pub fn last_token_is_hardbreak(&self) -> bool { if let Some(token) = self.last_token() { return token.is_hardbreak(); @@ -168,6 +175,14 @@ impl Printer { pub fn neverbreak(&mut self) { self.scan_break(BreakToken { never_break: true, ..BreakToken::default() }); } + + pub fn last_brace_is_closed(&self, kw: &str) -> bool { + self.out.rsplit_once(kw).map_or(true, |(_, relevant)| { + let open = relevant.chars().filter(|c| *c == '{').count(); + let close = relevant.chars().filter(|c| *c == '}').count(); + open == close + }) + } } impl Token { diff --git a/crates/fmt-2/src/pp/helpers.rs b/crates/fmt-2/src/pp/helpers.rs index 983d22f097c85..2ff3b4c510239 100644 --- a/crates/fmt-2/src/pp/helpers.rs +++ b/crates/fmt-2/src/pp/helpers.rs @@ -7,7 +7,7 @@ impl Printer { self.space(); } - /// Adds a new hardbrak if not at the beginning of the line. + /// Adds a new hardbreak if not at the beginning of the line. /// If there was a buffered break token, replaces it (ensures hardbreak) keeping the offset. pub fn hardbreak_if_not_bol(&mut self) { if !self.is_bol_or_only_ind() { diff --git a/crates/fmt-2/src/pp/mod.rs b/crates/fmt-2/src/pp/mod.rs index b43929d2f1194..aa78ed58558c0 100644 --- a/crates/fmt-2/src/pp/mod.rs +++ b/crates/fmt-2/src/pp/mod.rs @@ -70,7 +70,7 @@ enum PrintFrame { Broken(usize, Breaks), } -const SIZE_INFINITY: isize = 0xffff; +pub(crate) const SIZE_INFINITY: isize = 0xffff; #[derive(Debug)] pub struct Printer { @@ -129,8 +129,8 @@ impl Printer { } } - pub(crate) fn current_indent(&self) -> usize { - self.indent + pub(crate) fn space_left(&self) -> isize { + self.space } pub(crate) fn last_token(&self) -> Option<&Token> { diff --git a/crates/fmt-2/src/state.rs b/crates/fmt-2/src/state.rs index 3f3b3540ae218..73aa1dbc1d57b 100644 --- a/crates/fmt-2/src/state.rs +++ b/crates/fmt-2/src/state.rs @@ -26,7 +26,7 @@ pub(super) struct State<'sess, 'ast> { inline_config: InlineConfig, contract: Option<&'ast ast::ItemContract<'ast>>, - stmt_fmt: Option, + single_line_stmt: Option, } impl std::ops::Deref for State<'_, '_> { @@ -61,7 +61,7 @@ impl<'sess> State<'sess, '_> { inline_config, config, contract: None, - stmt_fmt: None, + single_line_stmt: None, } } @@ -239,6 +239,29 @@ impl<'sess> State<'sess, '_> { false } + fn print_trailing_comment_no_break(&mut self, span_pos: BytePos, next_pos: Option) { + if let Some(mut cmnt) = self.comments.trailing_comment(self.sm, span_pos, next_pos) { + if !self.is_bol_or_only_ind() { + self.nbsp(); + } + + if cmnt.lines.len() == 1 { + self.word(cmnt.lines.pop().unwrap()); + } else { + self.visual_align(); + for (pos, line) in cmnt.lines.into_iter().delimited() { + if !line.is_empty() { + self.word(line); + } + if !pos.is_last { + self.hardbreak(); + } + } + self.end(); + } + } + } + fn print_remaining_comments(&mut self) { // If there aren't any remaining comments, then we need to manually // make sure there is a line break at the end. @@ -390,7 +413,6 @@ impl<'sess> State<'sess, '_> { self.hardbreak(); // trailing and isolated comments already hardbreak } } - self.s.ibox(0); print(self, value); if !is_last { @@ -411,7 +433,6 @@ impl<'sess> State<'sess, '_> { self.break_offset_if_not_bol(0, -self.ind); skip_last_break = true; } - self.end(); if !is_last && !self.is_bol_or_only_ind() { self.space(); } @@ -832,7 +853,7 @@ impl<'ast> State<'_, 'ast> { self.word("{"); self.end(); self.end(); - self.print_block_without_braces(body, body_span, self.ind); + self.print_block_without_braces(body, body_span, Some(self.ind)); self.word("}"); } else { self.print_cmnts_with_space_skip_ws(body_span.lo()); @@ -1300,6 +1321,7 @@ impl<'ast> State<'_, 'ast> { self.print_array(exprs, expr.span, |this, e| this.print_expr(e), get_span!()) } ast::ExprKind::Assign(lhs, None, rhs) => { + self.hardbreak(); self.ibox(0); self.print_expr(lhs); self.word(" = "); @@ -1425,13 +1447,13 @@ impl<'ast> State<'_, 'ast> { self.s.cbox(self.ind); // conditional expression self.s.ibox(0); - self.print_comments(cond.span.lo()); + self.print_comments_skip_ws(cond.span.lo()); self.print_expr(cond); let cmnt = self.peek_comment_before(then.span.lo()); if cmnt.is_some() { self.space(); } - self.print_comments(then.span.lo()); + self.print_comments_skip_ws(then.span.lo()); self.end(); if !self.is_bol_or_only_ind() { self.space(); @@ -1444,7 +1466,7 @@ impl<'ast> State<'_, 'ast> { if cmnt.is_some() { self.space(); } - self.print_comments(els.span.lo()); + self.print_comments_skip_ws(els.span.lo()); self.end(); if !self.is_bol_or_only_ind() { self.space(); @@ -1624,7 +1646,7 @@ impl<'ast> State<'_, 'ast> { ast::StmtKind::Continue => self.word("continue"), ast::StmtKind::DoWhile(stmt, cond) => { self.word("do "); - self.print_stmt_as_block(stmt, BlockFormat::Regular); + self.print_stmt_as_block(stmt, false); self.nbsp(); self.print_if_cond("while", cond, cond.span.hi()); } @@ -1659,37 +1681,53 @@ impl<'ast> State<'_, 'ast> { self.word(") "); self.neverbreak(); self.end(); - self.print_stmt_as_block(body, BlockFormat::Regular); + self.print_stmt_as_block(body, false); self.end(); } ast::StmtKind::If(cond, then, els_opt) => { + // Check if blocks should be inlined and update cache if necessary + let (inline, cached) = self.is_single_line_block(cond, then, els_opt.as_ref()); + if !cached && self.single_line_stmt.is_none() { + self.single_line_stmt = Some(inline); + } + self.cbox(0); self.ibox(0); - let (fmt, cached) = self.get_block_format(then, els_opt); - self.print_if_no_else(cond, then, fmt); + // Print if stmt + self.print_if_no_else(cond, then, inline); + // Print else (if) stmts, if any let mut els_opt = els_opt.as_deref(); while let Some(els) = els_opt { - self.print_comments_skip_ws(els.span.lo()); if self.ends_with('}') { - self.nbsp(); + match self.print_comments_skip_ws(els.span.lo()) { + Some(cmnt) => { + if cmnt.is_mixed() { + self.hardbreak() + } + } + None => self.nbsp(), + } } else { self.hardbreak_if_not_bol(); + self.print_comments_skip_ws(els.span.lo()); } self.ibox(0); self.word("else "); if let ast::StmtKind::If(cond, then, els) = &els.kind { - self.print_if_no_else(cond, then, fmt); + self.print_if_no_else(cond, then, inline); els_opt = els.as_deref(); continue; } else { - self.print_stmt_as_block(els, fmt); + self.print_stmt_as_block(els, inline); self.end(); } break; } self.end(); - if !cached { - self.stmt_fmt = None; + + // Clear cache if necessary + if !cached && self.single_line_stmt.is_some() { + self.single_line_stmt = None; } } ast::StmtKind::Return(expr) => { @@ -1707,7 +1745,7 @@ impl<'ast> State<'_, 'ast> { let ast::TryCatchClause { args, block, span: try_span, .. } = first; self.ibox(0); self.word("try "); - self.print_comments(expr.span.lo()); + self.print_comments_skip_ws(expr.span.lo()); self.print_expr(expr); self.print_comments_skip_ws( args.first().map(|p| p.span.lo()).unwrap_or_else(|| expr.span.lo()), @@ -1743,7 +1781,7 @@ impl<'ast> State<'_, 'ast> { } self.s.cbox(self.ind); self.neverbreak(); - self.print_comments(catch_span.lo()); + self.print_comments_skip_ws(catch_span.lo()); self.word("catch "); self.neverbreak(); if !args.is_empty() { @@ -1767,42 +1805,35 @@ impl<'ast> State<'_, 'ast> { self.print_block(block, stmt.span); } ast::StmtKind::While(cond, stmt) => { - self.ibox(0); self.print_if_cond("while", cond, stmt.span.lo()); self.nbsp(); - self.end(); - let (fmt, cached) = self.get_block_format(stmt, &None); - self.print_stmt_as_block(stmt, fmt); - if !cached { - self.stmt_fmt = None; - } + self.print_stmt_as_block(stmt, self.is_inline_stmt(stmt, 6)); } ast::StmtKind::Placeholder => self.word("_"), } if stmt_needs_semi(kind) { self.word(";"); } - self.print_comments(stmt.span.hi()); + self.print_comments_skip_ws(stmt.span.hi()); } fn print_if_no_else( &mut self, cond: &'ast ast::Expr<'ast>, then: &'ast ast::Stmt<'ast>, - fmt: BlockFormat, + inline: bool, ) { // NOTE(rusowsky): unless we add bracket spans to solar, using `the.span.lo()` consumes // "cmnt12" of the IfStatement test inside the preceeding clause - self.print_if_cond("if", cond, then.span.lo()); // self.print_if_cond("if", cond, cond.span.hi()); - self.nbsp(); + self.print_if_cond("if", cond, then.span.lo()); + self.space(); self.end(); - self.print_stmt_as_block(then, fmt); + self.print_stmt_as_block(then, inline); } fn print_if_cond(&mut self, kw: &'static str, cond: &'ast ast::Expr<'ast>, pos_hi: BytePos) { self.word_nbsp(kw); - // TODO(ask dani): prevent exprs from always breaking the brackets (i.e. While test) self.print_tuple( std::slice::from_ref(cond), cond.span.lo(), @@ -1833,7 +1864,7 @@ impl<'ast> State<'_, 'ast> { &mut self, block: &'ast [ast::Stmt<'ast>], span: Span, - offset: isize, + offset: Option, ) { self.print_block_inner( block, @@ -1845,13 +1876,21 @@ impl<'ast> State<'_, 'ast> { } // Body of a if/loop. - fn print_stmt_as_block(&mut self, stmt: &'ast ast::Stmt<'ast>, format: BlockFormat) { + fn print_stmt_as_block(&mut self, stmt: &'ast ast::Stmt<'ast>, inline: bool) { let stmts = if let ast::StmtKind::Block(stmts) = &stmt.kind { stmts } else { std::slice::from_ref(stmt) }; - self.print_block_inner(stmts, format, Self::print_stmt, |b| b.span, stmt.span); + + if inline && !stmts.is_empty() { + self.neverbreak(); + self.print_block_without_braces(stmts, stmt.span, None); + } else { + self.word("{"); + self.print_block_without_braces(stmts, stmt.span, Some(self.ind)); + self.word("}"); + } } fn print_yul_block( @@ -1878,7 +1917,7 @@ impl<'ast> State<'_, 'ast> { span: Span, ) { // Attempt to print in a single line - if block_format.attempt_single_line() && block.len() == 1 && self.single_line_block(span) { + if block_format.attempt_single_line() && block.len() == 1 { self.s.cbox(self.ind); if matches!(block_format, BlockFormat::Compact(true)) { self.scan_break(BreakToken { pre_break: Some('{'), ..Default::default() }); @@ -1918,9 +1957,10 @@ impl<'ast> State<'_, 'ast> { // Other comments are printed inside the block else { if let BlockFormat::NoBraces(offset) = block_format { - // self.zerobreak(); - // self.s.offset(offset); - self.s.cbox(offset); + match offset { + Some(offset) => self.s.cbox(offset), + None => self.cbox(0), + } } else { self.word("{"); self.s.cbox(self.ind); @@ -1943,51 +1983,203 @@ impl<'ast> State<'_, 'ast> { return; } - if let BlockFormat::NoBraces(offset) = block_format { - if self.peek_comment_before(get_block_span(&block[0]).lo()).is_some() { - self.hardbreak(); - self.break_offset_if_not_bol(0, offset); + match block_format { + BlockFormat::NoBraces(None) => { self.print_comments_skip_ws(get_block_span(&block[0]).lo()); - self.s.offset(offset); - } else { - self.zerobreak(); - self.s.offset(offset); + self.s.cbox(0); } - self.s.cbox(self.ind); - } else { - self.word("{"); - self.s.cbox(self.ind); - if self - .print_comments_skip_ws(get_block_span(&block[0]).lo()) - .map_or(true, |cmnt| cmnt.is_mixed()) - { - self.hardbreak_if_nonempty(); + BlockFormat::NoBraces(offset) => { + if self.peek_comment_before(get_block_span(&block[0]).lo()).is_some() { + self.hardbreak(); + self.break_offset_if_not_bol(0, offset.unwrap()); + self.print_comments_skip_ws(get_block_span(&block[0]).lo()); + } else { + self.zerobreak(); + } + self.s.offset(offset.unwrap()); + self.s.cbox(self.ind); + } + _ => { + self.word("{"); + self.s.cbox(self.ind); + if self + .print_comments_skip_ws(get_block_span(&block[0]).lo()) + .map_or(true, |cmnt| cmnt.is_mixed()) + { + self.hardbreak_if_nonempty(); + } } } for stmt in block { print(self, stmt); - println!("last is hardbreak? {:?}", self.last_token_is_hardbreak()); - println!(" > {:?}", self.last_token()); - if !self.print_trailing_comment(get_block_span(stmt).hi(), None) { + if block_format.breaks() { + self.print_trailing_comment_no_break(get_block_span(stmt).hi(), None); + } else if !self.print_trailing_comment(get_block_span(stmt).hi(), None) { self.hardbreak_if_not_bol(); } } self.print_cmnts_with_space_skip_ws( block.last().map_or(span.hi(), |b| get_block_span(b).hi()), ); - self.s.offset(-self.ind); + if !block_format.breaks() { + if !self.last_token_is_break() { + self.hardbreak(); + } + self.s.offset(-self.ind); + } self.end(); if block_format.with_braces() { self.word("}"); } } - fn single_line_block(&self, span: Span) -> bool { - match self.config.single_line_statement_blocks { - config::SingleLineBlockStyle::Preserve => !self.sm.is_multiline(span), - config::SingleLineBlockStyle::Single => true, - config::SingleLineBlockStyle::Multi => false, + fn is_inline_stmt(&self, stmt: &'ast ast::Stmt<'ast>, cond_len: usize) -> bool { + if let ast::StmtKind::If(cond, then, els_opt) = &stmt.kind { + let if_span = Span::new(cond.span.lo(), then.span.hi()); + if self.sm.is_multiline(if_span) && + matches!( + self.config.single_line_statement_blocks, + config::SingleLineBlockStyle::Preserve + ) + { + return false; + } + if (cond_len + self.estimate_size(if_span)) as isize >= self.space_left() { + return false; + } + if let Some(els) = els_opt { + if !self.is_inline_stmt(els, 6) { + return false; + } + } + } else { + if matches!( + self.config.single_line_statement_blocks, + config::SingleLineBlockStyle::Preserve + ) && self.sm.is_multiline(stmt.span) + { + return false; + } + if (cond_len + self.estimate_size(stmt.span)) as isize >= self.space_left() { + return false; + } + } + true + } + + /// Determines if an `if/else` block should be inlined. + /// Also returns if the value was cached, so that it can be cleaned afterwards. + /// + /// # Returns + /// + /// A tuple `(should_inline, was_cached)`. The second boolean is `true` if the + /// decision was retrieved from the cache or is a final decision based on config, + /// preventing the caller from clearing a cache value that was never set. + fn is_single_line_block( + &mut self, + cond: &'ast ast::Expr<'ast>, + then: &'ast ast::Stmt<'ast>, + els_opt: Option<&'ast &'ast mut ast::Stmt<'ast>>, + ) -> (bool, bool) { + // If a decision is already cached from a parent 'if', use it directly. + if let Some(cached_decision) = self.single_line_stmt { + return (cached_decision, true); + } + + // Decide based on the block style configuration. + // Some style rules can give a definitive answer without estimating length. + let preliminary_decision = match self.config.single_line_statement_blocks { + // 'Multi' style never inlines. + config::SingleLineBlockStyle::Multi => Some((false, true)), + + // 'Preserve' style keeps the original formatting. + config::SingleLineBlockStyle::Preserve => { + if self.is_preserve_multiline_stmt(cond, then) { + Some((false, true)) + } else { + None + } + } + + // 'Single' style allows inlining if the block content is on one line. + config::SingleLineBlockStyle::Single => { + if self.is_multiline_block_content(then) { + Some((false, true)) + } else { + None + } + } + }; + + if let Some(decision) = preliminary_decision { + return decision; + } + + // If no definitive decision was made, estimate the formatted length. + // This is a conservative check; if it fails, we format as a multi-line block. + let can_inline = self.can_stmts_fit_on_one_line(cond, then, els_opt); + self.single_line_stmt = Some(can_inline); + (can_inline, false) + } + + /// Checks if a statement was explicitly written as multi-line in 'Preserve' mode. + fn is_preserve_multiline_stmt( + &self, + cond: &'ast ast::Expr<'ast>, + then: &'ast ast::Stmt<'ast>, + ) -> bool { + // It's explicitly multi-line if it uses braces. + if matches!(then.kind, ast::StmtKind::Block(_)) { + return true; + } + + // Or if there's a newline between the `if cond` and the `then` statement. + let span_between = cond.span.between(then.span); + if let Ok(snip) = self.sm.span_to_snippet(span_between) { + // Check for newlines after the closing parenthesis of the `if (...)`. + if let Some((_, after_paren)) = snip.split_once(')') { + return after_paren.lines().count() > 1; + } + } + + false + } + + /// Checks if a block statement `{ ... }` contains more than one line of actual code. + fn is_multiline_block_content(&self, stmt: &'ast ast::Stmt<'ast>) -> bool { + if matches!(stmt.kind, ast::StmtKind::Block(_)) && self.sm.is_multiline(stmt.span) { + if let Ok(snip) = self.sm.span_to_snippet(stmt.span) { + let code_lines = snip.lines().filter(|line| { + let trimmed = line.trim(); + // Ignore empty lines and lines with only '{' or '}' + !trimmed.is_empty() && trimmed != "{" && trimmed != "}" + }); + return code_lines.count() > 1; + } } + false + } + + /// Performs a size estimation to see if the if/else can fit on one line. + fn can_stmts_fit_on_one_line( + &mut self, + cond: &'ast ast::Expr<'ast>, + then: &'ast ast::Stmt<'ast>, + els_opt: Option<&'ast &'ast mut ast::Stmt<'ast>>, + ) -> bool { + let cond_len = self.estimate_size(cond.span); + + // If the condition fits in one line, 6 chars: 'if (' + {cond} + ') ' + {then} + // Otherwise chars: ') ' + {then} + let then_margin = + if (6 + cond_len as isize) < self.space_left() { 6 + cond_len } else { 2 }; + + if !self.is_inline_stmt(then, then_margin) { + return false; + } + + // Always 6 chars for the else: 'else ' + els_opt.map_or(true, |els| self.is_inline_stmt(els, 6)) } } @@ -2222,53 +2414,16 @@ impl<'ast> State<'_, 'ast> { } } - fn get_block_format( - &mut self, - then: &'ast ast::Stmt<'ast>, - els_opt: &'ast Option<&'ast mut ast::Stmt<'ast>>, - ) -> (BlockFormat, bool) { - if let Some(fmt) = self.stmt_fmt { - return (fmt, true); - } - - let fmt = if self.break_if_block(then) || - els_opt.as_ref().is_some_and(|els| self.break_if_block(els)) - { - BlockFormat::Regular - } else { - BlockFormat::Compact(true) - }; - - self.stmt_fmt = Some(fmt); - (fmt, false) - } - - fn break_if_block(&self, stmt: &'ast ast::Stmt<'ast>) -> bool { - if matches!(self.config.single_line_statement_blocks, config::SingleLineBlockStyle::Multi) { - return true; - } - - let range = stmt.span.to_range(); - if range.end - range.start > self.config.line_length { - return true; - } - - if let ast::StmtKind::Block(stmts) = &stmt.kind { - if stmts.len() > 1 || - self.peek_comment_between(stmt.span.lo(), stmt.span.hi()).is_some() - { - return true; + fn estimate_size(&self, span: Span) -> usize { + if let Ok(snip) = self.sm.span_to_snippet(span) { + let mut size = 0; + for line in snip.lines() { + size += line.trim().len(); } + return size; } - match &stmt.kind { - ast::StmtKind::If(..) | - ast::StmtKind::For { .. } | - ast::StmtKind::While(..) | - ast::StmtKind::DoWhile(..) | - ast::StmtKind::UncheckedBlock(..) => true, - _ => false, - } + span.hi().to_usize() - span.lo().to_usize() } } @@ -2311,13 +2466,16 @@ pub(crate) enum BlockFormat { Compact(bool), /// Doesn't print braces. Flags the offset that should be applied before opening the block box. /// Usefull when the caller needs to manually handle the braces. - NoBraces(isize), + NoBraces(Option), } impl BlockFormat { pub(crate) fn with_braces(&self) -> bool { !matches!(self, Self::NoBraces(_)) } + pub(crate) fn breaks(&self) -> bool { + matches!(self, Self::NoBraces(None)) + } pub(crate) fn attempt_single_line(&self) -> bool { matches!(self, Self::Compact(_)) diff --git a/crates/fmt-2/tests/formatter.rs b/crates/fmt-2/tests/formatter.rs index 8b7fccdd9e640..9df1d742b4129 100644 --- a/crates/fmt-2/tests/formatter.rs +++ b/crates/fmt-2/tests/formatter.rs @@ -173,7 +173,7 @@ fmt_tests! { FunctionDefinitionWithFunctionReturns, // OK FunctionType, // OK? is it acceptable? HexUnderscore, // OK - IfStatement, // TODO: figure out correct comment placements + condition parenthesis wrapping + support config line wrapping + IfStatement, // Ok IfStatement2, // OK ImportDirective, // OK InlineDisable, // FIX: invalid output From 3162a8971bfb13cfd8b1bee648a62a551c202947 Mon Sep 17 00:00:00 2001 From: 0xrusowsky <0xrusowsky@proton.me> Date: Sun, 13 Jul 2025 23:05:50 +0200 Subject: [PATCH 27/31] operator expr --- crates/fmt-2/src/state.rs | 204 +++++++++++++++++++------------- crates/fmt-2/tests/formatter.rs | 2 +- 2 files changed, 121 insertions(+), 85 deletions(-) diff --git a/crates/fmt-2/src/state.rs b/crates/fmt-2/src/state.rs index 73aa1dbc1d57b..e56fb96006557 100644 --- a/crates/fmt-2/src/state.rs +++ b/crates/fmt-2/src/state.rs @@ -14,8 +14,6 @@ use solar_parse::{ }; use std::{borrow::Cow, collections::HashMap}; -// TODO(dani): trailing comments should always be passed Some - pub(super) struct State<'sess, 'ast> { pub(crate) s: pp::Printer, ind: isize, @@ -25,8 +23,10 @@ pub(super) struct State<'sess, 'ast> { config: FormatterConfig, inline_config: InlineConfig, + // cache information for proper formatting contract: Option<&'ast ast::ItemContract<'ast>>, single_line_stmt: Option, + assign_expr: bool, } impl std::ops::Deref for State<'_, '_> { @@ -62,6 +62,7 @@ impl<'sess> State<'sess, '_> { config, contract: None, single_line_stmt: None, + assign_expr: false, } } @@ -255,6 +256,8 @@ impl<'sess> State<'sess, '_> { } if !pos.is_last { self.hardbreak(); + } else { + self.zerobreak(); } } self.end(); @@ -376,8 +379,6 @@ impl<'sess> State<'sess, '_> { return; } - // TODO(rusowsky): this should be false for expressions, as they have sub-items and break - // themselves (see IF, WHILE, DO-WHILE) let is_single_without_cmnts = !is_binary_expr && values.len() == 1 && self.peek_comment_before(pos_hi).is_none(); @@ -1331,17 +1332,42 @@ impl<'ast> State<'_, 'ast> { } ast::ExprKind::Assign(lhs, Some(bin_op), rhs) | ast::ExprKind::Binary(lhs, bin_op, rhs) => { - self.s.cbox(self.ind); - self.s.ibox(-self.ind); + let is_parent = matches!(lhs.kind, ast::ExprKind::Binary(..)) || + matches!(rhs.kind, ast::ExprKind::Binary(..)); + if is_parent { + self.s.ibox(self.ind); + } else { + self.ibox(0); + } + self.print_expr(lhs); - self.end(); - self.space(); + if !matches!(kind, ast::ExprKind::Assign(..)) && + self.peek_trailing_comment(rhs.span.hi(), None).is_none() && + self.peek_comment_before(rhs.span.hi()) + .map_or(true, |cmnt| cmnt.style.is_mixed()) + { + self.space(); + } else if !self.is_bol_or_only_ind() { + self.nbsp(); + } + if is_parent { + if has_complex_ancestor(&rhs.kind, false) { + self.s.ibox(0); + } else { + self.s.ibox(-self.ind); + } + } self.word(bin_op.kind.to_str()); if matches!(kind, ast::ExprKind::Assign(..)) { self.word("="); } self.nbsp(); self.print_expr(rhs); + self.print_trailing_comment(rhs.span.hi(), None); + + if is_parent { + self.end(); + } self.end(); } ast::ExprKind::Call(expr, call_args) => { @@ -1686,15 +1712,15 @@ impl<'ast> State<'_, 'ast> { } ast::StmtKind::If(cond, then, els_opt) => { // Check if blocks should be inlined and update cache if necessary - let (inline, cached) = self.is_single_line_block(cond, then, els_opt.as_ref()); - if !cached && self.single_line_stmt.is_none() { - self.single_line_stmt = Some(inline); + let inline = self.is_single_line_block(cond, then, els_opt.as_ref()); + if !inline.is_cached && self.single_line_stmt.is_none() { + self.single_line_stmt = Some(inline.outcome); } self.cbox(0); self.ibox(0); // Print if stmt - self.print_if_no_else(cond, then, inline); + self.print_if_no_else(cond, then, inline.outcome); // Print else (if) stmts, if any let mut els_opt = els_opt.as_deref(); while let Some(els) = els_opt { @@ -1714,11 +1740,11 @@ impl<'ast> State<'_, 'ast> { self.ibox(0); self.word("else "); if let ast::StmtKind::If(cond, then, els) = &els.kind { - self.print_if_no_else(cond, then, inline); + self.print_if_no_else(cond, then, inline.outcome); els_opt = els.as_deref(); continue; } else { - self.print_stmt_as_block(els, inline); + self.print_stmt_as_block(els, inline.outcome); self.end(); } break; @@ -1726,7 +1752,7 @@ impl<'ast> State<'_, 'ast> { self.end(); // Clear cache if necessary - if !cached && self.single_line_stmt.is_some() { + if !inline.is_cached && self.single_line_stmt.is_some() { self.single_line_stmt = None; } } @@ -1805,9 +1831,21 @@ impl<'ast> State<'_, 'ast> { self.print_block(block, stmt.span); } ast::StmtKind::While(cond, stmt) => { + // Check if blocks should be inlined and update cache if necessary + let inline = self.is_single_line_block(cond, stmt, None); + if !inline.is_cached && self.single_line_stmt.is_none() { + self.single_line_stmt = Some(inline.outcome); + } + + // Print while cond and its statement self.print_if_cond("while", cond, stmt.span.lo()); self.nbsp(); - self.print_stmt_as_block(stmt, self.is_inline_stmt(stmt, 6)); + self.print_stmt_as_block(stmt, inline.outcome); + + // Clear cache if necessary + if !inline.is_cached && self.single_line_stmt.is_some() { + self.single_line_stmt = None; + } } ast::StmtKind::Placeholder => self.word("_"), } @@ -2033,6 +2071,47 @@ impl<'ast> State<'_, 'ast> { } } + /// Determines if an `if/else` block should be inlined. + /// Also returns if the value was cached, so that it can be cleaned afterwards. + /// + /// # Returns + /// + /// A tuple `(should_inline, was_cached)`. The second boolean is `true` if the + /// decision was retrieved from the cache or is a final decision based on config, + /// preventing the caller from clearing a cache value that was never set. + fn is_single_line_block( + &mut self, + cond: &'ast ast::Expr<'ast>, + then: &'ast ast::Stmt<'ast>, + els_opt: Option<&'ast &'ast mut ast::Stmt<'ast>>, + ) -> Decision { + // If a decision is already cached from a parent, use it directly. + if let Some(cached_decision) = self.single_line_stmt { + return Decision { outcome: cached_decision, is_cached: true }; + } + + // If possible, take an early decision based on the block style configuration. + match self.config.single_line_statement_blocks { + config::SingleLineBlockStyle::Preserve => { + if self.is_stmt_in_new_line(cond, then) || self.is_multiline_block_stmt(then) { + return Decision { outcome: false, is_cached: true }; + } + } + config::SingleLineBlockStyle::Single => { + if self.is_multiline_block_stmt(then) { + return Decision { outcome: false, is_cached: true }; + } + } + config::SingleLineBlockStyle::Multi => { + return Decision { outcome: false, is_cached: true }; + } + }; + + // If no decision was made, estimate the length to be formatted. + // NOTE: conservative check -> worst-case scenario is formatting as multi-line block. + Decision { outcome: self.can_stmts_fit_on_one_line(cond, then, els_opt), is_cached: false } + } + fn is_inline_stmt(&self, stmt: &'ast ast::Stmt<'ast>, cond_len: usize) -> bool { if let ast::StmtKind::If(cond, then, els_opt) = &stmt.kind { let if_span = Span::new(cond.span.lo(), then.span.hi()); @@ -2067,73 +2146,12 @@ impl<'ast> State<'_, 'ast> { true } - /// Determines if an `if/else` block should be inlined. - /// Also returns if the value was cached, so that it can be cleaned afterwards. - /// - /// # Returns - /// - /// A tuple `(should_inline, was_cached)`. The second boolean is `true` if the - /// decision was retrieved from the cache or is a final decision based on config, - /// preventing the caller from clearing a cache value that was never set. - fn is_single_line_block( - &mut self, - cond: &'ast ast::Expr<'ast>, - then: &'ast ast::Stmt<'ast>, - els_opt: Option<&'ast &'ast mut ast::Stmt<'ast>>, - ) -> (bool, bool) { - // If a decision is already cached from a parent 'if', use it directly. - if let Some(cached_decision) = self.single_line_stmt { - return (cached_decision, true); - } - - // Decide based on the block style configuration. - // Some style rules can give a definitive answer without estimating length. - let preliminary_decision = match self.config.single_line_statement_blocks { - // 'Multi' style never inlines. - config::SingleLineBlockStyle::Multi => Some((false, true)), - - // 'Preserve' style keeps the original formatting. - config::SingleLineBlockStyle::Preserve => { - if self.is_preserve_multiline_stmt(cond, then) { - Some((false, true)) - } else { - None - } - } - - // 'Single' style allows inlining if the block content is on one line. - config::SingleLineBlockStyle::Single => { - if self.is_multiline_block_content(then) { - Some((false, true)) - } else { - None - } - } - }; - - if let Some(decision) = preliminary_decision { - return decision; - } - - // If no definitive decision was made, estimate the formatted length. - // This is a conservative check; if it fails, we format as a multi-line block. - let can_inline = self.can_stmts_fit_on_one_line(cond, then, els_opt); - self.single_line_stmt = Some(can_inline); - (can_inline, false) - } - - /// Checks if a statement was explicitly written as multi-line in 'Preserve' mode. - fn is_preserve_multiline_stmt( + /// Checks if a statement was explicitly written in a new line. + fn is_stmt_in_new_line( &self, cond: &'ast ast::Expr<'ast>, then: &'ast ast::Stmt<'ast>, ) -> bool { - // It's explicitly multi-line if it uses braces. - if matches!(then.kind, ast::StmtKind::Block(_)) { - return true; - } - - // Or if there's a newline between the `if cond` and the `then` statement. let span_between = cond.span.between(then.span); if let Ok(snip) = self.sm.span_to_snippet(span_between) { // Check for newlines after the closing parenthesis of the `if (...)`. @@ -2141,12 +2159,11 @@ impl<'ast> State<'_, 'ast> { return after_paren.lines().count() > 1; } } - false } /// Checks if a block statement `{ ... }` contains more than one line of actual code. - fn is_multiline_block_content(&self, stmt: &'ast ast::Stmt<'ast>) -> bool { + fn is_multiline_block_stmt(&self, stmt: &'ast ast::Stmt<'ast>) -> bool { if matches!(stmt.kind, ast::StmtKind::Block(_)) && self.sm.is_multiline(stmt.span) { if let Ok(snip) = self.sm.span_to_snippet(stmt.span) { let code_lines = snip.lines().filter(|line| { @@ -2643,11 +2660,30 @@ fn stmt_needs_semi<'ast>(stmt: &'ast ast::StmtKind<'ast>) -> bool { } } +pub enum Skip { + First, + All, +} + +pub struct Decision { + outcome: bool, + is_cached: bool, +} + fn is_binary_expr<'ast>(expr_kind: &'ast ast::ExprKind<'ast>) -> bool { matches!(expr_kind, ast::ExprKind::Binary(..)) } -pub enum Skip { - First, - All, +fn has_complex_ancestor<'ast>(expr_kind: &'ast ast::ExprKind<'ast>, left: bool) -> bool { + match expr_kind { + ast::ExprKind::Binary(lhs, _, rhs) => { + if left { + has_complex_ancestor(&lhs.kind, left) + } else { + has_complex_ancestor(&rhs.kind, left) + } + } + ast::ExprKind::Lit(..) | ast::ExprKind::Ident(_) => false, + _ => true, + } } diff --git a/crates/fmt-2/tests/formatter.rs b/crates/fmt-2/tests/formatter.rs index 9df1d742b4129..3f7d497ed0e63 100644 --- a/crates/fmt-2/tests/formatter.rs +++ b/crates/fmt-2/tests/formatter.rs @@ -183,7 +183,7 @@ fmt_tests! { ModifierDefinition, // OK NamedFunctionCallExpression, // FIX: idempotency (comment-related) NumberLiteralUnderscore, // OK - OperatorExpressions, // OKish: TODO: improve nested conditional expressions + OperatorExpressions, // OKish: FIX: empty line after trailing cmnt inside if PragmaDirective, // OK Repros, // TODO: check boxes, panics ReturnStatement, // FIX: idempotency (comment-related) From 5c2e8d63df1726efc74a596586a3b3fbf1e20edf Mon Sep 17 00:00:00 2001 From: 0xrusowsky <0xrusowsky@proton.me> Date: Mon, 14 Jul 2025 19:38:56 +0200 Subject: [PATCH 28/31] finish binary operators + housekeeping --- Cargo.lock | 88 ++++++++++++++++---------- Cargo.toml | 22 +++---- crates/chisel/src/solidity_helper.rs | 2 +- crates/fmt-2/src/pp/mod.rs | 44 ++++++++++++- crates/fmt-2/src/state.rs | 92 +++++++++++++++++----------- crates/fmt-2/tests/formatter.rs | 4 +- 6 files changed, 166 insertions(+), 86 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3e481646bdb40..7715138c6abf2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4068,9 +4068,9 @@ dependencies = [ [[package]] name = "foundry-block-explorers" -version = "0.18.0" +version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7dd57f39a860475780c0b001167b2bb9039e78d8d09323c6949897f5351ffae6" +checksum = "fc107bbc3b4480995fdf337ca0ddedc631728175f418d3136ead9df8f4dc465e" dependencies = [ "alloy-chains", "alloy-json-abi", @@ -4269,8 +4269,9 @@ dependencies = [ [[package]] name = "foundry-compilers" -version = "0.17.3" -source = "git+https://github.com/foundry-rs/compilers.git?branch=rusowsky%2Fbump-solar#c9c43ce1a64710eb22cfdbef0d361703ac3cf0f5" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f163b01cdad921f139776084391db2f1f5fb206ce2395f1847f0c1e992a89a4f" dependencies = [ "alloy-json-abi", "alloy-primitives", @@ -4283,7 +4284,7 @@ dependencies = [ "fs_extra", "futures-util", "home", - "itertools 0.13.0", + "itertools 0.14.0", "path-slash", "rand 0.8.5", "rayon", @@ -4305,8 +4306,9 @@ dependencies = [ [[package]] name = "foundry-compilers-artifacts" -version = "0.17.3" -source = "git+https://github.com/foundry-rs/compilers.git?branch=rusowsky%2Fbump-solar#c9c43ce1a64710eb22cfdbef0d361703ac3cf0f5" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2676d70082ed23680fe2d08c0b750d5f7f2438c6d946f1cb140a76c5e5e0392" dependencies = [ "foundry-compilers-artifacts-solc", "foundry-compilers-artifacts-vyper", @@ -4314,8 +4316,9 @@ dependencies = [ [[package]] name = "foundry-compilers-artifacts-solc" -version = "0.17.3" -source = "git+https://github.com/foundry-rs/compilers.git?branch=rusowsky%2Fbump-solar#c9c43ce1a64710eb22cfdbef0d361703ac3cf0f5" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3ada94dc5946334bb08df574855ba345ab03ba8c6f233560c72c8d61fa9db80" dependencies = [ "alloy-json-abi", "alloy-primitives", @@ -4336,8 +4339,9 @@ dependencies = [ [[package]] name = "foundry-compilers-artifacts-vyper" -version = "0.17.3" -source = "git+https://github.com/foundry-rs/compilers.git?branch=rusowsky%2Fbump-solar#c9c43ce1a64710eb22cfdbef0d361703ac3cf0f5" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "372052af72652e375a6e7eed22179bd8935114e25e1c5a8cca7f00e8f20bd94c" dependencies = [ "alloy-json-abi", "alloy-primitives", @@ -4350,8 +4354,9 @@ dependencies = [ [[package]] name = "foundry-compilers-core" -version = "0.17.3" -source = "git+https://github.com/foundry-rs/compilers.git?branch=rusowsky%2Fbump-solar#c9c43ce1a64710eb22cfdbef0d361703ac3cf0f5" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf0962c46855979300f6526ed57f987ccf6a025c2b92ce574b281d9cb2ef666b" dependencies = [ "alloy-primitives", "cfg-if", @@ -5481,6 +5486,17 @@ dependencies = [ "thread_local", ] +[[package]] +name = "io-uring" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b86e202f00093dcba4275d4636b93ef9dd75d025ae560d2521b45ea28ab49013" +dependencies = [ + "bitflags 2.9.1", + "cfg-if", + "libc", +] + [[package]] name = "ipnet" version = "2.11.0" @@ -8612,8 +8628,9 @@ dependencies = [ [[package]] name = "solar-ast" -version = "0.1.4" -source = "git+https://github.com/paradigmxyz/solar?branch=main#5c12ea15fae1eabbf1a06eaafd3181107aeb187c" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f7f30449c304fd09db4637209dc73bde7ec203e6e5c691fc9eed26b68cd105a" dependencies = [ "alloy-primitives", "bumpalo", @@ -8630,16 +8647,18 @@ dependencies = [ [[package]] name = "solar-config" -version = "0.1.4" -source = "git+https://github.com/paradigmxyz/solar?branch=main#5c12ea15fae1eabbf1a06eaafd3181107aeb187c" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "643ddf85ab917f5643ec47eb79ee6db6c6158cfaf7415e39840e154eecf7176f" dependencies = [ "strum 0.27.1", ] [[package]] name = "solar-data-structures" -version = "0.1.4" -source = "git+https://github.com/paradigmxyz/solar?branch=main#5c12ea15fae1eabbf1a06eaafd3181107aeb187c" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3cc21b4df6061e1c825c16faf8e1f16c2341f4c46a2b2a60e03069c4453fc5ac" dependencies = [ "bumpalo", "index_vec", @@ -8652,18 +8671,18 @@ dependencies = [ [[package]] name = "solar-interface" -version = "0.1.4" -source = "git+https://github.com/paradigmxyz/solar?branch=main#5c12ea15fae1eabbf1a06eaafd3181107aeb187c" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb2571bfb2f54c5a24688afe6876682117e96f6fd35393398dbac43e5f6f7144" dependencies = [ "annotate-snippets", "anstream", "anstyle", "const-hex", - "derive_builder", "derive_more 2.0.1", "dunce", "inturn", - "itertools 0.12.1", + "itertools 0.14.0", "itoa", "match_cfg", "normalize-path", @@ -8681,8 +8700,9 @@ dependencies = [ [[package]] name = "solar-macros" -version = "0.1.4" -source = "git+https://github.com/paradigmxyz/solar?branch=main#5c12ea15fae1eabbf1a06eaafd3181107aeb187c" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ca57257a3b6ef16bd7995d23da3e661e89cebdae6fb9e42e4331ba0f033bd1d" dependencies = [ "proc-macro2", "quote", @@ -8691,13 +8711,14 @@ dependencies = [ [[package]] name = "solar-parse" -version = "0.1.4" -source = "git+https://github.com/paradigmxyz/solar?branch=main#5c12ea15fae1eabbf1a06eaafd3181107aeb187c" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "081b4d9e2ddd3c7bb90079b3eb253b957ef9eb5c860ed6d6a4068884ec3a8b85" dependencies = [ "alloy-primitives", "bitflags 2.9.1", "bumpalo", - "itertools 0.12.1", + "itertools 0.14.0", "memchr", "num-bigint", "num-rational", @@ -8711,8 +8732,9 @@ dependencies = [ [[package]] name = "solar-sema" -version = "0.1.4" -source = "git+https://github.com/paradigmxyz/solar?branch=main#5c12ea15fae1eabbf1a06eaafd3181107aeb187c" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93b017d4017ee8324e669c6e5e6fe9a282ed07eb6171e5384ddd17b8a854766f" dependencies = [ "alloy-json-abi", "alloy-primitives", @@ -9304,17 +9326,19 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.45.1" +version = "1.46.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75ef51a33ef1da925cea3e4eb122833cb377c61439ca401b770f54902b806779" +checksum = "0cc3a2344dafbe23a245241fe8b09735b521110d30fcefbbd5feb1797ca35d17" dependencies = [ "backtrace", "bytes", + "io-uring", "libc", "mio", "parking_lot", "pin-project-lite", "signal-hook-registry", + "slab", "socket2", "tokio-macros", "windows-sys 0.52.0", diff --git a/Cargo.toml b/Cargo.toml index a83cc4b73be64..6422504873d59 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -204,14 +204,14 @@ foundry-wallets = { path = "crates/wallets" } foundry-linking = { path = "crates/linking" } # solc & compilation utilities -foundry-block-explorers = { version = "0.18.0", default-features = false } -foundry-compilers = { version = "0.17.3", default-features = false } +foundry-block-explorers = { version = "0.20.0", default-features = false } +foundry-compilers = { version = "0.18.0", default-features = false } foundry-fork-db = "0.15" solang-parser = { version = "=0.3.9", package = "foundry-solang-parser" } -solar-ast = { version = "=0.1.4", default-features = false } -solar-parse = { version = "=0.1.4", default-features = false } -solar-interface = { version = "=0.1.4", default-features = false } -solar-sema = { version = "=0.1.4", default-features = false } +solar-ast = { version = "=0.1.5", default-features = false } +solar-parse = { version = "=0.1.5", default-features = false } +solar-interface = { version = "=0.1.5", default-features = false } +solar-sema = { version = "=0.1.5", default-features = false } ## alloy alloy-consensus = { version = "1.0.11", default-features = false } @@ -364,10 +364,10 @@ idna_adapter = "=1.1.0" zip-extract = "=0.2.1" [patch.crates-io] -solar-parse = { git = "https://github.com/paradigmxyz/solar", branch = "main" } -solar-sema = { git = "https://github.com/paradigmxyz/solar", branch = "main" } -solar-ast = { git = "https://github.com/paradigmxyz/solar", branch = "main" } -solar-interface = { git = "https://github.com/paradigmxyz/solar", branch = "main" } +# solar-parse = { git = "https://github.com/paradigmxyz/solar", branch = "main" } +# solar-sema = { git = "https://github.com/paradigmxyz/solar", branch = "main" } +# solar-ast = { git = "https://github.com/paradigmxyz/solar", branch = "main" } +# solar-interface = { git = "https://github.com/paradigmxyz/solar", branch = "main" } figment = { git = "https://github.com/DaniPopes/Figment", branch = "less-mono" } @@ -417,5 +417,5 @@ figment = { git = "https://github.com/DaniPopes/Figment", branch = "less-mono" } # revm-inspectors = { git = "https://github.com/paradigmxyz/revm-inspectors.git", rev = "a625c04" } ## foundry -foundry-compilers = { git = "https://github.com/foundry-rs/compilers.git", branch = "rusowsky/bump-solar" } +# foundry-compilers = { git = "https://github.com/foundry-rs/compilers.git", branch = "main" } # foundry-fork-db = { git = "https://github.com/foundry-rs/foundry-fork-db", rev = "811a61a" } diff --git a/crates/chisel/src/solidity_helper.rs b/crates/chisel/src/solidity_helper.rs index 5087e7b47c377..0ea8faa9b5d5d 100644 --- a/crates/chisel/src/solidity_helper.rs +++ b/crates/chisel/src/solidity_helper.rs @@ -53,7 +53,7 @@ impl SolidityHelper { errored: false, do_paint: yansi::is_enabled(), sess: Session::builder().with_silent_emitter(None).build(), - globals: SessionGlobals::new(), + globals: SessionGlobals::default(), } } diff --git a/crates/fmt-2/src/pp/mod.rs b/crates/fmt-2/src/pp/mod.rs index aa78ed58558c0..721d8b6117718 100644 --- a/crates/fmt-2/src/pp/mod.rs +++ b/crates/fmt-2/src/pp/mod.rs @@ -78,7 +78,7 @@ pub struct Printer { /// Number of spaces left on line space: isize, /// Ring-buffer of tokens and calculated sizes - buf: RingBuffer, + pub buf: RingBuffer, /// Running size of stream "...left" left_total: isize, /// Running size of stream "...right" @@ -105,8 +105,8 @@ pub struct Printer { } #[derive(Debug)] -struct BufEntry { - token: Token, +pub struct BufEntry { + pub token: Token, size: isize, } @@ -149,6 +149,44 @@ impl Printer { self.buf.last_mut().token = token; } + /// WARNING: Be very careful with this! + /// + /// Searches backwards through the buffer to find and replace the last token + /// that satisfies a predicate. This is a specialized and sensitive operation. + /// + /// This function's traversal logic is specifically designed to handle cases + /// where formatting boxes have been closed (e.g., after a multi-line + /// comment). It will automatically skip over any trailing `Token::End` + /// tokens to find the substantive token before them. + /// + /// The search stops as soon as it encounters any token other than `End` + /// (i.e., a `String`, `Break`, or `Begin`). The provided predicate is then + /// called on that token. If the predicate returns `true`, the token is + /// replaced. + /// + /// This function will only ever evaluate the predicate on **one** token. + pub(crate) fn find_and_replace_last_token_still_buffered( + &mut self, + new_token: Token, + predicate: F, + ) where + F: FnOnce(&Token) -> bool, + { + for i in self.buf.index_range().rev() { + let token = &self.buf[i].token; + if let Token::End = token { + // It's safe to skip the end of a box. + continue; + } + + // Apply the predicate and return after the first non-end token. + if predicate(token) { + self.buf[i].token = new_token; + } + break; + } + } + fn scan_eof(&mut self) { if !self.scan_stack.is_empty() { self.check_stack(0); diff --git a/crates/fmt-2/src/state.rs b/crates/fmt-2/src/state.rs index e56fb96006557..7be7345c0c6cb 100644 --- a/crates/fmt-2/src/state.rs +++ b/crates/fmt-2/src/state.rs @@ -12,7 +12,7 @@ use solar_parse::{ interface::{BytePos, SourceMap}, Cursor, }; -use std::{borrow::Cow, collections::HashMap}; +use std::{borrow::Cow, collections::HashMap, fmt::Debug}; pub(super) struct State<'sess, 'ast> { pub(crate) s: pp::Printer, @@ -23,10 +23,8 @@ pub(super) struct State<'sess, 'ast> { config: FormatterConfig, inline_config: InlineConfig, - // cache information for proper formatting contract: Option<&'ast ast::ItemContract<'ast>>, single_line_stmt: Option, - assign_expr: bool, } impl std::ops::Deref for State<'_, '_> { @@ -62,7 +60,6 @@ impl<'sess> State<'sess, '_> { config, contract: None, single_line_stmt: None, - assign_expr: false, } } @@ -109,7 +106,8 @@ impl<'sess> State<'sess, '_> { } None => (), } - } else { + // Never print blank lines after docs comments + } else if !cmnt.is_doc { all_blank = false; } last_style = Some(cmnt.style); @@ -276,15 +274,25 @@ impl<'sess> State<'sess, '_> { } } - fn break_offset_if_not_bol(&mut self, n: usize, off: isize) { - if !self.is_beginning_of_line() { + fn break_offset_if_not_bol(&mut self, n: usize, off: isize, search: bool) { + // If the break token is expected to be inside a closed box, so we will traverse the + // buffer and evaluate the first non-end token. + if search { + // We do something pretty sketchy here: tuck the nonzero offset-adjustment we + // were going to deposit along with the break into the previous hardbreak. + self.find_and_replace_last_token_still_buffered( + pp::Printer::hardbreak_tok_offset(off), + |token| token.is_hardbreak(), + ); + } + // Otherwise, the break token is expected to be the last token. + else if !self.is_beginning_of_line() { self.break_offset(n, off) } else if off != 0 { if let Some(last_token) = self.last_token_still_buffered() { if last_token.is_hardbreak() { - // We do something pretty sketchy here: tuck the nonzero - // offset-adjustment we were going to deposit along with the - // break into the previous hardbreak. + // We do something pretty sketchy here: tuck the nonzero offset-adjustment we + // were going to deposit along with the break into the previous hardbreak. self.replace_last_token_still_buffered(pp::Printer::hardbreak_tok_offset(off)); } } @@ -327,7 +335,7 @@ impl<'sess> State<'sess, '_> { if !self.print_trailing_comment(span.hi(), None) && skip_break { self.neverbreak(); } else { - self.break_offset_if_not_bol(0, -self.ind); + self.break_offset_if_not_bol(0, -self.ind, false); } self.end(); } else { @@ -379,8 +387,15 @@ impl<'sess> State<'sess, '_> { return; } - let is_single_without_cmnts = - !is_binary_expr && values.len() == 1 && self.peek_comment_before(pos_hi).is_none(); + let (is_single_without_cmnts, is_single_ends_with_trailing) = if values.len() == 1 { + let value_pos = get_span(&values[0]).map(Span::lo).unwrap_or(pos_hi); + ( + !is_binary_expr && self.peek_comment_before(pos_hi).is_none(), + self.peek_trailing_comment(value_pos, None).is_some(), + ) + } else { + (false, false) + }; self.s.cbox(self.ind); let mut skip_first_break = is_single_without_cmnts; @@ -428,10 +443,16 @@ impl<'sess> State<'sess, '_> { self.hardbreak(); // trailing and isolated comments already hardbreak } self.print_cmnts_with_space_skip_ws(next_pos); - if is_last && self.is_bol_or_only_ind() { + + if is_single_ends_with_trailing { + // the value prints a trailing comment inside its box, we have to manually adjust + // the offset to avoid having a double break. + self.break_offset_if_not_bol(0, -self.ind, true); + skip_last_break = true; + } else if is_last && self.is_bol_or_only_ind() { // if a trailing comment is printed at the very end, we have to manually adjust // the offset to avoid having a double break. - self.break_offset_if_not_bol(0, -self.ind); + self.break_offset_if_not_bol(0, -self.ind, false); skip_last_break = true; } if !is_last && !self.is_bol_or_only_ind() { @@ -837,7 +858,7 @@ impl<'ast> State<'_, 'ast> { // Print fn body if let Some(body) = body { if self.peek_comment_before(body_span.lo()).map_or(true, |cmnt| cmnt.style.is_mixed()) { - if attributes.len() == 1 && empty_returns { + if attributes.len() == 1 && empty_returns && override_.is_none() { self.nbsp(); self.zerobreak(); } else { @@ -1322,7 +1343,6 @@ impl<'ast> State<'_, 'ast> { self.print_array(exprs, expr.span, |this, e| this.print_expr(e), get_span!()) } ast::ExprKind::Assign(lhs, None, rhs) => { - self.hardbreak(); self.ibox(0); self.print_expr(lhs); self.word(" = "); @@ -1423,15 +1443,15 @@ impl<'ast> State<'_, 'ast> { // Manually revert indentation if there is `expr1` and/or comments. if skip_break && expr1.is_some() { - self.break_offset_if_not_bol(0, -2 * self.ind); + self.break_offset_if_not_bol(0, -2 * self.ind, false); self.end(); // if a trailing comment is printed at the very end, we have to manually // adjust the offset to avoid having a double break. if !is_trailing { - self.break_offset_if_not_bol(0, -self.ind); + self.break_offset_if_not_bol(0, -self.ind, false); } } else if skip_break { - self.break_offset_if_not_bol(0, -self.ind); + self.break_offset_if_not_bol(0, -self.ind, false); } else if expr1.is_some() { self.end(); } @@ -1456,7 +1476,7 @@ impl<'ast> State<'_, 'ast> { if self.print_trailing_comment(expr.span.hi(), Some(ident.span.lo())) { // if a trailing comment is printed at the very end, we have to manually adjust // the offset to avoid having a double break. - self.break_offset_if_not_bol(0, self.ind); + self.break_offset_if_not_bol(0, self.ind, false); } self.word("."); self.print_ident(ident); @@ -1702,7 +1722,7 @@ impl<'ast> State<'_, 'ast> { } else { self.zerobreak(); } - self.break_offset_if_not_bol(0, -self.ind); + self.break_offset_if_not_bol(0, -self.ind, false); self.end(); self.word(") "); self.neverbreak(); @@ -1792,7 +1812,7 @@ impl<'ast> State<'_, 'ast> { { // if a trailing comment is printed at the very end, we have to manually // adjust the offset to avoid having a double break. - self.break_offset_if_not_bol(0, self.ind); + self.break_offset_if_not_bol(0, self.ind, false); skip_ind = true; }; self.end(); @@ -1946,7 +1966,7 @@ impl<'ast> State<'_, 'ast> { ); } - fn print_block_inner( + fn print_block_inner( &mut self, block: &'ast [T], block_format: BlockFormat, @@ -2004,18 +2024,16 @@ impl<'ast> State<'_, 'ast> { self.s.cbox(self.ind); } self.print_cmnts_with_space_skip_ws(span.hi()); + // manually adjust offset to ensure that the closing brace is properly indented. + // if the last cmnt was breaking, we replace offset to avoid e a double break. + if self.is_bol_or_only_ind() { + self.break_offset_if_not_bol(0, -self.ind, false); + } else { + self.s.break_offset(0, -self.ind); + } + self.end(); if block_format.with_braces() { - // manually adjust offset to ensure that the closing brace is properly indented. - // if the last cmnt was breaking, we replace offset to avoid e a double break. - if self.is_bol_or_only_ind() { - self.break_offset_if_not_bol(0, -self.ind); - } else { - self.s.break_offset(0, -self.ind); - } - self.end(); self.word("}"); - } else { - self.end(); } } return; @@ -2026,15 +2044,15 @@ impl<'ast> State<'_, 'ast> { self.print_comments_skip_ws(get_block_span(&block[0]).lo()); self.s.cbox(0); } - BlockFormat::NoBraces(offset) => { + BlockFormat::NoBraces(Some(offset)) => { if self.peek_comment_before(get_block_span(&block[0]).lo()).is_some() { self.hardbreak(); - self.break_offset_if_not_bol(0, offset.unwrap()); + self.break_offset_if_not_bol(0, offset, false); self.print_comments_skip_ws(get_block_span(&block[0]).lo()); } else { self.zerobreak(); } - self.s.offset(offset.unwrap()); + self.s.offset(offset); self.s.cbox(self.ind); } _ => { diff --git a/crates/fmt-2/tests/formatter.rs b/crates/fmt-2/tests/formatter.rs index 3f7d497ed0e63..3d4c1b155f160 100644 --- a/crates/fmt-2/tests/formatter.rs +++ b/crates/fmt-2/tests/formatter.rs @@ -167,7 +167,7 @@ fmt_tests! { ErrorDefinition, // OK EventDefinition, // OK ForStatement, // OK? TODO: try to improve nested conditional expressions - FunctionCall, // OK: TODO: enhance PP so that trailing comments aren't accounted for when breaking lines after a simcolon. + FunctionCall, // OK: TODO: enhance PP so that trailing comments aren't accounted for when breaking lines after a simcolon? FunctionCallArgsStatement, // OK? Is it acceptable? FunctionDefinition, // OK? Is it acceptable? FunctionDefinitionWithFunctionReturns, // OK @@ -183,7 +183,7 @@ fmt_tests! { ModifierDefinition, // OK NamedFunctionCallExpression, // FIX: idempotency (comment-related) NumberLiteralUnderscore, // OK - OperatorExpressions, // OKish: FIX: empty line after trailing cmnt inside if + OperatorExpressions, // OK PragmaDirective, // OK Repros, // TODO: check boxes, panics ReturnStatement, // FIX: idempotency (comment-related) From 31051921cd28395046fe65e1554a8df21808f84f Mon Sep 17 00:00:00 2001 From: 0xrusowsky <0xrusowsky@proton.me> Date: Mon, 14 Jul 2025 19:39:06 +0200 Subject: [PATCH 29/31] housekeeping --- crates/fmt-2/src/pp/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/fmt-2/src/pp/mod.rs b/crates/fmt-2/src/pp/mod.rs index 721d8b6117718..66c8c36dc93f6 100644 --- a/crates/fmt-2/src/pp/mod.rs +++ b/crates/fmt-2/src/pp/mod.rs @@ -78,7 +78,7 @@ pub struct Printer { /// Number of spaces left on line space: isize, /// Ring-buffer of tokens and calculated sizes - pub buf: RingBuffer, + buf: RingBuffer, /// Running size of stream "...left" left_total: isize, /// Running size of stream "...right" @@ -106,7 +106,7 @@ pub struct Printer { #[derive(Debug)] pub struct BufEntry { - pub token: Token, + token: Token, size: isize, } From 09dc9ff2fbb88347938b35a451ff07b8f1891142 Mon Sep 17 00:00:00 2001 From: 0xrusowsky <0xrusowsky@proton.me> Date: Tue, 15 Jul 2025 09:02:08 +0200 Subject: [PATCH 30/31] feat: binary expressions --- crates/fmt-2/src/state.rs | 65 +++++++++++++++++++++------------ crates/fmt-2/tests/formatter.rs | 6 +-- 2 files changed, 45 insertions(+), 26 deletions(-) diff --git a/crates/fmt-2/src/state.rs b/crates/fmt-2/src/state.rs index 7be7345c0c6cb..ea58b29871c9e 100644 --- a/crates/fmt-2/src/state.rs +++ b/crates/fmt-2/src/state.rs @@ -25,6 +25,7 @@ pub(super) struct State<'sess, 'ast> { contract: Option<&'ast ast::ItemContract<'ast>>, single_line_stmt: Option, + binary_expr: bool, } impl std::ops::Deref for State<'_, '_> { @@ -60,6 +61,7 @@ impl<'sess> State<'sess, '_> { config, contract: None, single_line_stmt: None, + binary_expr: false, } } @@ -275,8 +277,8 @@ impl<'sess> State<'sess, '_> { } fn break_offset_if_not_bol(&mut self, n: usize, off: isize, search: bool) { - // If the break token is expected to be inside a closed box, so we will traverse the - // buffer and evaluate the first non-end token. + // When searching, the break token is expected to be inside a closed box. Thus, we will + // traverse the buffer and evaluate the first non-end token. if search { // We do something pretty sketchy here: tuck the nonzero offset-adjustment we // were going to deposit along with the break into the previous hardbreak. @@ -284,9 +286,11 @@ impl<'sess> State<'sess, '_> { pp::Printer::hardbreak_tok_offset(off), |token| token.is_hardbreak(), ); + return; } - // Otherwise, the break token is expected to be the last token. - else if !self.is_beginning_of_line() { + + // When not explicitly searching, the break token is expected to be the last token. + if !self.is_beginning_of_line() { self.break_offset(n, off) } else if off != 0 { if let Some(last_token) = self.last_token_still_buffered() { @@ -387,11 +391,11 @@ impl<'sess> State<'sess, '_> { return; } - let (is_single_without_cmnts, is_single_ends_with_trailing) = if values.len() == 1 { + let (is_single_without_cmnts, is_binary_with_trailing) = if values.len() == 1 { let value_pos = get_span(&values[0]).map(Span::lo).unwrap_or(pos_hi); ( !is_binary_expr && self.peek_comment_before(pos_hi).is_none(), - self.peek_trailing_comment(value_pos, None).is_some(), + is_binary_expr && self.peek_trailing_comment(value_pos, None).is_some(), ) } else { (false, false) @@ -444,10 +448,10 @@ impl<'sess> State<'sess, '_> { } self.print_cmnts_with_space_skip_ws(next_pos); - if is_single_ends_with_trailing { - // the value prints a trailing comment inside its box, we have to manually adjust - // the offset to avoid having a double break. - self.break_offset_if_not_bol(0, -self.ind, true); + if is_binary_with_trailing { + // binary expressions prints trailing comment inside its boxes, we have to manually + // adjust the offset to avoid having a double break. + self.break_offset_if_not_bol(0, -2 * self.ind, true); skip_last_break = true; } else if is_last && self.is_bol_or_only_ind() { // if a trailing comment is printed at the very end, we have to manually adjust @@ -1354,9 +1358,12 @@ impl<'ast> State<'_, 'ast> { ast::ExprKind::Binary(lhs, bin_op, rhs) => { let is_parent = matches!(lhs.kind, ast::ExprKind::Binary(..)) || matches!(rhs.kind, ast::ExprKind::Binary(..)); - if is_parent { + let is_child = self.binary_expr; + if !is_child && is_parent { + // top-level expression of the chain -> set cache + self.binary_expr = true; self.s.ibox(self.ind); - } else { + } else if !is_child || !is_parent { self.ibox(0); } @@ -1370,11 +1377,13 @@ impl<'ast> State<'_, 'ast> { } else if !self.is_bol_or_only_ind() { self.nbsp(); } - if is_parent { - if has_complex_ancestor(&rhs.kind, false) { - self.s.ibox(0); - } else { + + // box expressions with complex sucessors to accomodate their own indentation + if !is_child && is_parent { + if has_complex_succesor(&rhs.kind, true) { self.s.ibox(-self.ind); + } else if has_complex_succesor(&rhs.kind, false) { + self.s.ibox(0); } } self.word(bin_op.kind.to_str()); @@ -1385,10 +1394,19 @@ impl<'ast> State<'_, 'ast> { self.print_expr(rhs); self.print_trailing_comment(rhs.span.hi(), None); - if is_parent { + if (has_complex_succesor(&rhs.kind, false) || has_complex_succesor(&rhs.kind, true)) && + (!is_child && is_parent) + { + self.end(); + } + + if !is_child { + // top-level expression of the chain -> clear cache + self.binary_expr = false; + self.end(); + } else if !is_parent { self.end(); } - self.end(); } ast::ExprKind::Call(expr, call_args) => { self.print_expr(expr); @@ -2655,7 +2673,7 @@ impl<'ast> AttributeCommentMapper<'ast> { } } -fn stmt_needs_semi<'ast>(stmt: &'ast ast::StmtKind<'ast>) -> bool { +fn stmt_needs_semi(stmt: &ast::StmtKind<'_>) -> bool { match stmt { ast::StmtKind::Assembly { .. } | ast::StmtKind::Block { .. } | @@ -2688,19 +2706,20 @@ pub struct Decision { is_cached: bool, } -fn is_binary_expr<'ast>(expr_kind: &'ast ast::ExprKind<'ast>) -> bool { +fn is_binary_expr(expr_kind: &ast::ExprKind<'_>) -> bool { matches!(expr_kind, ast::ExprKind::Binary(..)) } -fn has_complex_ancestor<'ast>(expr_kind: &'ast ast::ExprKind<'ast>, left: bool) -> bool { +fn has_complex_succesor(expr_kind: &ast::ExprKind<'_>, left: bool) -> bool { match expr_kind { ast::ExprKind::Binary(lhs, _, rhs) => { if left { - has_complex_ancestor(&lhs.kind, left) + has_complex_succesor(&lhs.kind, left) } else { - has_complex_ancestor(&rhs.kind, left) + has_complex_succesor(&rhs.kind, left) } } + ast::ExprKind::Unary(_, expr) => has_complex_succesor(&expr.kind, left), ast::ExprKind::Lit(..) | ast::ExprKind::Ident(_) => false, _ => true, } diff --git a/crates/fmt-2/tests/formatter.rs b/crates/fmt-2/tests/formatter.rs index 3d4c1b155f160..51830850d93c9 100644 --- a/crates/fmt-2/tests/formatter.rs +++ b/crates/fmt-2/tests/formatter.rs @@ -160,13 +160,13 @@ fmt_tests! { ConstructorModifierStyle, // OK ContractDefinition, // OK? Is it acceptable? DocComments, // OK (basics). TODO: wrapp comments - DoWhileStatement, // OK? TODO: try to improve nested conditional expressions + DoWhileStatement, // OK EmitStatement, // OK EnumDefinition, // OK EnumVariants, // OK ErrorDefinition, // OK EventDefinition, // OK - ForStatement, // OK? TODO: try to improve nested conditional expressions + ForStatement, // OKish (works if we use one less digit). FunctionCall, // OK: TODO: enhance PP so that trailing comments aren't accounted for when breaking lines after a simcolon? FunctionCallArgsStatement, // OK? Is it acceptable? FunctionDefinition, // OK? Is it acceptable? @@ -201,7 +201,7 @@ fmt_tests! { UsingDirective, // OK VariableAssignment, // FIX: variable assignment VariableDefinition, // FIX: variable assignment + declaration - WhileStatement, // OKish: TODO: improve nested conditional expressions + WhileStatement, // OK Yul, // FIX: idemptency (comment-related) YulStrings, // OK } From 57aff048b2fd432a6d4a1e219d321a2729ca9f6f Mon Sep 17 00:00:00 2001 From: 0xrusowsky <0xrusowsky@proton.me> Date: Tue, 15 Jul 2025 09:56:37 +0200 Subject: [PATCH 31/31] fix: string literals --- crates/fmt-2/src/state.rs | 8 ++++++-- crates/fmt-2/tests/formatter.rs | 4 ++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/crates/fmt-2/src/state.rs b/crates/fmt-2/src/state.rs index ea58b29871c9e..0c18a5a0000a4 100644 --- a/crates/fmt-2/src/state.rs +++ b/crates/fmt-2/src/state.rs @@ -1060,16 +1060,19 @@ impl<'ast> State<'_, 'ast> { ast::LitKind::Str(kind, ..) => { self.cbox(0); for (pos, (span, symbol)) in lit.literals().delimited() { + self.ibox(0); if !self.handle_span(span) { let quote_pos = span.lo() + kind.prefix().len() as u32; self.print_str_lit(kind, quote_pos, symbol.as_str()); } if !pos.is_last { - self.space_if_not_bol(); - self.print_trailing_comment(span.hi(), None); + if !self.print_trailing_comment(span.hi(), None) { + self.space_if_not_bol(); + } } else { self.neverbreak(); } + self.end(); } self.end(); } @@ -1080,6 +1083,7 @@ impl<'ast> State<'_, 'ast> { ast::LitKind::Bool(value) => self.word(if value { "true" } else { "false" }), ast::LitKind::Err(_) => self.word(symbol.to_string()), } + // self.print_trailing_comment(lit.span.hi(), None); } fn print_num_literal(&mut self, source: &str) { diff --git a/crates/fmt-2/tests/formatter.rs b/crates/fmt-2/tests/formatter.rs index 51830850d93c9..0ba0c2c55b2d5 100644 --- a/crates/fmt-2/tests/formatter.rs +++ b/crates/fmt-2/tests/formatter.rs @@ -166,7 +166,7 @@ fmt_tests! { EnumVariants, // OK ErrorDefinition, // OK EventDefinition, // OK - ForStatement, // OKish (works if we use one less digit). + ForStatement, // OK (works if we use one less digit + breaks as it should) FunctionCall, // OK: TODO: enhance PP so that trailing comments aren't accounted for when breaking lines after a simcolon? FunctionCallArgsStatement, // OK? Is it acceptable? FunctionDefinition, // OK? Is it acceptable? @@ -178,7 +178,7 @@ fmt_tests! { ImportDirective, // OK InlineDisable, // FIX: invalid output IntTypes, // OK - LiteralExpression, // FIX: idempotency (comment-related) + LiteralExpression, // Ok? is it acceptable? MappingType, // TODO: comments ModifierDefinition, // OK NamedFunctionCallExpression, // FIX: idempotency (comment-related)