Repository: foundry-rs/foundry Branch: master Commit: fc2c1473e143 Files: 1285 Total size: 12.8 MB Directory structure: gitextract_5h1i4s30/ ├── .cargo/ │ └── config.toml ├── .config/ │ └── nextest.toml ├── .devcontainer/ │ ├── Dockerfile.dev │ └── devcontainer.json ├── .dockerignore ├── .git-blame-ignore-revs ├── .gitattributes ├── .github/ │ ├── CODEOWNERS │ ├── FLAKY_TEST_FAILURE_TEMPLATE.md │ ├── FLAKY_TEST_ISOLATE_FAILURE_TEMPLATE.md │ ├── INTEGRATION_FAILURE.md │ ├── ISSUE_TEMPLATE/ │ │ ├── BUG-FORM.yml │ │ ├── FEATURE-FORM.yml │ │ └── config.yml │ ├── PULL_REQUEST_TEMPLATE.md │ ├── RELEASE_FAILURE_ISSUE_TEMPLATE.md │ ├── TEST_ISOLATE_FAILURE_TEMPLATE.md │ ├── changelog.json │ ├── dependabot.yml │ ├── scripts/ │ │ ├── combine-benchmarks.sh │ │ ├── commit-and-read-benchmarks.sh │ │ ├── create-tag.js │ │ ├── format-pr-comment.sh │ │ ├── format.sh │ │ ├── matrices.py │ │ ├── move-tag.js │ │ ├── prune-prereleases.js │ │ ├── setup-foundryup.sh │ │ └── shellcheck.sh │ └── workflows/ │ ├── benchmarks.yml │ ├── bump-forge-std.yml │ ├── ci.yml │ ├── dependencies.yml │ ├── docker-publish.yml │ ├── docs.yml │ ├── nix.yml │ ├── npm.yml │ ├── release.yml │ ├── test-flaky.yml │ ├── test-isolate.yml │ └── test.yml ├── .gitignore ├── CONTRIBUTING.md ├── Cargo.toml ├── Dockerfile ├── FUNDING.json ├── LICENSE-APACHE ├── LICENSE-MIT ├── Makefile ├── README.md ├── SECURITY.md ├── benches/ │ ├── Cargo.toml │ ├── LATEST.md │ ├── README.md │ └── src/ │ ├── lib.rs │ ├── main.rs │ └── results.rs ├── benchmark.sh ├── clippy.toml ├── crates/ │ ├── anvil/ │ │ ├── Cargo.toml │ │ ├── bin/ │ │ │ └── main.rs │ │ ├── core/ │ │ │ ├── Cargo.toml │ │ │ └── src/ │ │ │ ├── eth/ │ │ │ │ ├── block.rs │ │ │ │ ├── mod.rs │ │ │ │ ├── serde_helpers.rs │ │ │ │ ├── subscription.rs │ │ │ │ ├── transaction/ │ │ │ │ │ └── mod.rs │ │ │ │ └── wallet.rs │ │ │ ├── lib.rs │ │ │ └── types.rs │ │ ├── rpc/ │ │ │ ├── Cargo.toml │ │ │ └── src/ │ │ │ ├── error.rs │ │ │ ├── lib.rs │ │ │ ├── request.rs │ │ │ └── response.rs │ │ ├── server/ │ │ │ ├── Cargo.toml │ │ │ └── src/ │ │ │ ├── config.rs │ │ │ ├── error.rs │ │ │ ├── handler.rs │ │ │ ├── ipc.rs │ │ │ ├── lib.rs │ │ │ ├── pubsub.rs │ │ │ └── ws.rs │ │ ├── src/ │ │ │ ├── args.rs │ │ │ ├── cmd.rs │ │ │ ├── config.rs │ │ │ ├── error.rs │ │ │ ├── eth/ │ │ │ │ ├── api.rs │ │ │ │ ├── backend/ │ │ │ │ │ ├── cheats.rs │ │ │ │ │ ├── db.rs │ │ │ │ │ ├── env.rs │ │ │ │ │ ├── executor.rs │ │ │ │ │ ├── fork.rs │ │ │ │ │ ├── genesis.rs │ │ │ │ │ ├── info.rs │ │ │ │ │ ├── mem/ │ │ │ │ │ │ ├── cache.rs │ │ │ │ │ │ ├── fork_db.rs │ │ │ │ │ │ ├── in_memory_db.rs │ │ │ │ │ │ ├── inspector.rs │ │ │ │ │ │ ├── mod.rs │ │ │ │ │ │ ├── state.rs │ │ │ │ │ │ └── storage.rs │ │ │ │ │ ├── mod.rs │ │ │ │ │ ├── notifications.rs │ │ │ │ │ ├── time.rs │ │ │ │ │ └── validate.rs │ │ │ │ ├── error.rs │ │ │ │ ├── fees.rs │ │ │ │ ├── macros.rs │ │ │ │ ├── miner.rs │ │ │ │ ├── mod.rs │ │ │ │ ├── otterscan/ │ │ │ │ │ ├── api.rs │ │ │ │ │ └── mod.rs │ │ │ │ ├── pool/ │ │ │ │ │ ├── mod.rs │ │ │ │ │ └── transactions.rs │ │ │ │ ├── sign.rs │ │ │ │ └── util.rs │ │ │ ├── evm.rs │ │ │ ├── filter.rs │ │ │ ├── lib.rs │ │ │ ├── logging.rs │ │ │ ├── opts.rs │ │ │ ├── pubsub.rs │ │ │ ├── server/ │ │ │ │ ├── beacon/ │ │ │ │ │ ├── error.rs │ │ │ │ │ ├── handlers.rs │ │ │ │ │ ├── mod.rs │ │ │ │ │ └── utils.rs │ │ │ │ ├── mod.rs │ │ │ │ └── rpc_handlers.rs │ │ │ ├── service.rs │ │ │ ├── shutdown.rs │ │ │ └── tasks/ │ │ │ ├── block_listener.rs │ │ │ └── mod.rs │ │ ├── test-data/ │ │ │ ├── SimpleStorage.json │ │ │ ├── SimpleStorage.sol │ │ │ ├── emit_logs.json │ │ │ ├── emit_logs.sol │ │ │ ├── greeter.json │ │ │ ├── multicall.json │ │ │ ├── multicall.sol │ │ │ ├── state-dump-legacy-stress.json │ │ │ ├── state-dump-legacy.json │ │ │ ├── state-dump.json │ │ │ └── storage_sample.json │ │ └── tests/ │ │ └── it/ │ │ ├── abi.rs │ │ ├── anvil.rs │ │ ├── anvil_api.rs │ │ ├── api.rs │ │ ├── beacon_api.rs │ │ ├── eip2935.rs │ │ ├── eip4844.rs │ │ ├── eip7702.rs │ │ ├── fork.rs │ │ ├── gas.rs │ │ ├── genesis.rs │ │ ├── ipc.rs │ │ ├── logs.rs │ │ ├── main.rs │ │ ├── optimism.rs │ │ ├── otterscan.rs │ │ ├── proof.rs │ │ ├── pubsub.rs │ │ ├── revert.rs │ │ ├── sign.rs │ │ ├── simulate.rs │ │ ├── state.rs │ │ ├── traces.rs │ │ ├── transaction.rs │ │ ├── txpool.rs │ │ ├── utils.rs │ │ └── wsapi.rs │ ├── cast/ │ │ ├── Cargo.toml │ │ ├── bin/ │ │ │ └── main.rs │ │ ├── src/ │ │ │ ├── args.rs │ │ │ ├── base.rs │ │ │ ├── cmd/ │ │ │ │ ├── access_list.rs │ │ │ │ ├── artifact.rs │ │ │ │ ├── b2e_payload.rs │ │ │ │ ├── bind.rs │ │ │ │ ├── call.rs │ │ │ │ ├── constructor_args.rs │ │ │ │ ├── create2.rs │ │ │ │ ├── creation_code.rs │ │ │ │ ├── da_estimate.rs │ │ │ │ ├── erc20.rs │ │ │ │ ├── estimate.rs │ │ │ │ ├── find_block.rs │ │ │ │ ├── interface.rs │ │ │ │ ├── logs.rs │ │ │ │ ├── mktx.rs │ │ │ │ ├── mod.rs │ │ │ │ ├── rpc.rs │ │ │ │ ├── run.rs │ │ │ │ ├── send.rs │ │ │ │ ├── storage.rs │ │ │ │ ├── trace.rs │ │ │ │ ├── txpool.rs │ │ │ │ └── wallet/ │ │ │ │ ├── list.rs │ │ │ │ ├── mod.rs │ │ │ │ └── vanity.rs │ │ │ ├── debug.rs │ │ │ ├── errors.rs │ │ │ ├── lib.rs │ │ │ ├── opts.rs │ │ │ ├── rlp_converter.rs │ │ │ └── tx.rs │ │ └── tests/ │ │ ├── cli/ │ │ │ ├── erc20.rs │ │ │ ├── main.rs │ │ │ └── selectors.rs │ │ └── fixtures/ │ │ ├── ERC20Artifact.json │ │ ├── TestToken.sol │ │ ├── cast_logs.stdout │ │ ├── interface.json │ │ ├── interface_inherited_struct.json │ │ ├── keystore/ │ │ │ ├── UTC--2022-10-30T06-51-20.130356000Z--560d246fcddc9ea98a8b032c9a2f474efb493c28 │ │ │ ├── UTC--2022-12-20T10-30-43.591916000Z--ec554aeafe75601aaab43bd4621a22284db566c2 │ │ │ ├── password │ │ │ └── password-ec554 │ │ ├── sign_typed_data.json │ │ ├── storage_layout_complex.json │ │ └── storage_layout_simple.json │ ├── cheatcodes/ │ │ ├── Cargo.toml │ │ ├── README.md │ │ ├── assets/ │ │ │ ├── cheatcodes.json │ │ │ └── cheatcodes.schema.json │ │ ├── spec/ │ │ │ ├── Cargo.toml │ │ │ └── src/ │ │ │ ├── cheatcode.rs │ │ │ ├── function.rs │ │ │ ├── items.rs │ │ │ ├── lib.rs │ │ │ └── vm.rs │ │ └── src/ │ │ ├── base64.rs │ │ ├── config.rs │ │ ├── crypto.rs │ │ ├── env.rs │ │ ├── error.rs │ │ ├── evm/ │ │ │ ├── fork.rs │ │ │ ├── mapping.rs │ │ │ ├── mock.rs │ │ │ ├── prank.rs │ │ │ └── record_debug_step.rs │ │ ├── evm.rs │ │ ├── fs.rs │ │ ├── inspector/ │ │ │ ├── analysis.rs │ │ │ └── utils.rs │ │ ├── inspector.rs │ │ ├── json.rs │ │ ├── lib.rs │ │ ├── script.rs │ │ ├── string.rs │ │ ├── test/ │ │ │ ├── assert.rs │ │ │ ├── assume.rs │ │ │ ├── expect.rs │ │ │ └── revert_handlers.rs │ │ ├── test.rs │ │ ├── toml.rs │ │ ├── utils.rs │ │ └── version.rs │ ├── chisel/ │ │ ├── Cargo.toml │ │ ├── assets/ │ │ │ └── preview.tape │ │ ├── bin/ │ │ │ └── main.rs │ │ ├── src/ │ │ │ ├── args.rs │ │ │ ├── cmd.rs │ │ │ ├── dispatcher.rs │ │ │ ├── executor.rs │ │ │ ├── lib.rs │ │ │ ├── opts.rs │ │ │ ├── runner.rs │ │ │ ├── session.rs │ │ │ ├── solidity_helper.rs │ │ │ └── source.rs │ │ └── tests/ │ │ └── it/ │ │ ├── main.rs │ │ └── repl/ │ │ ├── mod.rs │ │ └── session.rs │ ├── cli/ │ │ ├── Cargo.toml │ │ ├── README.md │ │ └── src/ │ │ ├── clap.rs │ │ ├── handler.rs │ │ ├── lib.rs │ │ ├── opts/ │ │ │ ├── build/ │ │ │ │ ├── core.rs │ │ │ │ ├── mod.rs │ │ │ │ ├── paths.rs │ │ │ │ └── utils.rs │ │ │ ├── chain.rs │ │ │ ├── dependency.rs │ │ │ ├── evm.rs │ │ │ ├── global.rs │ │ │ ├── mod.rs │ │ │ ├── network.rs │ │ │ ├── rpc.rs │ │ │ ├── tempo.rs │ │ │ └── transaction.rs │ │ └── utils/ │ │ ├── abi.rs │ │ ├── allocator.rs │ │ ├── cmd.rs │ │ ├── default_directives.txt │ │ ├── mod.rs │ │ └── suggestions.rs │ ├── cli-markdown/ │ │ ├── Cargo.toml │ │ └── src/ │ │ └── lib.rs │ ├── common/ │ │ ├── Cargo.toml │ │ ├── README.md │ │ ├── build.rs │ │ ├── fmt/ │ │ │ ├── Cargo.toml │ │ │ └── src/ │ │ │ ├── console.rs │ │ │ ├── dynamic.rs │ │ │ ├── exp.rs │ │ │ ├── lib.rs │ │ │ └── ui.rs │ │ └── src/ │ │ ├── abi.rs │ │ ├── calc.rs │ │ ├── comments/ │ │ │ ├── comment.rs │ │ │ ├── inline_config.rs │ │ │ └── mod.rs │ │ ├── compile.rs │ │ ├── constants.rs │ │ ├── contracts.rs │ │ ├── errors/ │ │ │ ├── fs.rs │ │ │ └── mod.rs │ │ ├── fs.rs │ │ ├── io/ │ │ │ ├── macros.rs │ │ │ ├── mod.rs │ │ │ ├── shell.rs │ │ │ ├── stdin.rs │ │ │ └── style.rs │ │ ├── iter.rs │ │ ├── lib.rs │ │ ├── mapping_slots.rs │ │ ├── preprocessor/ │ │ │ ├── data.rs │ │ │ ├── deps.rs │ │ │ └── mod.rs │ │ ├── provider/ │ │ │ ├── curl_transport.rs │ │ │ ├── mod.rs │ │ │ └── runtime_transport.rs │ │ ├── retry.rs │ │ ├── selectors.rs │ │ ├── serde_helpers.rs │ │ ├── slot_identifier.rs │ │ ├── term.rs │ │ ├── traits.rs │ │ ├── transactions.rs │ │ ├── utils.rs │ │ └── version.rs │ ├── config/ │ │ ├── Cargo.toml │ │ ├── README.md │ │ └── src/ │ │ ├── bind_json.rs │ │ ├── cache.rs │ │ ├── compilation.rs │ │ ├── doc.rs │ │ ├── endpoints.rs │ │ ├── error.rs │ │ ├── etherscan.rs │ │ ├── extend.rs │ │ ├── filter.rs │ │ ├── fix.rs │ │ ├── fmt.rs │ │ ├── fs_permissions.rs │ │ ├── fuzz.rs │ │ ├── inline/ │ │ │ ├── mod.rs │ │ │ └── natspec.rs │ │ ├── invariant.rs │ │ ├── lib.rs │ │ ├── lint.rs │ │ ├── macros.rs │ │ ├── providers/ │ │ │ ├── ext.rs │ │ │ ├── mod.rs │ │ │ ├── remappings.rs │ │ │ └── warnings.rs │ │ ├── resolve.rs │ │ ├── soldeer.rs │ │ ├── utils.rs │ │ ├── vyper.rs │ │ └── warning.rs │ ├── debugger/ │ │ ├── Cargo.toml │ │ └── src/ │ │ ├── builder.rs │ │ ├── debugger.rs │ │ ├── dump.rs │ │ ├── lib.rs │ │ ├── node.rs │ │ ├── op.rs │ │ └── tui/ │ │ ├── context.rs │ │ ├── draw.rs │ │ └── mod.rs │ ├── doc/ │ │ ├── Cargo.toml │ │ ├── README.md │ │ ├── src/ │ │ │ ├── builder.rs │ │ │ ├── document.rs │ │ │ ├── helpers.rs │ │ │ ├── lib.rs │ │ │ ├── parser/ │ │ │ │ ├── comment.rs │ │ │ │ ├── error.rs │ │ │ │ ├── item.rs │ │ │ │ └── mod.rs │ │ │ ├── preprocessor/ │ │ │ │ ├── contract_inheritance.rs │ │ │ │ ├── deployments.rs │ │ │ │ ├── git_source.rs │ │ │ │ ├── infer_hyperlinks.rs │ │ │ │ ├── inheritdoc.rs │ │ │ │ └── mod.rs │ │ │ ├── solang_ext/ │ │ │ │ ├── ast_eq.rs │ │ │ │ ├── loc.rs │ │ │ │ ├── mod.rs │ │ │ │ ├── safe_unwrap.rs │ │ │ │ └── visit.rs │ │ │ └── writer/ │ │ │ ├── as_doc.rs │ │ │ ├── buf_writer.rs │ │ │ ├── markdown.rs │ │ │ ├── mod.rs │ │ │ └── traits.rs │ │ └── static/ │ │ ├── book.css │ │ └── book.toml │ ├── evm/ │ │ ├── abi/ │ │ │ ├── Cargo.toml │ │ │ └── src/ │ │ │ ├── Console.json │ │ │ ├── console/ │ │ │ │ ├── ds.rs │ │ │ │ ├── hh.rs │ │ │ │ └── mod.rs │ │ │ ├── console.py │ │ │ └── lib.rs │ │ ├── core/ │ │ │ ├── Cargo.toml │ │ │ ├── src/ │ │ │ │ ├── backend/ │ │ │ │ │ ├── cow.rs │ │ │ │ │ ├── diagnostic.rs │ │ │ │ │ ├── error.rs │ │ │ │ │ ├── in_memory_db.rs │ │ │ │ │ ├── mod.rs │ │ │ │ │ └── snapshot.rs │ │ │ │ ├── buffer.rs │ │ │ │ ├── bytecode.rs │ │ │ │ ├── constants.rs │ │ │ │ ├── decode.rs │ │ │ │ ├── either_evm.rs │ │ │ │ ├── env.rs │ │ │ │ ├── evm.rs │ │ │ │ ├── fork/ │ │ │ │ │ ├── database.rs │ │ │ │ │ ├── mod.rs │ │ │ │ │ └── multi.rs │ │ │ │ ├── hardfork.rs │ │ │ │ ├── ic.rs │ │ │ │ ├── lib.rs │ │ │ │ ├── opts.rs │ │ │ │ ├── precompiles.rs │ │ │ │ ├── state_snapshot.rs │ │ │ │ └── utils.rs │ │ │ └── test-data/ │ │ │ └── storage.json │ │ ├── coverage/ │ │ │ ├── Cargo.toml │ │ │ └── src/ │ │ │ ├── analysis.rs │ │ │ ├── anchors.rs │ │ │ ├── inspector.rs │ │ │ └── lib.rs │ │ ├── evm/ │ │ │ ├── Cargo.toml │ │ │ └── src/ │ │ │ ├── executors/ │ │ │ │ ├── builder.rs │ │ │ │ ├── corpus.rs │ │ │ │ ├── fuzz/ │ │ │ │ │ ├── mod.rs │ │ │ │ │ └── types.rs │ │ │ │ ├── invariant/ │ │ │ │ │ ├── error.rs │ │ │ │ │ ├── mod.rs │ │ │ │ │ ├── replay.rs │ │ │ │ │ ├── result.rs │ │ │ │ │ └── shrink.rs │ │ │ │ ├── mod.rs │ │ │ │ └── trace.rs │ │ │ ├── inspectors/ │ │ │ │ ├── chisel_state.rs │ │ │ │ ├── custom_printer.rs │ │ │ │ ├── logs.rs │ │ │ │ ├── mod.rs │ │ │ │ ├── revert_diagnostic.rs │ │ │ │ ├── script.rs │ │ │ │ └── stack.rs │ │ │ └── lib.rs │ │ ├── fuzz/ │ │ │ ├── Cargo.toml │ │ │ └── src/ │ │ │ ├── error.rs │ │ │ ├── inspector.rs │ │ │ ├── invariant/ │ │ │ │ ├── call_override.rs │ │ │ │ ├── filters.rs │ │ │ │ └── mod.rs │ │ │ ├── lib.rs │ │ │ └── strategies/ │ │ │ ├── calldata.rs │ │ │ ├── int.rs │ │ │ ├── invariants.rs │ │ │ ├── literals.rs │ │ │ ├── mod.rs │ │ │ ├── mutators.rs │ │ │ ├── param.rs │ │ │ ├── state.rs │ │ │ └── uint.rs │ │ ├── networks/ │ │ │ ├── Cargo.toml │ │ │ ├── README.md │ │ │ └── src/ │ │ │ ├── celo/ │ │ │ │ ├── mod.rs │ │ │ │ └── transfer.rs │ │ │ └── lib.rs │ │ ├── test-data/ │ │ │ └── solc-obj.json │ │ └── traces/ │ │ ├── Cargo.toml │ │ └── src/ │ │ ├── backtrace/ │ │ │ ├── mod.rs │ │ │ └── source_map.rs │ │ ├── debug/ │ │ │ ├── mod.rs │ │ │ └── sources.rs │ │ ├── decoder/ │ │ │ ├── mod.rs │ │ │ └── precompiles.rs │ │ ├── folded_stack_trace.rs │ │ ├── identifier/ │ │ │ ├── external.rs │ │ │ ├── local.rs │ │ │ ├── mod.rs │ │ │ └── signatures.rs │ │ └── lib.rs │ ├── fmt/ │ │ ├── Cargo.toml │ │ ├── README.md │ │ ├── src/ │ │ │ ├── lib.rs │ │ │ ├── pp/ │ │ │ │ ├── convenience.rs │ │ │ │ ├── helpers.rs │ │ │ │ ├── mod.rs │ │ │ │ └── ring.rs │ │ │ └── state/ │ │ │ ├── common.rs │ │ │ ├── mod.rs │ │ │ ├── sol.rs │ │ │ └── yul.rs │ │ ├── testdata/ │ │ │ ├── Annotation/ │ │ │ │ ├── fmt.sol │ │ │ │ └── original.sol │ │ │ ├── ArrayExpressions/ │ │ │ │ ├── fmt.sol │ │ │ │ └── original.sol │ │ │ ├── BlockComments/ │ │ │ │ ├── fmt.sol │ │ │ │ ├── original.sol │ │ │ │ └── tab.fmt.sol │ │ │ ├── BlockCommentsFunction/ │ │ │ │ ├── fmt.sol │ │ │ │ ├── original.sol │ │ │ │ └── tab.fmt.sol │ │ │ ├── CommentEmptyLine/ │ │ │ │ ├── fmt.sol │ │ │ │ └── original.sol │ │ │ ├── ConditionalOperatorExpression/ │ │ │ │ ├── fmt.sol │ │ │ │ └── original.sol │ │ │ ├── ConstructorDefinition/ │ │ │ │ ├── fmt.sol │ │ │ │ └── original.sol │ │ │ ├── ConstructorModifierStyle/ │ │ │ │ ├── fmt.sol │ │ │ │ └── original.sol │ │ │ ├── ContractDefinition/ │ │ │ │ ├── bracket-spacing.fmt.sol │ │ │ │ ├── contract-new-lines.fmt.sol │ │ │ │ ├── fmt.sol │ │ │ │ └── original.sol │ │ │ ├── DoWhileStatement/ │ │ │ │ ├── fmt.sol │ │ │ │ └── original.sol │ │ │ ├── DocComments/ │ │ │ │ ├── block.fmt.sol │ │ │ │ ├── fmt.sol │ │ │ │ ├── line.fmt.sol │ │ │ │ ├── original.sol │ │ │ │ ├── tab.fmt.sol │ │ │ │ └── wrap-comments.fmt.sol │ │ │ ├── EmitStatement/ │ │ │ │ ├── 120.compact.fmt.sol │ │ │ │ ├── 120.fmt.sol │ │ │ │ ├── fmt.sol │ │ │ │ └── original.sol │ │ │ ├── EnumDefinition/ │ │ │ │ ├── bracket-spacing.fmt.sol │ │ │ │ ├── fmt.sol │ │ │ │ └── original.sol │ │ │ ├── EnumVariants/ │ │ │ │ ├── fmt.sol │ │ │ │ └── original.sol │ │ │ ├── ErrorDefinition/ │ │ │ │ ├── fmt.sol │ │ │ │ └── original.sol │ │ │ ├── EventDefinition/ │ │ │ │ ├── fmt.sol │ │ │ │ └── original.sol │ │ │ ├── ForStatement/ │ │ │ │ ├── fmt.sol │ │ │ │ └── original.sol │ │ │ ├── FunctionCall/ │ │ │ │ ├── bracket-spacing.fmt.sol │ │ │ │ ├── fmt.sol │ │ │ │ └── original.sol │ │ │ ├── FunctionCallArgsStatement/ │ │ │ │ ├── bracket-spacing.fmt.sol │ │ │ │ ├── fmt.sol │ │ │ │ └── original.sol │ │ │ ├── FunctionDefinition/ │ │ │ │ ├── all-params.fmt.sol │ │ │ │ ├── all.fmt.sol │ │ │ │ ├── fmt.sol │ │ │ │ ├── original.sol │ │ │ │ ├── override-spacing.fmt.sol │ │ │ │ ├── params-always.fmt.sol │ │ │ │ └── params-multi.fmt.sol │ │ │ ├── FunctionDefinitionWithFunctionReturns/ │ │ │ │ ├── fmt.sol │ │ │ │ └── original.sol │ │ │ ├── FunctionType/ │ │ │ │ ├── fmt.sol │ │ │ │ └── original.sol │ │ │ ├── HexUnderscore/ │ │ │ │ ├── bytes.fmt.sol │ │ │ │ ├── fmt.sol │ │ │ │ ├── original.sol │ │ │ │ ├── preserve.fmt.sol │ │ │ │ └── remove.fmt.sol │ │ │ ├── IfStatement/ │ │ │ │ ├── block-multi.fmt.sol │ │ │ │ ├── block-single.fmt.sol │ │ │ │ ├── fmt.sol │ │ │ │ └── original.sol │ │ │ ├── IfStatement2/ │ │ │ │ ├── 120.fmt.sol │ │ │ │ ├── fmt.sol │ │ │ │ └── original.sol │ │ │ ├── ImportDirective/ │ │ │ │ ├── bracket-spacing.fmt.sol │ │ │ │ ├── fmt.sol │ │ │ │ ├── namespace-import-prefer-glob.fmt.sol │ │ │ │ ├── namespace-import-prefer-plain.fmt.sol │ │ │ │ ├── namespace-import-preserve.fmt.sol │ │ │ │ ├── original.sol │ │ │ │ ├── preserve-quote.fmt.sol │ │ │ │ ├── single-quote.fmt.sol │ │ │ │ └── single_line_import.fmt.sol │ │ │ ├── InlineDisable/ │ │ │ │ ├── fmt.sol │ │ │ │ └── original.sol │ │ │ ├── IntTypes/ │ │ │ │ ├── fmt.sol │ │ │ │ ├── original.sol │ │ │ │ ├── preserve.fmt.sol │ │ │ │ └── short.fmt.sol │ │ │ ├── LiteralExpression/ │ │ │ │ ├── fmt.sol │ │ │ │ ├── original.sol │ │ │ │ ├── preserve-quote.fmt.sol │ │ │ │ └── single-quote.fmt.sol │ │ │ ├── MappingType/ │ │ │ │ ├── fmt.sol │ │ │ │ └── original.sol │ │ │ ├── ModifierDefinition/ │ │ │ │ ├── fmt.sol │ │ │ │ ├── original.sol │ │ │ │ └── override-spacing.fmt.sol │ │ │ ├── NamedFunctionCallExpression/ │ │ │ │ ├── fmt.sol │ │ │ │ └── original.sol │ │ │ ├── NonKeywords/ │ │ │ │ ├── fmt.sol │ │ │ │ └── original.sol │ │ │ ├── NumberLiteralUnderscore/ │ │ │ │ ├── fmt.sol │ │ │ │ ├── original.sol │ │ │ │ ├── preserve.fmt.sol │ │ │ │ ├── remove.fmt.sol │ │ │ │ └── thousands.fmt.sol │ │ │ ├── OperatorExpressions/ │ │ │ │ ├── 120.fmt.sol │ │ │ │ ├── fmt.sol │ │ │ │ ├── original.sol │ │ │ │ └── pow-no-space.fmt.sol │ │ │ ├── PragmaDirective/ │ │ │ │ ├── fmt.sol │ │ │ │ └── original.sol │ │ │ ├── Repros/ │ │ │ │ ├── fmt.sol │ │ │ │ ├── original.sol │ │ │ │ ├── sorted.fmt.sol │ │ │ │ └── tab.fmt.sol │ │ │ ├── ReprosCalls/ │ │ │ │ ├── 110.fmt.sol │ │ │ │ ├── 120.fmt.sol │ │ │ │ ├── 80.fmt.sol │ │ │ │ ├── consistent.120.fmt.sol │ │ │ │ └── original.sol │ │ │ ├── ReprosFunctionDefs/ │ │ │ │ ├── all.120.fmt.sol │ │ │ │ └── original.sol │ │ │ ├── ReturnStatement/ │ │ │ │ ├── fmt.sol │ │ │ │ └── original.sol │ │ │ ├── RevertNamedArgsStatement/ │ │ │ │ ├── bracket-spacing.fmt.sol │ │ │ │ ├── fmt.sol │ │ │ │ └── original.sol │ │ │ ├── RevertStatement/ │ │ │ │ ├── fmt.sol │ │ │ │ └── original.sol │ │ │ ├── SimpleComments/ │ │ │ │ ├── fmt.sol │ │ │ │ ├── original.sol │ │ │ │ └── wrap-comments.fmt.sol │ │ │ ├── SortedImports/ │ │ │ │ ├── fmt.sol │ │ │ │ └── original.sol │ │ │ ├── StatementBlock/ │ │ │ │ ├── bracket-spacing.fmt.sol │ │ │ │ ├── fmt.sol │ │ │ │ └── original.sol │ │ │ ├── StructDefinition/ │ │ │ │ ├── bracket-spacing.fmt.sol │ │ │ │ ├── fmt.sol │ │ │ │ └── original.sol │ │ │ ├── StructFieldAccess/ │ │ │ │ ├── fmt.sol │ │ │ │ └── original.sol │ │ │ ├── ThisExpression/ │ │ │ │ ├── fmt.sol │ │ │ │ └── original.sol │ │ │ ├── TrailingComma/ │ │ │ │ ├── fmt.sol │ │ │ │ └── original.sol │ │ │ ├── TryStatement/ │ │ │ │ ├── fmt.sol │ │ │ │ └── original.sol │ │ │ ├── TypeDefinition/ │ │ │ │ ├── fmt.sol │ │ │ │ └── original.sol │ │ │ ├── UnitExpression/ │ │ │ │ ├── fmt.sol │ │ │ │ └── original.sol │ │ │ ├── UsingDirective/ │ │ │ │ ├── fmt.sol │ │ │ │ └── original.sol │ │ │ ├── VariableAssignment/ │ │ │ │ ├── bracket-spacing.fmt.sol │ │ │ │ ├── fmt.sol │ │ │ │ └── original.sol │ │ │ ├── VariableDefinition/ │ │ │ │ ├── fmt.sol │ │ │ │ ├── original.sol │ │ │ │ └── override-spacing.fmt.sol │ │ │ ├── WhileStatement/ │ │ │ │ ├── block-multi.fmt.sol │ │ │ │ ├── block-single.fmt.sol │ │ │ │ ├── fmt.sol │ │ │ │ └── original.sol │ │ │ ├── Yul/ │ │ │ │ ├── fmt.sol │ │ │ │ └── original.sol │ │ │ └── YulStrings/ │ │ │ ├── fmt.sol │ │ │ ├── original.sol │ │ │ ├── preserve-quote.fmt.sol │ │ │ └── single-quote.fmt.sol │ │ └── tests/ │ │ └── formatter.rs │ ├── forge/ │ │ ├── Cargo.toml │ │ ├── assets/ │ │ │ ├── .gitignoreTemplate │ │ │ ├── README.md │ │ │ ├── generated/ │ │ │ │ └── TestTemplate.t.sol │ │ │ ├── solidity/ │ │ │ │ ├── CounterTemplate.s.sol │ │ │ │ ├── CounterTemplate.sol │ │ │ │ ├── CounterTemplate.t.sol │ │ │ │ └── workflowTemplate.yml │ │ │ ├── tempo/ │ │ │ │ ├── MailTemplate.s.sol │ │ │ │ ├── MailTemplate.sol │ │ │ │ ├── MailTemplate.t.sol │ │ │ │ ├── README.md │ │ │ │ └── workflowTemplate.yml │ │ │ └── vyper/ │ │ │ ├── CounterTemplate.s.sol │ │ │ ├── CounterTemplate.t.sol │ │ │ ├── CounterTemplate.vy │ │ │ ├── ICounterTemplate.sol │ │ │ └── workflowTemplate.yml │ │ ├── bin/ │ │ │ └── main.rs │ │ ├── src/ │ │ │ ├── args.rs │ │ │ ├── cmd/ │ │ │ │ ├── bind.rs │ │ │ │ ├── bind_json.rs │ │ │ │ ├── build.rs │ │ │ │ ├── cache.rs │ │ │ │ ├── clone.rs │ │ │ │ ├── compiler.rs │ │ │ │ ├── config.rs │ │ │ │ ├── coverage.rs │ │ │ │ ├── create.rs │ │ │ │ ├── doc/ │ │ │ │ │ ├── mod.rs │ │ │ │ │ └── server.rs │ │ │ │ ├── eip712.rs │ │ │ │ ├── flatten.rs │ │ │ │ ├── fmt.rs │ │ │ │ ├── geiger.rs │ │ │ │ ├── generate/ │ │ │ │ │ └── mod.rs │ │ │ │ ├── init.rs │ │ │ │ ├── inspect.rs │ │ │ │ ├── install.rs │ │ │ │ ├── lint.rs │ │ │ │ ├── mod.rs │ │ │ │ ├── remappings.rs │ │ │ │ ├── remove.rs │ │ │ │ ├── selectors.rs │ │ │ │ ├── snapshot.rs │ │ │ │ ├── soldeer.rs │ │ │ │ ├── test/ │ │ │ │ │ ├── filter.rs │ │ │ │ │ ├── mod.rs │ │ │ │ │ └── summary.rs │ │ │ │ ├── tree.rs │ │ │ │ ├── update.rs │ │ │ │ └── watch.rs │ │ │ ├── coverage.rs │ │ │ ├── gas_report.rs │ │ │ ├── lib.rs │ │ │ ├── lockfile.rs │ │ │ ├── multi_runner.rs │ │ │ ├── opts.rs │ │ │ ├── progress.rs │ │ │ ├── result.rs │ │ │ └── runner.rs │ │ └── tests/ │ │ ├── cli/ │ │ │ ├── backtrace.rs │ │ │ ├── bind.rs │ │ │ ├── bind_json.rs │ │ │ ├── build.rs │ │ │ ├── cache.rs │ │ │ ├── cmd.rs │ │ │ ├── compiler.rs │ │ │ ├── config.rs │ │ │ ├── constants.rs │ │ │ ├── context.rs │ │ │ ├── coverage.rs │ │ │ ├── create.rs │ │ │ ├── debug.rs │ │ │ ├── doc.rs │ │ │ ├── eip712.rs │ │ │ ├── ext_integration.rs │ │ │ ├── failure_assertions.rs │ │ │ ├── fmt.rs │ │ │ ├── fmt_integration.rs │ │ │ ├── inline_config.rs │ │ │ ├── install.rs │ │ │ ├── json.rs │ │ │ ├── lint/ │ │ │ │ └── geiger.rs │ │ │ ├── lint.rs │ │ │ ├── main.rs │ │ │ ├── multi_script.rs │ │ │ ├── precompiles.rs │ │ │ ├── script.rs │ │ │ ├── soldeer.rs │ │ │ ├── svm.rs │ │ │ ├── test_cmd/ │ │ │ │ ├── core.rs │ │ │ │ ├── fuzz.rs │ │ │ │ ├── invariant/ │ │ │ │ │ ├── common.rs │ │ │ │ │ ├── mod.rs │ │ │ │ │ ├── storage.rs │ │ │ │ │ └── target.rs │ │ │ │ ├── logs.rs │ │ │ │ ├── mod.rs │ │ │ │ ├── repros.rs │ │ │ │ ├── spec.rs │ │ │ │ ├── table.rs │ │ │ │ └── trace.rs │ │ │ ├── test_optimizer.rs │ │ │ ├── utils.rs │ │ │ ├── verify.rs │ │ │ ├── verify_bytecode.rs │ │ │ └── version.rs │ │ ├── fixtures/ │ │ │ ├── CreateXScript.sol │ │ │ ├── ExpectCallFailures.t.sol │ │ │ ├── ExpectCreateFailures.t.sol │ │ │ ├── ExpectEmitFailures.t.sol │ │ │ ├── ExpectEmitParamFailures.t.sol │ │ │ ├── ExpectEmitParamHarness.sol │ │ │ ├── ExpectRevertFailures.t.sol │ │ │ ├── MemSafetyFailures.t.sol │ │ │ ├── ScriptVerify.sol │ │ │ ├── SimpleContractTestNonVerbose.json │ │ │ ├── SimpleContractTestVerbose.json │ │ │ └── backtraces/ │ │ │ ├── Backtrace.t.sol │ │ │ ├── DelegateCall.sol │ │ │ ├── ForkBacktrace.t.sol │ │ │ ├── ForkedERC20Wrapper.sol │ │ │ ├── LibraryBacktrace.t.sol │ │ │ ├── LibraryConsumer.sol │ │ │ ├── MultipleLibraryBacktrace.t.sol │ │ │ ├── MultipleLibraryConsumer.sol │ │ │ ├── NestedCalls.sol │ │ │ ├── SimpleRevert.sol │ │ │ ├── StaticCall.sol │ │ │ └── libraries/ │ │ │ ├── ExternalMathLib.sol │ │ │ ├── InternalMathLib.sol │ │ │ └── MultipleLibraries.sol │ │ └── ui.rs │ ├── linking/ │ │ ├── Cargo.toml │ │ └── src/ │ │ └── lib.rs │ ├── lint/ │ │ ├── Cargo.toml │ │ ├── README.md │ │ ├── src/ │ │ │ ├── lib.rs │ │ │ ├── linter/ │ │ │ │ ├── early.rs │ │ │ │ ├── late.rs │ │ │ │ └── mod.rs │ │ │ └── sol/ │ │ │ ├── codesize/ │ │ │ │ ├── mod.rs │ │ │ │ └── unwrapped_modifier_logic.rs │ │ │ ├── gas/ │ │ │ │ ├── custom_errors.rs │ │ │ │ ├── keccak.rs │ │ │ │ └── mod.rs │ │ │ ├── high/ │ │ │ │ ├── incorrect_shift.rs │ │ │ │ ├── mod.rs │ │ │ │ └── unchecked_calls.rs │ │ │ ├── info/ │ │ │ │ ├── imports.rs │ │ │ │ ├── interface_naming.rs │ │ │ │ ├── mixed_case.rs │ │ │ │ ├── mod.rs │ │ │ │ ├── multi_contract_file.rs │ │ │ │ ├── named_struct_fields.rs │ │ │ │ ├── pascal_case.rs │ │ │ │ ├── screaming_snake_case.rs │ │ │ │ └── unsafe_cheatcodes.rs │ │ │ ├── macros.rs │ │ │ ├── med/ │ │ │ │ ├── div_mul.rs │ │ │ │ ├── mod.rs │ │ │ │ └── unsafe_typecast.rs │ │ │ └── mod.rs │ │ └── testdata/ │ │ ├── .gitignore │ │ ├── CustomErrors.sol │ │ ├── CustomErrors.stderr │ │ ├── DivideBeforeMultiply.sol │ │ ├── DivideBeforeMultiply.stderr │ │ ├── Imports.sol │ │ ├── Imports.stderr │ │ ├── IncorrectShift.sol │ │ ├── IncorrectShift.stderr │ │ ├── Keccak256.sol │ │ ├── Keccak256.stderr │ │ ├── MixedCase.sol │ │ ├── MixedCase.stderr │ │ ├── MultiContractFile.sol │ │ ├── MultiContractFile.stderr │ │ ├── MultiContractFile_InterfaceLibrary.sol │ │ ├── MultiContractFile_InterfaceLibrary.stderr │ │ ├── NamedStructFields.sol │ │ ├── NamedStructFields.stderr │ │ ├── ScreamingSnakeCase.sol │ │ ├── ScreamingSnakeCase.stderr │ │ ├── SoloInterfaces.sol │ │ ├── StructPascalCase.sol │ │ ├── StructPascalCase.stderr │ │ ├── UncheckedCall.sol │ │ ├── UncheckedCall.stderr │ │ ├── UncheckedTransferERC20.sol │ │ ├── UncheckedTransferERC20.stderr │ │ ├── UnsafeCheatcodes.sol │ │ ├── UnsafeCheatcodes.stderr │ │ ├── UnsafeTypecast.sol │ │ ├── UnsafeTypecast.stderr │ │ ├── UnwrappedModifierLogic.sol │ │ ├── UnwrappedModifierLogic.stderr │ │ └── auxiliary/ │ │ ├── ImportsAnotherFile.sol │ │ ├── ImportsAnotherFile2.sol │ │ ├── ImportsConstants.sol │ │ ├── ImportsFile.sol │ │ ├── ImportsSomeFile.sol │ │ ├── ImportsSomeFile2.sol │ │ ├── ImportsTypes.sol │ │ ├── ImportsUtils.sol │ │ ├── ImportsUtils2.sol │ │ └── Test.sol │ ├── macros/ │ │ ├── Cargo.toml │ │ └── src/ │ │ ├── cheatcodes.rs │ │ ├── console_fmt.rs │ │ └── lib.rs │ ├── primitives/ │ │ ├── Cargo.toml │ │ └── src/ │ │ ├── lib.rs │ │ ├── network/ │ │ │ ├── mod.rs │ │ │ ├── receipt.rs │ │ │ └── transaction.rs │ │ └── transaction/ │ │ ├── envelope.rs │ │ ├── mod.rs │ │ ├── receipt.rs │ │ └── request.rs │ ├── script/ │ │ ├── Cargo.toml │ │ └── src/ │ │ ├── broadcast.rs │ │ ├── build.rs │ │ ├── execute.rs │ │ ├── lib.rs │ │ ├── multi_sequence.rs │ │ ├── progress.rs │ │ ├── providers.rs │ │ ├── receipts.rs │ │ ├── runner.rs │ │ ├── sequence.rs │ │ ├── simulate.rs │ │ ├── transaction.rs │ │ └── verify.rs │ ├── script-sequence/ │ │ ├── Cargo.toml │ │ └── src/ │ │ ├── lib.rs │ │ ├── reader.rs │ │ ├── sequence.rs │ │ └── transaction.rs │ ├── sol-macro-gen/ │ │ ├── Cargo.toml │ │ └── src/ │ │ ├── lib.rs │ │ └── sol_macro_gen.rs │ ├── test-utils/ │ │ ├── Cargo.toml │ │ └── src/ │ │ ├── etherscan.rs │ │ ├── ext.rs │ │ ├── fd_lock.rs │ │ ├── filter.rs │ │ ├── lib.rs │ │ ├── macros.rs │ │ ├── prj.rs │ │ ├── rpc.rs │ │ ├── script.rs │ │ ├── ui_runner.rs │ │ └── util.rs │ ├── verify/ │ │ ├── Cargo.toml │ │ └── src/ │ │ ├── bytecode.rs │ │ ├── etherscan/ │ │ │ ├── flatten.rs │ │ │ ├── mod.rs │ │ │ └── standard_json.rs │ │ ├── lib.rs │ │ ├── provider.rs │ │ ├── retry.rs │ │ ├── sourcify.rs │ │ ├── types.rs │ │ ├── utils.rs │ │ └── verify.rs │ └── wallets/ │ ├── Cargo.toml │ └── src/ │ ├── error.rs │ ├── lib.rs │ ├── opts.rs │ ├── signer.rs │ ├── utils.rs │ ├── wallet_browser/ │ │ ├── app/ │ │ │ ├── assets/ │ │ │ │ ├── index.html │ │ │ │ ├── main.js │ │ │ │ └── styles.css │ │ │ └── mod.rs │ │ ├── error.rs │ │ ├── handlers.rs │ │ ├── mod.rs │ │ ├── opts.rs │ │ ├── queue.rs │ │ ├── router.rs │ │ ├── server.rs │ │ ├── signer.rs │ │ ├── state.rs │ │ └── types.rs │ ├── wallet_multi/ │ │ └── mod.rs │ └── wallet_raw/ │ └── mod.rs ├── deny.toml ├── docs/ │ └── dev/ │ ├── README.md │ ├── architecture.md │ ├── cheatcodes.md │ ├── debugging.md │ ├── lintrules.md │ ├── networks.md │ └── scripting.md ├── dprint.json ├── flake.nix ├── foundryup/ │ ├── README.md │ ├── foundryup │ └── install ├── npm/ │ ├── .gitignore │ ├── @foundry-rs/ │ │ ├── anvil/ │ │ │ ├── README.md │ │ │ └── package.json │ │ ├── cast/ │ │ │ ├── README.md │ │ │ └── package.json │ │ ├── chisel/ │ │ │ ├── README.md │ │ │ └── package.json │ │ └── forge/ │ │ ├── README.md │ │ └── package.json │ ├── README.md │ ├── bunfig.toml │ ├── env.d.ts │ ├── package.json │ ├── scripts/ │ │ ├── check.sh │ │ ├── prepublish.mjs │ │ ├── publish-meta.mjs │ │ ├── publish.mjs │ │ └── stage-from-artifact.mjs │ ├── src/ │ │ ├── bin.mjs │ │ ├── const.mjs │ │ ├── generate-package-json.mjs │ │ └── install.mjs │ └── tsconfig.json ├── rustfmt.toml ├── testdata/ │ ├── .gitignore │ ├── README.md │ ├── default/ │ │ ├── cheats/ │ │ │ ├── AccessList.t.sol │ │ │ ├── Addr.t.sol │ │ │ ├── ArbitraryStorage.t.sol │ │ │ ├── Assert.t.sol │ │ │ ├── Assume.t.sol │ │ │ ├── AssumeNoRevert.t.sol │ │ │ ├── AttachBlob.t.sol │ │ │ ├── AttachDelegation.t.sol │ │ │ ├── Bank.t.sol │ │ │ ├── Base64.t.sol │ │ │ ├── BlobBaseFee.t.sol │ │ │ ├── Blobhashes.t.sol │ │ │ ├── Broadcast.t.sol │ │ │ ├── BroadcastRawTransaction.t.sol │ │ │ ├── ChainId.t.sol │ │ │ ├── CloneAccount.t.sol │ │ │ ├── Cool.t.sol │ │ │ ├── CopyStorage.t.sol │ │ │ ├── CurrentFilePath.t.sol │ │ │ ├── Deal.t.sol │ │ │ ├── DeployCode.t.sol │ │ │ ├── Derive.t.sol │ │ │ ├── Ed25519.t.sol │ │ │ ├── EnsNamehash.t.sol │ │ │ ├── Env.t.sol │ │ │ ├── Etch.t.sol │ │ │ ├── ExecuteTransaction.t.sol │ │ │ ├── ExpectCall.t.sol │ │ │ ├── ExpectCreate.t.sol │ │ │ ├── ExpectEmit.t.sol │ │ │ ├── ExpectRevert.t.sol │ │ │ ├── Fee.t.sol │ │ │ ├── Ffi.t.sol │ │ │ ├── Fork.t.sol │ │ │ ├── Fork2.t.sol │ │ │ ├── Fs.t.sol │ │ │ ├── GasMetering.t.sol │ │ │ ├── GetArtifactPath.t.sol │ │ │ ├── GetBlockTimestamp.t.sol │ │ │ ├── GetChain.t.sol │ │ │ ├── GetCode.t.sol │ │ │ ├── GetDeployedCode.t.sol │ │ │ ├── GetFoundryVersion.t.sol │ │ │ ├── GetLabel.t.sol │ │ │ ├── GetNonce.t.sol │ │ │ ├── GetRawBlockHeader.t.sol │ │ │ ├── GetStorageSlots.t.sol │ │ │ ├── Json.t.sol │ │ │ ├── Label.t.sol │ │ │ ├── Load.t.sol │ │ │ ├── Mapping.t.sol │ │ │ ├── MemSafety.t.sol │ │ │ ├── MockCall.t.sol │ │ │ ├── MockCalls.t.sol │ │ │ ├── MockFunction.t.sol │ │ │ ├── Nonce.t.sol │ │ │ ├── Parse.t.sol │ │ │ ├── Prank.t.sol │ │ │ ├── Prevrandao.t.sol │ │ │ ├── ProjectRoot.t.sol │ │ │ ├── Prompt.t.sol │ │ │ ├── RandomAddress.t.sol │ │ │ ├── RandomBytes.t.sol │ │ │ ├── RandomCheatcodes.t.sol │ │ │ ├── RandomUint.t.sol │ │ │ ├── ReadCallers.t.sol │ │ │ ├── Record.t.sol │ │ │ ├── RecordAccountAccesses.t.sol │ │ │ ├── RecordDebugTrace.t.sol │ │ │ ├── RecordLogs.t.sol │ │ │ ├── Remember.t.sol │ │ │ ├── ResetNonce.t.sol │ │ │ ├── Rlp.t.sol │ │ │ ├── Roll.t.sol │ │ │ ├── RpcUrls.t.sol │ │ │ ├── Seed.t.sol │ │ │ ├── SetBlockhash.t.sol │ │ │ ├── SetNonce.t.sol │ │ │ ├── SetNonceUnsafe.t.sol │ │ │ ├── Setup.t.sol │ │ │ ├── Shuffle.t.sol │ │ │ ├── Sign.t.sol │ │ │ ├── SignP256.t.sol │ │ │ ├── Skip.t.sol │ │ │ ├── Sleep.t.sol │ │ │ ├── Sort.t.sol │ │ │ ├── StateDiffBytesString.t.sol │ │ │ ├── StateDiffMappings.t.sol │ │ │ ├── StateDiffStorageLayout.t.sol │ │ │ ├── StateDiffStructTest.t.sol │ │ │ ├── StateSnapshots.t.sol │ │ │ ├── StorageSlotState.t.sol │ │ │ ├── Store.t.sol │ │ │ ├── StringUtils.t.sol │ │ │ ├── ToString.t.sol │ │ │ ├── Toml.t.sol │ │ │ ├── Travel.t.sol │ │ │ ├── TryFfi.sol │ │ │ ├── UnixTime.t.sol │ │ │ ├── Wallet.t.sol │ │ │ ├── Warp.t.sol │ │ │ ├── dumpState.t.sol │ │ │ ├── getBlockNumber.t.sol │ │ │ └── loadAllocs.t.sol │ │ ├── core/ │ │ │ ├── Abstract.t.sol │ │ │ ├── BadSigAfterInvariant.t.sol │ │ │ ├── ContractEnvironment.t.sol │ │ │ ├── Reverting.t.sol │ │ │ └── SetupConsistency.t.sol │ │ ├── fork/ │ │ │ ├── DssExecLib.sol │ │ │ ├── ForkSame_1.t.sol │ │ │ ├── ForkSame_2.t.sol │ │ │ └── LaunchFork.t.sol │ │ ├── fs/ │ │ │ ├── Disabled.t.sol │ │ │ └── ReadOnly.sol │ │ ├── inline/ │ │ │ ├── FuzzInlineConf.t.sol │ │ │ └── InvariantInlineConf.t.sol │ │ ├── linking/ │ │ │ ├── cycle/ │ │ │ │ └── Cycle.t.sol │ │ │ ├── duplicate/ │ │ │ │ └── Duplicate.t.sol │ │ │ ├── nested/ │ │ │ │ └── Nested.t.sol │ │ │ ├── samefile_union/ │ │ │ │ ├── Libs.sol │ │ │ │ └── SameFileUnion.t.sol │ │ │ └── simple/ │ │ │ └── Simple.t.sol │ │ ├── repros/ │ │ │ ├── Issue10302.t.sol │ │ │ ├── Issue10477.t.sol │ │ │ ├── Issue10527.t.sol │ │ │ ├── Issue10552.t.sol │ │ │ ├── Issue10586.t.sol │ │ │ ├── Issue10957.t.sol │ │ │ ├── Issue11353.t.sol │ │ │ ├── Issue11616.t.sol │ │ │ ├── Issue12075.t.sol │ │ │ ├── Issue2623.t.sol │ │ │ ├── Issue2629.t.sol │ │ │ ├── Issue2723.t.sol │ │ │ ├── Issue2898.t.sol │ │ │ ├── Issue2956.t.sol │ │ │ ├── Issue2984.t.sol │ │ │ ├── Issue3077.t.sol │ │ │ ├── Issue3110.t.sol │ │ │ ├── Issue3119.t.sol │ │ │ ├── Issue3190.t.sol │ │ │ ├── Issue3192.t.sol │ │ │ ├── Issue3220.t.sol │ │ │ ├── Issue3221.t.sol │ │ │ ├── Issue3223.t.sol │ │ │ ├── Issue3653.t.sol │ │ │ ├── Issue3661.t.sol │ │ │ ├── Issue3674.t.sol │ │ │ ├── Issue3685.t.sol │ │ │ ├── Issue3703.t.sol │ │ │ ├── Issue3708.t.sol │ │ │ ├── Issue3753.t.sol │ │ │ ├── Issue3792.t.sol │ │ │ ├── Issue4232.t.sol │ │ │ ├── Issue4402.t.sol │ │ │ ├── Issue4586.t.sol │ │ │ ├── Issue4630.t.sol │ │ │ ├── Issue4640.t.sol │ │ │ ├── Issue5038.t.sol │ │ │ ├── Issue5529.t.sol │ │ │ ├── Issue5739.t.sol │ │ │ ├── Issue5808.t.sol │ │ │ ├── Issue5929.t.sol │ │ │ ├── Issue5935.t.sol │ │ │ ├── Issue5948.t.sol │ │ │ ├── Issue6006.t.sol │ │ │ ├── Issue6032.t.sol │ │ │ ├── Issue6070.t.sol │ │ │ ├── Issue6115.t.sol │ │ │ ├── Issue6180.t.sol │ │ │ ├── Issue6293.t.sol │ │ │ ├── Issue6437.t.sol │ │ │ ├── Issue6538.t.sol │ │ │ ├── Issue6554.t.sol │ │ │ ├── Issue6616.t.sol │ │ │ ├── Issue6634.t.sol │ │ │ ├── Issue6643.t.sol │ │ │ ├── Issue6759.t.sol │ │ │ ├── Issue6966.t.sol │ │ │ ├── Issue7238.t.sol │ │ │ ├── Issue7457.t.sol │ │ │ ├── Issue7481.t.sol │ │ │ ├── Issue8004.t.sol │ │ │ ├── Issue8006.t.sol │ │ │ ├── Issue8168.t.sol │ │ │ ├── Issue8277.t.sol │ │ │ ├── Issue8287.t.sol │ │ │ ├── Issue8566.t.sol │ │ │ ├── Issue8639.t.sol │ │ │ ├── Issue8971.t.sol │ │ │ └── Issue9643.t.sol │ │ ├── spec/ │ │ │ └── ShanghaiCompat.t.sol │ │ └── vyper/ │ │ └── CounterTest.vy │ ├── etherscan/ │ │ ├── 0x044b75f554b886A065b9567891e45c79542d7357/ │ │ │ ├── creation_data.json │ │ │ └── metadata.json │ │ ├── 0x35Fb958109b70799a8f9Bc2a8b1Ee4cC62034193/ │ │ │ ├── creation_data.json │ │ │ └── metadata.json │ │ ├── 0x3a23F943181408EAC424116Af7b7790c94Cb97a5/ │ │ │ ├── creation_data.json │ │ │ └── metadata.json │ │ ├── 0x71356E37e0368Bd10bFDbF41dC052fE5FA24cD05/ │ │ │ ├── creation_data.json │ │ │ └── metadata.json │ │ ├── 0x8B3D32cf2bb4d0D16656f4c0b04Fa546274f1545/ │ │ │ ├── creation_data.json │ │ │ └── metadata.json │ │ ├── 0x9AB6b21cDF116f611110b048987E58894786C244/ │ │ │ ├── creation_data.json │ │ │ └── metadata.json │ │ ├── 0x9d27527Ada2CF29fBDAB2973cfa243845a08Bd3F/ │ │ │ ├── creation_data.json │ │ │ └── metadata.json │ │ └── 0xDb53f47aC61FE54F456A4eb3E09832D08Dd7BEec/ │ │ ├── creation_data.json │ │ └── metadata.json │ ├── fixtures/ │ │ ├── Derive/ │ │ │ ├── mnemonic_chinese_simplified.txt │ │ │ ├── mnemonic_chinese_traditional.txt │ │ │ ├── mnemonic_czech.txt │ │ │ ├── mnemonic_english.txt │ │ │ ├── mnemonic_french.txt │ │ │ ├── mnemonic_italian.txt │ │ │ ├── mnemonic_japanese.txt │ │ │ ├── mnemonic_korean.txt │ │ │ ├── mnemonic_portuguese.txt │ │ │ └── mnemonic_spanish.txt │ │ ├── Dir/ │ │ │ ├── depth1 │ │ │ └── nested/ │ │ │ ├── depth2 │ │ │ └── nested2/ │ │ │ └── depth3 │ │ ├── File/ │ │ │ ├── ignored/ │ │ │ │ └── .gitignore │ │ │ └── read.txt │ │ ├── GetCode/ │ │ │ ├── HardhatWorkingContract.json │ │ │ ├── HuffWorkingContract.json │ │ │ ├── Override.json │ │ │ ├── Override.sol │ │ │ ├── UnlinkedContract.sol │ │ │ ├── WorkingContract.json │ │ │ └── WorkingContract.sol │ │ ├── Json/ │ │ │ ├── Issue4402.json │ │ │ ├── Issue4630.json │ │ │ ├── nested_json_struct.json │ │ │ ├── test.json │ │ │ ├── test_allocs.json │ │ │ ├── whole_json.json │ │ │ ├── write_complex_test.json │ │ │ └── write_test.json │ │ ├── Rpc/ │ │ │ ├── README.md │ │ │ ├── balance_params.json │ │ │ └── eth_getLogs.json │ │ ├── Toml/ │ │ │ ├── Issue4402.toml │ │ │ ├── nested_toml_struct.toml │ │ │ ├── test.toml │ │ │ ├── whole_toml.toml │ │ │ ├── write_complex_test.toml │ │ │ └── write_test.toml │ │ ├── broadcast.log.json │ │ └── broadcast.sensitive.log.json │ ├── forge-std-rev │ ├── foundry.toml │ ├── multi-version/ │ │ ├── Counter.sol │ │ ├── Importer.sol │ │ └── cheats/ │ │ ├── GetCode.t.sol │ │ └── GetCode17.t.sol │ ├── paris/ │ │ ├── cheats/ │ │ │ ├── Fork.t.sol │ │ │ ├── GasSnapshots.t.sol │ │ │ └── LastCallGas.t.sol │ │ ├── core/ │ │ │ └── BeforeTest.t.sol │ │ ├── fork/ │ │ │ └── Transact.t.sol │ │ └── spec/ │ │ └── ShanghaiCompat.t.sol │ ├── src/ │ │ ├── Counter.vy │ │ └── ICounter.vyi │ └── utils/ │ ├── DSTest.sol │ ├── Test.sol │ ├── Vm.sol │ └── console.sol └── typos.toml ================================================ FILE CONTENTS ================================================ ================================================ FILE: .cargo/config.toml ================================================ [alias] cheats = "test -p foundry-cheatcodes-spec --features schema tests::" test-debugger = "test -p forge --test cli manual_debug_setup -- --include-ignored --nocapture" bless-lints = "test -p forge --test ui -- --bless" # Increase the stack size to 10MB for Windows targets, which is in line with Linux # (whereas default for Windows is 1MB). [target.x86_64-pc-windows-msvc] rustflags = ["-Clink-arg=/STACK:10000000"] [target.i686-pc-windows-msvc] rustflags = ["-Clink-arg=/STACK:10000000"] ================================================ FILE: .config/nextest.toml ================================================ [test-groups] [profile.default] retries = { backoff = "exponential", count = 2, delay = "5s", jitter = true } slow-timeout = { period = "30s", terminate-after = 3 } # Exclude flaky tests from regular CI runs - they run in nightly test-flaky workflow default-filter = "not test(/flaky_/)" [[profile.default.overrides]] filter = "test(/ext_integration/)" slow-timeout = { period = "5m", terminate-after = 4 } # Do not re-run so that `cargo cheats` is ran locally. [[profile.default.overrides]] filter = "package(foundry-cheatcodes-spec)" retries = 0 # Profile for running flaky tests (used by nightly CI and for local debugging) # Run with: cargo nextest run --profile flaky [profile.flaky] default-filter = "test(/flaky_/)" retries = { backoff = "exponential", count = 5, delay = "10s", jitter = true } slow-timeout = { period = "60s", terminate-after = 3 } ================================================ FILE: .devcontainer/Dockerfile.dev ================================================ FROM ubuntu:22.04 ARG USERNAME=foundry ARG USER_UID=1000 ARG USER_GID=$USER_UID ARG PYTHON_VERSION=3.14 ARG NODE_MAJOR=20 ARG VYPER_VERSION=0.4.3 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 ================================================ FILE: .devcontainer/devcontainer.json ================================================ // 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" ] } ================================================ FILE: .dockerignore ================================================ target .github ================================================ FILE: .git-blame-ignore-revs ================================================ # Since version 2.23 (released in August 2019), git-blame has a feature # to ignore or bypass certain commits. # # This file contains a list of commits that are not likely what you # are looking for in a blame, such as mass reformatting or renaming. # You can set this file as a default ignore file for blame by running # the following command. # # $ git config blame.ignoreRevsFile .git-blame-ignore-revs # fmt: all (#3398) 860d083183b51a6f8d865408ef1a44aa694d6862 # chore: bump to rust edition 2024 (#10802) 710a1584aae8e0f8ca8d5ba552632dc72381091e ================================================ FILE: .gitattributes ================================================ crates/cheatcodes/assets/*.json linguist-generated testdata/cheats/Vm.sol linguist-generated bun.lock linguist-generated # See *.rs diff=rust crates/lint/testdata/* text eol=lf testdata/fixtures/**/* eol=lf dprint.json linguist-language=JSON-with-Comments .devcontainer/devcontainer.json linguist-language=JSON-with-Comments .env.example linguist-language=Dotenv ================================================ FILE: .github/CODEOWNERS ================================================ * @danipopes @mattsse @grandizzy @yash-atreya @zerosnacks @onbjerg @0xrusowsky ================================================ FILE: .github/FLAKY_TEST_FAILURE_TEMPLATE.md ================================================ --- title: "bug: flaky tests workflow failed" labels: P-normal, T-bug --- The nightly flaky tests workflow has failed. This indicates external API rate limiting, RPC reliability issues, or other intermittent failures that may affect users. Check the [flaky tests workflow page]({{ env.WORKFLOW_URL }}) for details. This issue was raised by the workflow at `.github/workflows/test-flaky.yml`. ================================================ FILE: .github/FLAKY_TEST_ISOLATE_FAILURE_TEMPLATE.md ================================================ --- title: "bug: flaky tests workflow failed (isolate)" labels: P-normal, T-bug --- The nightly flaky tests workflow (with isolation mode enabled) has failed. This indicates external API rate limiting, RPC reliability issues, or other intermittent failures that may affect users. Check the [flaky tests workflow page]({{ env.WORKFLOW_URL }}) for details. This issue was raised by the workflow at `.github/workflows/test-isolate.yml`. ================================================ FILE: .github/INTEGRATION_FAILURE.md ================================================ --- title: "bug: long-running integration tests failed" labels: P-high, T-bug --- The heavy (long-running) integration tests have failed. This indicates a regression in Foundry. Check the [heavy integration tests workflow page]({{ env.WORKFLOW_URL }}) for details. This issue was raised by the workflow at `.github/workflows/heavy-integration.yml`. ================================================ FILE: .github/ISSUE_TEMPLATE/BUG-FORM.yml ================================================ name: Bug report description: File a bug report type: Bug labels: ["T-bug", "T-needs-triage"] body: - type: markdown attributes: value: | Please ensure that the bug has not already been filed in the issue tracker. Thanks for taking the time to report this bug! - type: dropdown id: component attributes: label: Component description: What component is the bug in? multiple: true options: - Forge - Cast - Anvil - Foundryup - Chisel - Other (please describe) validations: required: true - type: checkboxes attributes: label: Have you ensured that all of these are up to date? options: - label: Foundry - label: Foundryup - type: input attributes: label: What version of Foundry are you on? placeholder: "Run forge --version and paste the output here" - type: input attributes: label: What version of Foundryup are you on? placeholder: "Run foundryup --version and paste the output here" - type: input attributes: label: What command(s) is the bug in? description: Leave empty if not relevant placeholder: "For example: forge test" - type: dropdown attributes: label: Operating System description: What operating system are you on? options: - Windows - macOS (Intel) - macOS (Apple Silicon) - Linux - type: textarea id: description attributes: label: Describe the bug description: Please include relevant Solidity snippets as well if relevant. validations: required: true ================================================ FILE: .github/ISSUE_TEMPLATE/FEATURE-FORM.yml ================================================ name: Feature request description: Suggest a feature type: Feature labels: ["T-feature", "T-needs-triage"] body: - type: markdown attributes: value: | Please ensure that the feature has not already been requested in the issue tracker. Thanks for helping us improve Foundry! - type: dropdown id: component attributes: label: Component description: What component is the feature for? multiple: true options: - Forge - Cast - Anvil - Foundryup - Chisel - Other (please describe) validations: required: true - type: textarea id: description attributes: label: Describe the feature you would like description: Please also describe what the feature is aiming to solve, if relevant. validations: required: true - type: textarea attributes: label: Additional context description: Add any other context to the feature (like screenshots, resources) ================================================ FILE: .github/ISSUE_TEMPLATE/config.yml ================================================ blank_issues_enabled: true contact_links: - name: Support url: https://t.me/foundry_support about: This issue tracker is only for bugs and feature requests. Support is available on Telegram! ================================================ FILE: .github/PULL_REQUEST_TEMPLATE.md ================================================ ## Motivation ## Solution ## PR Checklist - [ ] Added Tests - [ ] Added Documentation - [ ] Breaking changes ================================================ FILE: .github/RELEASE_FAILURE_ISSUE_TEMPLATE.md ================================================ --- title: "bug: release workflow failed" labels: P-high, T-bug --- The release workflow has failed. Some or all binaries might have not been published correctly. Check the [release workflow page]({{ env.WORKFLOW_URL }}) for details. This issue was raised by the workflow at `.github/workflows/release.yml`. ================================================ FILE: .github/TEST_ISOLATE_FAILURE_TEMPLATE.md ================================================ --- title: "bug: test-isolate workflow failed" labels: P-high, T-bug --- The daily test-isolate workflow (tests with isolation mode enabled) has failed. This indicates a regression in Foundry's test isolation behavior. Check the [test-isolate workflow page]({{ env.WORKFLOW_URL }}) for details. This issue was raised by the workflow at `.github/workflows/test-isolate.yml`. ================================================ FILE: .github/changelog.json ================================================ { "categories": [ { "title": "## Breaking changes", "labels": ["T-likely-breaking "] }, { "title": "## Anvil Features", "labels": ["C-anvil", "T-feature"], "exhaustive": true, "exhaustive_rules": false }, { "title": "## Anvil Fixes", "labels": ["C-anvil", "T-bug"], "exhaustive": true, "exhaustive_rules": false }, { "title": "## Cast Features", "labels": ["C-cast", "T-feature"], "exhaustive": true, "exhaustive_rules": false }, { "title": "## Cast Fixes", "labels": ["C-cast", "T-bug"], "exhaustive": true, "exhaustive_rules": false }, { "title": "## Chisel Features", "labels": ["C-chisel", "T-feature"], "exhaustive": true, "exhaustive_rules": false }, { "title": "## Chisel Fixes", "labels": ["C-chisel", "T-bug"], "exhaustive": true, "exhaustive_rules": false }, { "title": "## Forge Features", "labels": ["C-forge", "T-feature"], "exhaustive": true, "exhaustive_rules": false }, { "title": "## Forge Fixes", "labels": ["C-forge", "T-bug"], "exhaustive": true, "exhaustive_rules": false }, { "title": "## Performance improvements", "labels": ["T-perf"] } ], "ignore_labels": ["L-ignore"], "template": "${{CHANGELOG}}\n## Other\n\n${{UNCATEGORIZED}}\n## Full Changelog:\n ${{RELEASE_DIFF}}", "pr_template": "- ${{TITLE}} (#${{NUMBER}}) by @${{AUTHOR}}", "empty_template": "- No changes", "max_pull_requests": 500, "max_back_track_time_days": 120 } ================================================ FILE: .github/dependabot.yml ================================================ version: 2 updates: - package-ecosystem: "github-actions" directory: "/" schedule: interval: "weekly" ignore: - dependency-name: "softprops/action-gh-release" ================================================ FILE: .github/scripts/combine-benchmarks.sh ================================================ #!/bin/bash set -euo pipefail # Script to combine individual benchmark results into LATEST.md # Usage: ./combine-benchmarks.sh OUTPUT_DIR="${1:-benches}" # Create output directory if it doesn't exist mkdir -p "$OUTPUT_DIR" # Define the benchmark files and their section names declare -A BENCHMARK_FILES=( ["forge_test_bench.md"]="Forge Test" ["forge_isolate_test_bench.md"]="Forge Test (Isolated)" ["forge_build_bench.md"]="Forge Build" ["forge_coverage_bench.md"]="Forge Coverage" ) # Function to extract a specific section from a benchmark file extract_section() { local file=$1 local section=$2 local in_section=0 while IFS= read -r line; do if [[ "$line" =~ ^##[[:space:]]+"$section" ]]; then in_section=1 echo "$line" elif [[ $in_section -eq 1 && "$line" =~ ^##[[:space:]] && ! "$line" =~ ^##[[:space:]]+"$section" ]]; then break elif [[ $in_section -eq 1 ]]; then echo "$line" fi done < "$file" } # Function to extract summary info (repos and versions) from a file extract_summary_info() { local file=$1 local in_summary=0 local in_repos=0 local in_versions=0 while IFS= read -r line; do # Check for Summary section if [[ "$line" =~ ^##[[:space:]]+Summary ]]; then in_summary=1 continue fi # Check for Repositories Tested subsection if [[ $in_summary -eq 1 && "$line" =~ ^###[[:space:]]+Repositories[[:space:]]+Tested ]]; then in_repos=1 echo "### Repositories Tested" echo continue fi # Check for Foundry Versions subsection if [[ $in_summary -eq 1 && "$line" =~ ^###[[:space:]]+Foundry[[:space:]]+Versions ]]; then in_repos=0 in_versions=1 echo "### Foundry Versions" echo continue fi # End of summary section if [[ $in_summary -eq 1 && "$line" =~ ^##[[:space:]] && ! "$line" =~ ^##[[:space:]]+Summary ]]; then break fi # Output repo or version lines if [[ ($in_repos -eq 1 || $in_versions -eq 1) && -n "$line" ]]; then echo "$line" fi done < "$file" } # Function to extract benchmark table from a section extract_benchmark_table() { local file=$1 local section=$2 local in_section=0 local found_table=0 while IFS= read -r line; do if [[ "$line" =~ ^##[[:space:]]+"$section" ]]; then in_section=1 continue elif [[ $in_section -eq 1 && "$line" =~ ^##[[:space:]] && ! "$line" =~ ^##[[:space:]]+"$section" ]]; then break elif [[ $in_section -eq 1 ]]; then # Skip empty lines before table if [[ -z "$line" && $found_table -eq 0 ]]; then continue fi # Detect table start if [[ "$line" =~ ^\|[[:space:]]*Repository ]]; then found_table=1 fi # Output table lines if [[ $found_table -eq 1 && -n "$line" ]]; then echo "$line" fi fi done < "$file" } # Function to extract system information extract_system_info() { local file=$1 awk '/^## System Information/ { found=1; next } found { print }' "$file" } # Start building LATEST.md cat > "$OUTPUT_DIR/LATEST.md" << EOF # 📊 Foundry Benchmark Results **Generated at**: $(date -u '+%Y-%m-%d %H:%M:%S UTC') EOF # Process each benchmark file FIRST_FILE=1 SYSTEM_INFO="" for bench_file in "forge_test_bench.md" "forge_isolate_test_bench.md" "forge_build_bench.md" "forge_coverage_bench.md"; do if [ -f "$OUTPUT_DIR/$bench_file" ]; then echo "Processing $bench_file..." SECTION_NAME="${BENCHMARK_FILES[$bench_file]:-$bench_file}" # Grouped output for ShellCheck SC2129 { # Add section header echo "## $SECTION_NAME" echo # Add summary info extract_summary_info "$OUTPUT_DIR/$bench_file" echo # Handle benchmark tables if [[ "$bench_file" == "forge_test_bench.md" ]]; then extract_benchmark_table "$OUTPUT_DIR/$bench_file" "Forge Test" echo if grep -q "^## Forge Fuzz Test" "$OUTPUT_DIR/$bench_file"; then echo "## Forge Fuzz Test" echo extract_benchmark_table "$OUTPUT_DIR/$bench_file" "Forge Fuzz Test" fi elif [[ "$bench_file" == "forge_build_bench.md" ]]; then echo "### No Cache" echo extract_benchmark_table "$OUTPUT_DIR/$bench_file" "Forge Build (No Cache)" echo echo "### With Cache" echo extract_benchmark_table "$OUTPUT_DIR/$bench_file" "Forge Build (With Cache)" else extract_benchmark_table "$OUTPUT_DIR/$bench_file" "$SECTION_NAME" fi echo } >> "$OUTPUT_DIR/LATEST.md" # Extract system info from first file only if [[ $FIRST_FILE -eq 1 ]]; then SYSTEM_INFO=$(extract_system_info "$OUTPUT_DIR/$bench_file") FIRST_FILE=0 fi else echo "Warning: $bench_file not found, skipping..." fi done # Add system information at the end if [[ -n "$SYSTEM_INFO" ]]; then { echo "## System Information" echo echo "$SYSTEM_INFO" } >> "$OUTPUT_DIR/LATEST.md" fi echo "Successfully combined benchmark results into $OUTPUT_DIR/LATEST.md" ================================================ FILE: .github/scripts/commit-and-read-benchmarks.sh ================================================ #!/bin/bash set -euo pipefail # Script to commit benchmark results and read them for GitHub Actions output # Usage: ./commit-and-read-benchmarks.sh OUTPUT_DIR="${1:-benches}" GITHUB_EVENT_NAME="${2:-pull_request}" GITHUB_REPOSITORY="${3:-}" # Global variable for branch name BRANCH_NAME="" # Function to commit benchmark results commit_results() { echo "Configuring git..." git config --local user.email "action@github.com" git config --local user.name "GitHub Action" # For PR runs, fetch and checkout the PR branch to ensure we're up to date if [ "$GITHUB_EVENT_NAME" = "pull_request" ] && [ -n "${GITHUB_HEAD_REF:-}" ]; then echo "Fetching latest changes for PR branch: $GITHUB_HEAD_REF" git fetch origin "$GITHUB_HEAD_REF" git checkout -B "$GITHUB_HEAD_REF" "origin/$GITHUB_HEAD_REF" fi echo "Adding benchmark file..." git add "$OUTPUT_DIR/LATEST.md" if git diff --staged --quiet; then echo "No changes to commit" else echo "Committing benchmark results..." git commit -m "chore(\`benches\`): update benchmark results 🤖 Generated with [Foundry Benchmarks](https://github.com/${GITHUB_REPOSITORY}/actions) Co-Authored-By: github-actions " echo "Pushing to repository..." if [ "$GITHUB_EVENT_NAME" = "workflow_dispatch" ]; then # For manual runs, we're on a new branch git push origin "$BRANCH_NAME" elif [ "$GITHUB_EVENT_NAME" = "pull_request" ]; then # For PR runs, push to the PR branch if [ -n "${GITHUB_HEAD_REF:-}" ]; then echo "Pushing to PR branch: $GITHUB_HEAD_REF" git push origin "$GITHUB_HEAD_REF" else echo "Error: GITHUB_HEAD_REF not set for pull_request event" exit 1 fi else # This workflow should only run on workflow_dispatch or pull_request echo "Error: Unexpected event type: $GITHUB_EVENT_NAME" echo "This workflow only supports 'workflow_dispatch' and 'pull_request' events" exit 1 fi echo "Successfully pushed benchmark results" fi } # Function to read benchmark results and output for GitHub Actions read_results() { if [ -f "$OUTPUT_DIR/LATEST.md" ]; then echo "Reading benchmark results..." # Output full results { echo 'results<> "$GITHUB_OUTPUT" # Format results for PR comment echo "Formatting results for PR comment..." FORMATTED_COMMENT=$("$(dirname "$0")/format-pr-comment.sh" "$OUTPUT_DIR/LATEST.md") { echo 'pr_comment<> "$GITHUB_OUTPUT" echo "Successfully read and formatted benchmark results" else echo 'results=No benchmark results found.' >> "$GITHUB_OUTPUT" echo 'pr_comment=No benchmark results found.' >> "$GITHUB_OUTPUT" echo "Warning: No benchmark results found at $OUTPUT_DIR/LATEST.md" fi } # Main execution echo "Starting benchmark results processing..." # Create new branch for manual runs if [ "$GITHUB_EVENT_NAME" = "workflow_dispatch" ]; then echo "Manual workflow run detected, creating new branch..." BRANCH_NAME="benchmarks/results-$(date +%Y%m%d-%H%M%S)" git checkout -b "$BRANCH_NAME" echo "Created branch: $BRANCH_NAME" # Output branch name for later use echo "branch_name=$BRANCH_NAME" >> "$GITHUB_OUTPUT" fi # Always commit benchmark results echo "Committing benchmark results..." commit_results # Always read results for output read_results echo "Benchmark results processing complete" ================================================ FILE: .github/scripts/create-tag.js ================================================ module.exports = async ({ github, context }, tagName) => { try { await github.rest.git.createRef({ owner: context.repo.owner, repo: context.repo.repo, ref: `refs/tags/${tagName}`, sha: context.sha, force: true, }); } catch (err) { console.error(`Failed to create tag: ${tagName}`); console.error(err); } }; ================================================ FILE: .github/scripts/format-pr-comment.sh ================================================ #!/bin/bash set -euo pipefail # Script to format benchmark results for PR comment # Usage: ./format-pr-comment.sh RESULTS_FILE="${1:-}" if [ -z "$RESULTS_FILE" ] || [ ! -f "$RESULTS_FILE" ]; then echo "Error: Benchmark results file not provided or does not exist" exit 1 fi # Read the file content CONTENT=$(cat "$RESULTS_FILE") # Find where "## Forge Build" starts and split the content # Extract everything before "## Forge Build" BEFORE_FORGE_BUILD=$(echo "$CONTENT" | awk '/^## Forge Build$/ {exit} {print}') # Extract everything from "## Forge Build" onwards FROM_FORGE_BUILD=$(echo "$CONTENT" | awk '/^## Forge Build$/ {found=1} found {print}') # Output the formatted comment with dropdown cat << EOF ${BEFORE_FORGE_BUILD}
📈 View all benchmark results ${FROM_FORGE_BUILD}
EOF ================================================ FILE: .github/scripts/format.sh ================================================ #!/usr/bin/env bash set -eo pipefail # We have to ignore at shell level because testdata/ is not a valid Foundry project, # so running `forge fmt` with `--root testdata` won't actually check anything sol_files=() while IFS= read -r -d '' file; do sol_files+=("$file") done < <(find testdata -name '*.sol' ! -name Vm.sol ! -name console.sol -print0) # Run forge fmt on all found files cargo run --bin forge -- fmt "$@" "${sol_files[@]}" ================================================ FILE: .github/scripts/matrices.py ================================================ #!/usr/bin/env python3 import json import os # A runner target class Target: # GHA runner runner_label: str # Rust target triple target: str # SVM Solc target svm_target_platform: str def __init__(self, runner_label: str, target: str, svm_target_platform: str): self.runner_label = runner_label self.target = target self.svm_target_platform = svm_target_platform # A single test suite to run. class Case: # Name of the test suite. name: str # Nextest filter expression. filter: str # Number of partitions to split the test suite into. n_partitions: int # Whether to run on non-Linux platforms for PRs. All platforms and tests are run on pushes. pr_cross_platform: bool def __init__( self, name: str, filter: str, n_partitions: int, pr_cross_platform: bool ): self.name = name self.filter = filter self.n_partitions = n_partitions self.pr_cross_platform = pr_cross_platform # GHA matrix entry class Expanded: name: str runner_label: str target: str svm_target_platform: str flags: str partition: int def __init__( self, name: str, runner_label: str, target: str, svm_target_platform: str, flags: str, partition: int, ): self.name = name self.runner_label = runner_label self.target = target self.svm_target_platform = svm_target_platform self.flags = flags self.partition = partition profile = os.environ.get("PROFILE") is_pr = os.environ.get("EVENT_NAME") == "pull_request" t_linux_x86 = Target( "depot-ubuntu-latest-16", "x86_64-unknown-linux-gnu", "linux-amd64" ) t_linux_arm = Target( "depot-ubuntu-latest-arm-16", "aarch64-unknown-linux-gnu", "linux-aarch64" ) t_macos = Target("depot-macos-latest", "aarch64-apple-darwin", "macosx-aarch64") t_windows = Target("depot-windows-latest-16", "x86_64-pc-windows-msvc", "windows-amd64") targets = [t_linux_x86] if is_pr else [t_linux_x86, t_linux_arm, t_macos, t_windows] config = [ Case( name="all", filter="!test(/\\bext_integration/)", n_partitions=1, pr_cross_platform=True, ), Case( name="external", filter="package(=forge) & test(/\\bext_integration/)", n_partitions=1, pr_cross_platform=False, ), ] def main(): expanded = [] for target in targets: for case in config: if is_pr and (not case.pr_cross_platform and target != t_linux_x86): continue for partition in range(1, case.n_partitions + 1): os_str = "" if len(targets) > 1: os_str = f" ({target.target})" name = case.name flags = f"-E '{case.filter}'" if case.n_partitions > 1: s = f"{partition}/{case.n_partitions}" name += f" ({s})" flags += f" --partition count:{s}" if profile == "isolate": flags += " --features=isolate-by-default" name += os_str flags += " --no-fail-fast" obj = Expanded( name=name, runner_label=target.runner_label, target=target.target, svm_target_platform=target.svm_target_platform, flags=flags, partition=partition, ) expanded.append(vars(obj)) print_json({"include": expanded}) def print_json(obj): print(json.dumps(obj), end="", flush=True) if __name__ == "__main__": main() ================================================ FILE: .github/scripts/move-tag.js ================================================ module.exports = async ({ github, context }, tagName) => { try { await github.rest.git.updateRef({ owner: context.repo.owner, repo: context.repo.repo, ref: `tags/${tagName}`, sha: context.sha, force: true, }); } catch (err) { console.error(`Failed to move nightly tag.`); console.error(`This should only happen the first time.`); console.error(err); } }; ================================================ FILE: .github/scripts/prune-prereleases.js ================================================ // In case node 21 is not used. function groupBy(array, keyOrIterator) { var iterator; // use the function passed in, or create one if(typeof keyOrIterator !== 'function') { const key = String(keyOrIterator); iterator = function (item) { return item[key]; }; } else { iterator = keyOrIterator; } return array.reduce(function (memo, item) { const key = iterator(item); memo[key] = memo[key] || []; memo[key].push(item); return memo; }, {}); } module.exports = async ({ github, context }) => { console.log("Pruning old prereleases"); // doc: https://docs.github.com/en/rest/releases/releases const { data: releases } = await github.rest.repos.listReleases({ owner: context.repo.owner, repo: context.repo.repo, }); let nightlies = releases.filter( release => // Only consider releases tagged `nightly-${SHA}` for deletion release.tag_name.includes("nightly") && release.tag_name !== "nightly" ); // Pruning rules: // 1. only keep the earliest (by created_at) release of the month // 2. to keep the newest 30 nightlies (to make sure nightlies are kept until the next monthly release) // Notes: // - This addresses https://github.com/foundry-rs/foundry/issues/6732 // - Name of the release may deviate from created_at due to the usage of different timezones. // Group releases by months. // Per doc: // > The latest release is the most recent non-prerelease, non-draft release, sorted by the created_at attribute. const groups = groupBy(nightlies, i => i.created_at.slice(0, 7)); const nightliesToPrune = Object.values(groups) .reduce((acc, cur) => acc.concat(cur.slice(0, -1)), []) // rule 1 .slice(30); // rule 2 for (const nightly of nightliesToPrune) { console.log(`Deleting nightly: ${nightly.tag_name}`); await github.rest.repos.deleteRelease({ owner: context.repo.owner, repo: context.repo.repo, release_id: nightly.id, }); console.log(`Deleting nightly tag: ${nightly.tag_name}`); await github.rest.git.deleteRef({ owner: context.repo.owner, repo: context.repo.repo, ref: `tags/${nightly.tag_name}`, }); } console.log("Done."); }; ================================================ FILE: .github/scripts/setup-foundryup.sh ================================================ #!/bin/bash set -euo pipefail # Script to setup foundryup in CI environment # This ensures foundryup is available in PATH for the benchmark binary echo "Setting up foundryup..." # Check if foundryup script exists in the repo if [ ! -f "foundryup/foundryup" ]; then echo "Error: foundryup/foundryup script not found in repository" exit 1 fi # Copy foundryup to a location in PATH echo "Copying foundryup to /usr/local/bin..." sudo cp foundryup/foundryup /usr/local/bin/foundryup sudo chmod +x /usr/local/bin/foundryup # Verify foundryup is accessible if ! command -v foundryup &> /dev/null; then echo "Error: foundryup not found in PATH after installation" exit 1 fi echo "foundryup is now available at: $(which foundryup)" # Create foundry directories echo "Creating foundry directories..." # Use FOUNDRY_DIR if set, otherwise default to $HOME/.foundry FOUNDRY_DIR="${FOUNDRY_DIR:-$HOME/.foundry}" echo "Using FOUNDRY_DIR: $FOUNDRY_DIR" # Create all necessary directories mkdir -p "$FOUNDRY_DIR/bin" mkdir -p "$FOUNDRY_DIR/versions" mkdir -p "$FOUNDRY_DIR/share/man/man1" # Export PATH for current session export PATH="$FOUNDRY_DIR/bin:$PATH" # Run foundryup to install default version echo "Installing default foundry version..." foundryup # Verify installation if command -v forge &> /dev/null; then echo "Forge installed successfully: $(forge --version)" else echo "Warning: forge not found in PATH after installation" fi echo "Foundry setup complete!" ================================================ FILE: .github/scripts/shellcheck.sh ================================================ #!/usr/bin/env bash # runs shellcheck and prints GitHub Actions annotations for each warning and error # https://github.com/koalaman/shellcheck IGNORE_DIRS=( "./.git/*" "./target/*" ) ignore_args=() for dir in "${IGNORE_DIRS[@]}"; do ignore_args+=(-not -path "$dir") done find . -name "*.sh" "${ignore_args[@]}" -exec shellcheck -f gcc {} + | \ while IFS=: read -r file line col severity msg; do level="warning" [[ "$severity" == *error* ]] && level="error" file="${file#./}" echo "::${level} file=${file},line=${line},col=${col}::${file}:${line}:${col}:${msg}" done exit "${PIPESTATUS[0]}" ================================================ FILE: .github/workflows/benchmarks.yml ================================================ name: Foundry Benchmarks permissions: {} on: workflow_dispatch: inputs: pr_number: description: "PR number to comment on (optional)" required: false type: string versions: description: "Comma-separated list of Foundry versions to benchmark (e.g., stable,nightly,v1.0.0)" required: false type: string default: "stable,nightly" repos: description: "Comma-separated list of repos to benchmark (e.g., ithacaxyz/account:main,Vectorized/solady)" required: false type: string default: "ithacaxyz/account:v0.3.2,Vectorized/solady:v0.1.22" env: ITHACAXYZ_ACCOUNT: "ithacaxyz/account:v0.3.2" VECTORIZED_SOLADY: "Vectorized/solady:v0.1.22" DEFAULT_REPOS: "ithacaxyz/account:v0.3.2,Vectorized/solady:v0.1.22" RUSTC_WRAPPER: "sccache" jobs: run-benchmarks: name: Run All Benchmarks runs-on: depot-ubuntu-24.04-32 permissions: contents: write steps: - name: Checkout repository uses: actions/checkout@v6 with: persist-credentials: false - name: Install build dependencies run: | sudo apt-get update sudo apt-get install -y build-essential pkg-config - name: Setup Rust toolchain uses: dtolnay/rust-toolchain@e97e2d8cc328f1b50210efc529dca0028893a2d9 # master with: toolchain: stable - uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # v1 - uses: mozilla-actions/sccache-action@7d986dd989559c6ecdb630a3fd2557667be217ad # v0.0.9 - name: Setup Foundry env: FOUNDRY_DIR: ${{ github.workspace }}/.foundry GITHUB_WORKSPACE: ${{ github.workspace }} run: | ./.github/scripts/setup-foundryup.sh printf '%s\n' "$GITHUB_WORKSPACE/.foundry/bin" >> "$GITHUB_PATH" - name: Build benchmark binary run: cargo build --release --bin foundry-bench - name: Setup Node.js uses: actions/setup-node@v6 with: node-version: "24" - name: Install hyperfine run: | curl -L https://github.com/sharkdp/hyperfine/releases/download/v1.19.0/hyperfine-v1.19.0-x86_64-unknown-linux-gnu.tar.gz | tar xz sudo mv hyperfine-v1.19.0-x86_64-unknown-linux-gnu/hyperfine /usr/local/bin/ rm -rf hyperfine-v1.19.0-x86_64-unknown-linux-gnu - name: Run forge test benchmarks env: FOUNDRY_DIR: ${{ github.workspace }}/.foundry run: | VERSIONS="${{ github.event.inputs.versions || 'stable,nightly' }}" REPOS="${{ github.event.inputs.repos || env.DEFAULT_REPOS }}" ./target/release/foundry-bench --output-dir ./benches --force-install \ --versions $VERSIONS \ --repos $REPOS \ --benchmarks forge_test,forge_fuzz_test \ --output-file forge_test_bench.md - name: Run forge isolate test benchmarks env: FOUNDRY_DIR: ${{ github.workspace }}/.foundry run: | VERSIONS="${{ github.event.inputs.versions || 'stable,nightly' }}" # Isolate tests default to Vectorized/solady but can be overridden REPOS="${{ github.event.inputs.repos || env.VECTORIZED_SOLADY }}" ./target/release/foundry-bench --output-dir ./benches --force-install \ --versions $VERSIONS \ --repos $REPOS \ --benchmarks forge_isolate_test \ --output-file forge_isolate_test_bench.md - name: Run forge build benchmarks env: FOUNDRY_DIR: ${{ github.workspace }}/.foundry run: | VERSIONS="${{ github.event.inputs.versions || 'stable,nightly' }}" REPOS="${{ github.event.inputs.repos || env.DEFAULT_REPOS }}" ./target/release/foundry-bench --output-dir ./benches --force-install \ --versions $VERSIONS \ --repos $REPOS \ --benchmarks forge_build_no_cache,forge_build_with_cache \ --output-file forge_build_bench.md - name: Run forge coverage benchmarks env: FOUNDRY_DIR: ${{ github.workspace }}/.foundry run: | VERSIONS="${{ github.event.inputs.versions || 'stable,nightly' }}" # Coverage only runs on ithacaxyz/account:v0.3.2 ./target/release/foundry-bench --output-dir ./benches --force-install \ --versions $VERSIONS \ --repos ${{ env.ITHACAXYZ_ACCOUNT }} \ --benchmarks forge_coverage \ --output-file forge_coverage_bench.md - name: Combine benchmark results run: ./.github/scripts/combine-benchmarks.sh benches - name: Commit and read benchmark results id: benchmark_results env: GITHUB_HEAD_REF: ${{ github.head_ref }} run: ./.github/scripts/commit-and-read-benchmarks.sh benches "${{ github.event_name }}" "${{ github.repository }}" - name: Upload benchmark results as artifacts uses: actions/upload-artifact@v7 with: name: benchmark-results path: | benches/forge_test_bench.md benches/forge_isolate_test_bench.md benches/forge_build_bench.md benches/forge_coverage_bench.md benches/LATEST.md outputs: branch_name: ${{ steps.benchmark_results.outputs.branch_name }} pr_comment: ${{ steps.benchmark_results.outputs.pr_comment }} publish-results: name: Publish Results needs: run-benchmarks runs-on: ubuntu-latest permissions: contents: write pull-requests: write steps: - name: Checkout repository uses: actions/checkout@v6 with: persist-credentials: false - name: Download benchmark results uses: actions/download-artifact@v8 with: name: benchmark-results path: benches/ - name: Push branch for manual runs if: github.event_name == 'workflow_dispatch' run: | git push origin "${{ needs.run-benchmarks.outputs.branch_name }}" echo "Pushed branch: ${{ needs.run-benchmarks.outputs.branch_name }}" - name: Create PR for manual runs if: github.event_name == 'workflow_dispatch' uses: actions/github-script@v8 with: script: | const branchName = '${{ needs.run-benchmarks.outputs.branch_name }}'; const prComment = `${{ needs.run-benchmarks.outputs.pr_comment }}`; // Create the pull request const { data: pr } = await github.rest.pulls.create({ owner: context.repo.owner, repo: context.repo.repo, title: 'chore(bench): update benchmark results', head: branchName, base: 'master', body: `## Benchmark Results Update This PR contains the latest benchmark results from a manual workflow run. ${prComment} --- 🤖 This PR was automatically generated by the [Foundry Benchmarks workflow](https://github.com/${{ github.repository }}/actions).` }); console.log(`Created PR #${pr.number}: ${pr.html_url}`); - name: Comment on PR if: github.event.inputs.pr_number != '' || github.event_name == 'pull_request' uses: actions/github-script@v8 with: script: | const prNumber = ${{ github.event.inputs.pr_number || github.event.pull_request.number }}; const prComment = `${{ needs.run-benchmarks.outputs.pr_comment }}`; const comment = `${prComment} --- 🤖 This comment was automatically generated by the [Foundry Benchmarks workflow](https://github.com/${{ github.repository }}/actions). To run benchmarks manually: Go to [Actions](https://github.com/${{ github.repository }}/actions/workflows/benchmarks.yml) → "Run workflow"`; github.rest.issues.createComment({ issue_number: prNumber, owner: context.repo.owner, repo: context.repo.repo, body: comment }); ================================================ FILE: .github/workflows/bump-forge-std.yml ================================================ # Daily CI job to update forge-std version used for tests if new release has been published name: bump-forge-std permissions: {} on: schedule: - cron: "0 0 * * *" # Run daily at midnight UTC workflow_dispatch: # Needed so we can run it manually jobs: update-tag: name: update forge-std tag runs-on: ubuntu-latest permissions: contents: write pull-requests: write steps: - uses: actions/checkout@v6 with: persist-credentials: false - name: Fetch and update forge-std tag run: curl 'https://api.github.com/repos/foundry-rs/forge-std/tags' | jq '.[0].commit.sha' -jr > testdata/forge-std-rev - name: Create pull request uses: peter-evans/create-pull-request@c0f553fe549906ede9cf27b5156039d195d2ece0 # v7 with: commit-message: "chore: bump forge-std version used for tests" title: "chore(tests): bump forge-std version" body: | New release of forge-std has been published, bump forge-std version used in tests. Likely some fixtures need to be updated. branch: chore/bump-forge-std ================================================ FILE: .github/workflows/ci.yml ================================================ name: CI permissions: {} on: push: branches: [master] pull_request: concurrency: group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} cancel-in-progress: true env: CARGO_TERM_COLOR: always RUST_BACKTRACE: full RUSTC_WRAPPER: "sccache" jobs: test: uses: ./.github/workflows/test.yml permissions: contents: read with: profile: default secrets: inherit docs: uses: ./.github/workflows/docs.yml permissions: contents: read pages: write id-token: write secrets: inherit doctest: runs-on: depot-ubuntu-latest timeout-minutes: 30 permissions: contents: read steps: - uses: actions/checkout@v6 with: persist-credentials: false - uses: dtolnay/rust-toolchain@e97e2d8cc328f1b50210efc529dca0028893a2d9 # master with: toolchain: stable - uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # v1 - uses: mozilla-actions/sccache-action@7d986dd989559c6ecdb630a3fd2557667be217ad # v0.0.9 - uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2 - run: cargo test --workspace --doc typos: runs-on: depot-ubuntu-latest timeout-minutes: 30 permissions: contents: read steps: - uses: actions/checkout@v6 with: persist-credentials: false - uses: crate-ci/typos@631208b7aac2daa8b707f55e7331f9112b0e062d # v1 shellcheck: runs-on: depot-ubuntu-latest timeout-minutes: 5 permissions: contents: read steps: - uses: actions/checkout@v6 with: persist-credentials: false - name: Shellcheck shell: bash run: ./.github/scripts/shellcheck.sh clippy: runs-on: depot-ubuntu-latest timeout-minutes: 30 permissions: contents: read steps: - uses: actions/checkout@v6 with: persist-credentials: false - uses: dtolnay/rust-toolchain@e97e2d8cc328f1b50210efc529dca0028893a2d9 # master with: toolchain: nightly components: clippy - uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # v1 - uses: mozilla-actions/sccache-action@7d986dd989559c6ecdb630a3fd2557667be217ad # v0.0.9 - uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2 - run: cargo clippy --workspace --all-targets --all-features env: RUSTFLAGS: -Dwarnings rustfmt: runs-on: depot-ubuntu-latest timeout-minutes: 30 permissions: contents: read steps: - uses: actions/checkout@v6 with: persist-credentials: false - uses: dtolnay/rust-toolchain@e97e2d8cc328f1b50210efc529dca0028893a2d9 # master with: toolchain: nightly components: rustfmt - run: cargo fmt --all --check forge-fmt: runs-on: depot-ubuntu-latest timeout-minutes: 30 permissions: contents: read steps: - uses: actions/checkout@v6 with: persist-credentials: false - uses: dtolnay/rust-toolchain@e97e2d8cc328f1b50210efc529dca0028893a2d9 # master with: toolchain: stable - uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # v1 - uses: mozilla-actions/sccache-action@7d986dd989559c6ecdb630a3fd2557667be217ad # v0.0.9 - uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2 - name: forge fmt shell: bash run: ./.github/scripts/format.sh --check crate-checks: runs-on: depot-ubuntu-latest timeout-minutes: 30 permissions: contents: read steps: - uses: actions/checkout@v6 with: persist-credentials: false - uses: dtolnay/rust-toolchain@e97e2d8cc328f1b50210efc529dca0028893a2d9 # master with: toolchain: stable - uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # v1 - uses: taiki-e/install-action@94a7388bec5d4c8dd93e3ebf09e0ff448f3f6f4d # v2 with: tool: cargo-hack - uses: mozilla-actions/sccache-action@7d986dd989559c6ecdb630a3fd2557667be217ad # v0.0.9 - uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2 - run: cargo hack check deny: uses: tempoxyz/ci/.github/workflows/deny.yml@268b3ce142717ff86c58fbbcc3abc3f109f0fb8d # main permissions: contents: read codeql: name: analyze (${{ matrix.language }}) runs-on: ubuntu-latest permissions: security-events: write actions: read contents: read strategy: fail-fast: false matrix: include: - language: actions build-mode: none steps: - name: Checkout repository uses: actions/checkout@v6 with: persist-credentials: false - name: Initialize CodeQL uses: github/codeql-action/init@v4 with: languages: ${{ matrix.language }} build-mode: ${{ matrix.build-mode }} - name: Perform CodeQL Analysis uses: github/codeql-action/analyze@v4 with: category: "/language:${{matrix.language}}" ci-success: runs-on: ubuntu-latest if: always() permissions: {} needs: - test - docs - doctest - typos - clippy - rustfmt - forge-fmt - crate-checks - deny - codeql timeout-minutes: 30 steps: - name: Decide whether the needed jobs succeeded or failed uses: re-actors/alls-green@05ac9388f0aebcb5727afa17fcccfecd6f8ec5fe # release/v1 with: jobs: ${{ toJSON(needs) }} ================================================ FILE: .github/workflows/dependencies.yml ================================================ # Runs `cargo update` periodically. name: dependencies permissions: {} on: schedule: - cron: "0 0 * * SUN" # Run weekly on Sundays at midnight UTC workflow_dispatch: # Needed so we can run it manually jobs: update: uses: tempoxyz/ci/.github/workflows/cargo-update-pr.yml@268b3ce142717ff86c58fbbcc3abc3f109f0fb8d # main permissions: contents: write pull-requests: write secrets: token: ${{ secrets.GITHUB_TOKEN }} ================================================ FILE: .github/workflows/docker-publish.yml ================================================ name: docker permissions: {} on: workflow_dispatch: inputs: tag_name: default: nightly description: The tag we're building for type: string workflow_call: inputs: tag_name: required: true type: string concurrency: group: docker-${{ github.head_ref }} cancel-in-progress: ${{ github.event_name == 'pull_request' }} env: REGISTRY: ghcr.io IMAGE_NAME: ${{ github.repository }} # Keep in sync with `release.yml`. RUST_PROFILE: dist RUST_FEATURES: aws-kms,gcp-kms,turnkey,cli,asm-keccak,js-tracer jobs: build: name: build and push runs-on: depot-ubuntu-latest permissions: contents: read id-token: write packages: write timeout-minutes: 60 steps: - uses: actions/checkout@v6 with: persist-credentials: false # Login against a Docker registry except on PR # https://github.com/docker/login-action - name: Login into registry ${{ env.REGISTRY }} uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0 with: registry: ${{ env.REGISTRY }} username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} # Extract metadata (tags, labels) for Docker # https://github.com/docker/metadata-action - name: Extract Docker metadata id: meta uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf # v6.0.0 with: images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} # Creates an additional 'latest' or 'nightly' tag # If the job is triggered via cron schedule, tag nightly and nightly-{SHA} # If the job is triggered via workflow dispatch and on a master branch, tag branch and latest # Otherwise, just tag as the branch name - name: Finalize Docker Metadata id: docker_tagging run: | TAG="${{ inputs.tag_name }}" REGISTRY="${{ env.REGISTRY }}" IMAGE="${{ env.IMAGE_NAME }}" if [[ "${{ github.event_name }}" == "schedule" ]]; then printf "cron trigger, assigning nightly tag\n" printf "docker_tags=%s/%s:nightly,%s/%s:nightly-%s\n" "$REGISTRY" "$IMAGE" "$REGISTRY" "$IMAGE" "$GITHUB_SHA" >> "$GITHUB_OUTPUT" elif [[ "${GITHUB_REF##*/}" == "main" ]] || [[ "${GITHUB_REF##*/}" == "master" ]]; then printf "manual trigger from master/main branch, assigning latest tag\n" printf "docker_tags=%s/%s:%s,%s/%s:latest\n" "$REGISTRY" "$IMAGE" "${GITHUB_REF##*/}" "$REGISTRY" "$IMAGE" >> "$GITHUB_OUTPUT" elif [[ "$TAG" =~ ^v([0-9]+)\.([0-9]+)\.([0-9]+)$ ]]; then MAJOR="v${BASH_REMATCH[1]}" MINOR="v${BASH_REMATCH[1]}.${BASH_REMATCH[2]}" printf "version tag release, assigning %s, %s, and %s tags\n" "$TAG" "$MINOR" "$MAJOR" printf "docker_tags=%s/%s:%s,%s/%s:%s,%s/%s:%s\n" "$REGISTRY" "$IMAGE" "$TAG" "$REGISTRY" "$IMAGE" "$MINOR" "$REGISTRY" "$IMAGE" "$MAJOR" >> "$GITHUB_OUTPUT" else printf "Neither scheduled nor manual release from main branch. Just tagging as branch name\n" printf "docker_tags=%s/%s:%s\n" "$REGISTRY" "$IMAGE" "${GITHUB_REF##*/}" >> "$GITHUB_OUTPUT" fi # Log docker metadata to explicitly know what is being pushed - name: Inspect Docker Metadata run: | printf "TAGS -> %s\n" "${{ steps.docker_tagging.outputs.docker_tags }}" printf "LABELS -> %s\n" "${{ steps.meta.outputs.labels }}" - name: Set up Depot CLI uses: depot/setup-action@15c09a5f77a0840ad4bce955686522a257853461 # v1 - name: Build and push Foundry image uses: depot/build-push-action@5f3b3c2e5a00f0093de47f657aeaefcedff27d18 # v1 with: build-args: | RUST_PROFILE=${{ env.RUST_PROFILE }} RUST_FEATURES=${{ env.RUST_FEATURES }} TAG_NAME=${{ inputs.tag_name }} VERGEN_GIT_SHA=${{ github.sha }} project: 8gkbxxjrpw context: . tags: ${{ steps.docker_tagging.outputs.docker_tags }} labels: ${{ steps.meta.outputs.labels }} platforms: linux/amd64,linux/arm64 push: true ================================================ FILE: .github/workflows/docs.yml ================================================ name: docs permissions: {} on: push: branches: - master workflow_call: concurrency: group: docs-${{ github.workflow }}-${{ github.head_ref || github.run_id }} cancel-in-progress: true env: CARGO_TERM_COLOR: always RUST_BACKTRACE: full RUSTC_WRAPPER: "sccache" jobs: docs: runs-on: depot-ubuntu-latest timeout-minutes: 30 permissions: contents: read steps: - uses: actions/checkout@v6 with: persist-credentials: false - uses: dtolnay/rust-toolchain@e97e2d8cc328f1b50210efc529dca0028893a2d9 # master with: toolchain: nightly - uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # v1 - uses: mozilla-actions/sccache-action@7d986dd989559c6ecdb630a3fd2557667be217ad # v0.0.9 - uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2 - name: Build documentation run: cargo doc --workspace --all-features --no-deps --document-private-items env: RUSTDOCFLAGS: --cfg docsrs -D warnings --show-type-layout --generate-link-to-definition --enable-index-page -Zunstable-options - name: Setup Pages if: github.ref_name == 'master' && github.event_name == 'push' uses: actions/configure-pages@v5 - name: Upload artifact if: github.ref_name == 'master' && github.event_name == 'push' uses: actions/upload-pages-artifact@v4 with: path: ./target/doc deploy-docs: if: github.ref_name == 'master' && github.event_name == 'push' needs: [docs] runs-on: depot-ubuntu-latest timeout-minutes: 30 permissions: pages: write id-token: write environment: name: github-pages url: ${{ steps.deployment.outputs.page_url }} steps: - name: Deploy to GitHub Pages id: deployment uses: actions/deploy-pages@v4 ================================================ FILE: .github/workflows/nix.yml ================================================ name: nix permissions: {} on: schedule: - cron: "0 0 * * SUN" # Run weekly on Sundays at midnight UTC 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 permissions: contents: write pull-requests: write steps: - uses: DeterminateSystems/determinate-nix-action@131015bad844610e5e6300f8a143bf625d3e74f4 # v3 - uses: actions/checkout@v6 with: persist-credentials: false - uses: DeterminateSystems/update-flake-lock@e80a657d7603606be0c69b117cfdc240f1e6af88 # 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 }} permissions: contents: read steps: - uses: DeterminateSystems/determinate-nix-action@131015bad844610e5e6300f8a143bf625d3e74f4 # v3 - uses: actions/checkout@v6 with: persist-credentials: false - 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 ================================================ FILE: .github/workflows/npm.yml ================================================ name: npm permissions: {} on: workflow_dispatch: inputs: run_id: type: string required: false description: The run id of the release to publish workflow_run: types: [completed] workflows: [release] concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true defaults: run: shell: bash env: NPM_CONFIG_PROVENANCE: true NPM_REGISTRY_URL: "https://registry.npmjs.org" jobs: publish-arch: permissions: contents: read actions: read id-token: write name: ${{ matrix.tool }}-${{ matrix.os }}-${{ matrix.arch }} runs-on: ubuntu-latest strategy: max-parallel: 3 fail-fast: false matrix: include: - tool: forge os: linux arch: amd64 - tool: forge os: linux arch: arm64 - tool: forge os: darwin arch: amd64 - tool: forge os: darwin arch: arm64 - tool: forge os: win32 arch: amd64 - tool: cast os: linux arch: amd64 - tool: cast os: linux arch: arm64 - tool: cast os: darwin arch: amd64 - tool: cast os: darwin arch: arm64 - tool: cast os: win32 arch: amd64 - tool: anvil os: linux arch: amd64 - tool: anvil os: linux arch: arm64 - tool: anvil os: darwin arch: amd64 - tool: anvil os: darwin arch: arm64 - tool: anvil os: win32 arch: amd64 - tool: chisel os: linux arch: amd64 - tool: chisel os: linux arch: arm64 - tool: chisel os: darwin arch: amd64 - tool: chisel os: darwin arch: arm64 - tool: chisel os: win32 arch: amd64 # Run automatically after a successful 'release' workflow, or manually if a run_id is provided if: >- ${{ (github.event_name == 'workflow_run' && github.event.workflow_run.conclusion == 'success') || (github.event_name == 'workflow_dispatch' && inputs.run_id != '') }} outputs: RELEASE_VERSION: ${{ steps.release-version.outputs.RELEASE_VERSION }} steps: - name: Checkout uses: actions/checkout@v6 with: persist-credentials: false - name: Set Isolated Artifact Directory id: paths run: | set -euo pipefail printf 'artifact_dir=%s\n' "$RUNNER_TEMP/foundry_artifacts" >> "$GITHUB_OUTPUT" - name: Prepare Isolated Artifact Directory env: ARTIFACT_DIR: ${{ steps.paths.outputs.artifact_dir }} run: | mkdir -p "$ARTIFACT_DIR" ls -la "$ARTIFACT_DIR" || true - name: Download Release Assets uses: actions/download-artifact@v8 with: merge-multiple: true # Download all foundry artifacts from the triggering release run pattern: "foundry_*" # Extract artifacts into an isolated temp directory, not the workspace path: ${{ steps.paths.outputs.artifact_dir }} github-token: ${{ secrets.GITHUB_TOKEN }} run-id: ${{ github.event.workflow_run.id || inputs.run_id }} - name: Setup Bun uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2 with: bun-version: latest - name: Setup Node (for npm publish auth) uses: actions/setup-node@v6 with: node-version: "24" registry-url: "https://registry.npmjs.org" - name: Derive RELEASE_VERSION id: release-version working-directory: ./npm env: PROVENANCE: true NPM_TOKEN: ${{ secrets.NPM_TOKEN }} NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} NPM_REGISTRY_URL: ${{ env.NPM_REGISTRY_URL }} ARTIFACT_DIR: ${{ steps.paths.outputs.artifact_dir }} run: | set -euo pipefail echo "Artifacts in $ARTIFACT_DIR:" ls -la "$ARTIFACT_DIR" || true # Derive RELEASE_VERSION from any foundry artifact we downloaded # Expected names: foundry___.{tar.gz,zip} first_file=$(ls "$ARTIFACT_DIR"/foundry_* 2>/dev/null | head -n1 || true) if [[ -z "${first_file}" ]]; then echo "No foundry artifacts found to publish" >&2 exit 1 fi version_part=$(basename "$first_file") version_part=${version_part#foundry_} export RELEASE_VERSION=${version_part%%_*} echo "Detected RELEASE_VERSION=$RELEASE_VERSION" printf 'RELEASE_VERSION=%s\n' "$RELEASE_VERSION" >>"$GITHUB_OUTPUT" - name: Stage Binary Into Package working-directory: ./npm env: RELEASE_VERSION: ${{ steps.release-version.outputs.RELEASE_VERSION }} ARTIFACT_DIR: ${{ steps.paths.outputs.artifact_dir }} run: | set -euo pipefail bun ./scripts/stage-from-artifact.mjs \ --tool '${{ matrix.tool }}' \ --platform '${{ matrix.os }}' \ --arch '${{ matrix.arch }}' \ --release-version "$RELEASE_VERSION" \ --artifact-dir "$ARTIFACT_DIR" - name: Sanity Check Binary working-directory: ./npm run: | set -euo pipefail TOOL='${{ matrix.tool }}' PLATFORM='${{ matrix.os }}' ARCH='${{ matrix.arch }}' PKG_DIR="./@foundry-rs/${TOOL}-${PLATFORM}-${ARCH}" BIN="$PKG_DIR/bin/${TOOL}" if [[ "$PLATFORM" == "win32" ]]; then BIN="$PKG_DIR/bin/${TOOL}.exe" fi echo "Verifying binary at: $BIN" ls -la "$BIN" if [[ ! -f "$BIN" ]]; then echo "ERROR: Binary not found at $BIN" >&2 exit 1 fi if [[ "${{ matrix.os }}" != "win32" ]]; then if [[ ! -x "$BIN" ]]; then echo "ERROR: Binary not marked executable" >&2 exit 1 fi fi - name: Publish ${{ matrix.os }}-${{ matrix.arch }} Binary working-directory: ./npm env: PROVENANCE: true VERSION_NAME: ${{ steps.release-version.outputs.RELEASE_VERSION }} RELEASE_VERSION: ${{ steps.release-version.outputs.RELEASE_VERSION }} NPM_TOKEN: ${{ secrets.NPM_TOKEN }} NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} run: | set -euo pipefail TOOL='${{ matrix.tool }}' PLATFORM='${{ matrix.os }}' ARCH='${{ matrix.arch }}' PACKAGE_DIR="./@foundry-rs/${TOOL}-${PLATFORM}-${ARCH}" ls -la "$PACKAGE_DIR" bun ./scripts/publish.mjs "$PACKAGE_DIR" echo "Published @foundry-rs/${TOOL}-${PLATFORM}-${ARCH}" publish-meta: permissions: contents: read actions: read id-token: write needs: publish-arch name: Publish Meta Package runs-on: ubuntu-latest env: NPM_TOKEN: ${{ secrets.NPM_TOKEN }} NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} RELEASE_VERSION: ${{ needs.publish-arch.outputs.RELEASE_VERSION }} steps: - name: Checkout uses: actions/checkout@v6 with: persist-credentials: false - name: Setup Bun uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2 with: bun-version: latest - name: Setup Node (for npm publish auth) uses: actions/setup-node@v6 with: node-version: "24" registry-url: "https://registry.npmjs.org" - name: Install Dependencies working-directory: ./npm run: bun install --frozen-lockfile - name: Typecheck working-directory: ./npm run: bun tsc --project tsconfig.json --noEmit - name: Publish Meta Packages working-directory: ./npm run: | set -euo pipefail bun ./scripts/publish-meta.mjs --release-version "$RELEASE_VERSION" env: PROVENANCE: true VERSION_NAME: ${{ env.RELEASE_VERSION }} RELEASE_VERSION: ${{ env.RELEASE_VERSION }} NPM_TOKEN: ${{ env.NPM_TOKEN }} NODE_AUTH_TOKEN: ${{ env.NODE_AUTH_TOKEN }} NPM_REGISTRY_URL: ${{ env.NPM_REGISTRY_URL }} ================================================ FILE: .github/workflows/release.yml ================================================ name: release permissions: {} on: push: tags: - "stable" - "rc" - "rc-*" - "v*.*.*" schedule: - cron: "0 6 * * *" workflow_dispatch: env: CARGO_TERM_COLOR: always IS_NIGHTLY: ${{ github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' }} # Keep in sync with `docker-publish.yml`. RUST_PROFILE: dist RUST_FEATURES: aws-kms,gcp-kms,turnkey,cli,asm-keccak,js-tracer LAST_STABLE_VERSION: "v1.5.1" jobs: prepare: name: Prepare release runs-on: ubuntu-latest timeout-minutes: 30 permissions: contents: write pull-requests: read outputs: tag_name: ${{ steps.release_info.outputs.tag_name }} release_name: ${{ steps.release_info.outputs.release_name }} changelog: ${{ steps.build_changelog.outputs.changelog }} steps: - uses: actions/checkout@v6 with: persist-credentials: false fetch-depth: 0 - name: Compute release name and tag id: release_info run: | if [[ ${IS_NIGHTLY} == 'true' ]]; then printf 'tag_name=%s\n' "nightly-${GITHUB_SHA}" >> "$GITHUB_OUTPUT" printf 'release_name=%s\n' "Nightly ($(date '+%Y-%m-%d'))" >> "$GITHUB_OUTPUT" else printf 'tag_name=%s\n' "$GITHUB_REF_NAME" >> "$GITHUB_OUTPUT" printf 'release_name=%s\n' "$GITHUB_REF_NAME" >> "$GITHUB_OUTPUT" fi # Creates a `nightly-SHA` tag for this specific nightly # This tag is used for this specific nightly version's release # which allows users to roll back. It is also used to build # the changelog. - name: Create build-specific nightly tag if: ${{ env.IS_NIGHTLY == 'true' }} uses: actions/github-script@v8 env: TAG_NAME: ${{ steps.release_info.outputs.tag_name }} with: script: | const createTag = require('./.github/scripts/create-tag.js') await createTag({ github, context }, process.env.TAG_NAME) - name: Build changelog id: build_changelog uses: mikepenz/release-changelog-builder-action@a34a8009a9588bb86b02a873cf592440e96a5da8 # v6 with: configuration: "./.github/changelog.json" fromTag: ${{ env.IS_NIGHTLY == 'true' && 'nightly' || env.STABLE_VERSION }} toTag: ${{ steps.release_info.outputs.tag_name }} env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} release-docker: name: Release Docker needs: prepare uses: ./.github/workflows/docker-publish.yml permissions: contents: read id-token: write packages: write with: tag_name: ${{ needs.prepare.outputs.tag_name }} release: permissions: id-token: write contents: write attestations: write name: release ${{ matrix.target }} (${{ matrix.runner }}) runs-on: ${{ matrix.runner }} timeout-minutes: 240 needs: prepare strategy: fail-fast: false matrix: include: # `runner`: GHA runner label # `target`: Rust build target triple # `platform` and `arch`: Used in tarball names # `svm`: target platform to use for the Solc binary: https://github.com/roynalnaruto/svm-rs/blob/84cbe0ac705becabdc13168bae28a45ad2299749/svm-builds/build.rs#L4-L24 # These are pinned to the oldest runner versions to support old libc/SDK versions. - runner: depot-ubuntu-22.04-16 target: x86_64-unknown-linux-gnu svm_target_platform: linux-amd64 platform: linux arch: amd64 - runner: depot-ubuntu-22.04-16 target: x86_64-unknown-linux-musl svm_target_platform: linux-amd64 platform: alpine arch: amd64 - runner: depot-ubuntu-22.04-arm-16 target: aarch64-unknown-linux-gnu svm_target_platform: linux-aarch64 platform: linux arch: arm64 - runner: depot-ubuntu-22.04-16 target: aarch64-unknown-linux-musl svm_target_platform: linux-aarch64 platform: alpine arch: arm64 - runner: macos-14-large target: x86_64-apple-darwin svm_target_platform: macosx-amd64 platform: darwin arch: amd64 - runner: macos-latest-large target: aarch64-apple-darwin svm_target_platform: macosx-aarch64 platform: darwin arch: arm64 - runner: depot-windows-latest-16 target: x86_64-pc-windows-msvc svm_target_platform: windows-amd64 platform: win32 arch: amd64 steps: - uses: actions/checkout@v6 with: persist-credentials: false - uses: dtolnay/rust-toolchain@e97e2d8cc328f1b50210efc529dca0028893a2d9 # master with: toolchain: stable targets: ${{ matrix.target }} - uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # v1 - uses: mozilla-actions/sccache-action@7d986dd989559c6ecdb630a3fd2557667be217ad # v0.0.9 if: ${{ contains(matrix.runner, 'depot') }} - run: printf 'RUSTC_WRAPPER=sccache\n' >> "$GITHUB_ENV" if: ${{ contains(matrix.runner, 'depot') }} - name: Apple M1 setup if: matrix.target == 'aarch64-apple-darwin' run: | printf 'SDKROOT=%s\n' "$(xcrun -sdk macosx --show-sdk-path)" >> "$GITHUB_ENV" printf 'MACOSX_DEPLOYMENT_TARGET=%s\n' "$(xcrun -sdk macosx --show-sdk-platform-version)" >> "$GITHUB_ENV" - name: cross setup if: contains(matrix.target, 'musl') uses: taiki-e/cache-cargo-install-action@59027ebf20a9617c4e819eb53ccd2673cb162b89 # v2 with: git: https://github.com/cross-rs/cross rev: baf457efc2555225af47963475bd70e8d2f5993f tool: cross - name: Build binaries env: TAG_NAME: ${{ (env.IS_NIGHTLY == 'true' && 'nightly') || needs.prepare.outputs.tag_name }} SVM_TARGET_PLATFORM: ${{ matrix.svm_target_platform }} PLATFORM_NAME: ${{ matrix.platform }} TARGET: ${{ matrix.target }} OUT_DIR: target/${{ matrix.target }}/${{ env.RUST_PROFILE }} shell: bash run: | set -eo pipefail flags=(--target $TARGET --profile $RUST_PROFILE --bins --no-default-features --features "$RUST_FEATURES") # `jemalloc` is not fully supported on MSVC or aarch64 Linux. if [[ "$TARGET" != *msvc* && "$TARGET" != "aarch64-unknown-linux-gnu" ]]; then flags+=(--features jemalloc) fi [[ "$TARGET" == *windows* ]] && ext=".exe" if [[ "$TARGET" == *-musl ]]; then cross build "${flags[@]}" else cargo build "${flags[@]}" fi bins=(anvil cast chisel forge) for name in "${bins[@]}"; do bin="$OUT_DIR/$name$ext" printf '\n' file "$bin" || true du -h "$bin" || true ldd "$bin" || true $bin --version || true printf '%s_bin_path=%s\n' "$name" "$bin" >> "$GITHUB_ENV" done - name: Archive binaries id: artifacts env: PLATFORM_NAME: ${{ matrix.platform }} OUT_DIR: target/${{ matrix.target }}/${{ env.RUST_PROFILE }} VERSION_NAME: ${{ (env.IS_NIGHTLY == 'true' && 'nightly') || needs.prepare.outputs.tag_name }} ARCH: ${{ matrix.arch }} shell: bash run: | if [[ "$PLATFORM_NAME" == "linux" || "$PLATFORM_NAME" == "alpine" ]]; then tar -czvf "foundry_${VERSION_NAME}_${PLATFORM_NAME}_${ARCH}.tar.gz" -C "$OUT_DIR" forge cast anvil chisel printf "file_name=%s\n" "foundry_${VERSION_NAME}_${PLATFORM_NAME}_${ARCH}.tar.gz" >> "$GITHUB_OUTPUT" elif [ "$PLATFORM_NAME" == "darwin" ]; then # We need to use gtar here otherwise the archive is corrupt. # See: https://github.com/actions/virtual-environments/issues/2619 gtar -czvf "foundry_${VERSION_NAME}_${PLATFORM_NAME}_${ARCH}.tar.gz" -C "$OUT_DIR" forge cast anvil chisel printf "file_name=%s\n" "foundry_${VERSION_NAME}_${PLATFORM_NAME}_${ARCH}.tar.gz" >> "$GITHUB_OUTPUT" else cd "$OUT_DIR" 7z a -tzip "foundry_${VERSION_NAME}_${PLATFORM_NAME}_${ARCH}.zip" forge.exe cast.exe anvil.exe chisel.exe mv "foundry_${VERSION_NAME}_${PLATFORM_NAME}_${ARCH}.zip" ../../../ printf "file_name=%s\n" "foundry_${VERSION_NAME}_${PLATFORM_NAME}_${ARCH}.zip" >> "$GITHUB_OUTPUT" fi printf "foundry_attestation=%s\n" "foundry_${VERSION_NAME}_${PLATFORM_NAME}_${ARCH}.attestation.txt" >> "$GITHUB_OUTPUT" - name: Upload build artifacts uses: actions/upload-artifact@v7 with: retention-days: 1 name: ${{ steps.artifacts.outputs.file_name }} path: ${{ steps.artifacts.outputs.file_name }} - name: Build man page id: man if: matrix.target == 'x86_64-unknown-linux-gnu' env: OUT_DIR: target/${{ matrix.target }}/${{ env.RUST_PROFILE }} VERSION_NAME: ${{ (env.IS_NIGHTLY == 'true' && 'nightly') || needs.prepare.outputs.tag_name }} shell: bash run: | sudo apt-get -y install help2man help2man -N $OUT_DIR/forge > forge.1 help2man -N $OUT_DIR/cast > cast.1 help2man -N $OUT_DIR/anvil > anvil.1 help2man -N $OUT_DIR/chisel > chisel.1 gzip forge.1 gzip cast.1 gzip anvil.1 gzip chisel.1 tar -czvf "foundry_man_${VERSION_NAME}.tar.gz" forge.1.gz cast.1.gz anvil.1.gz chisel.1.gz printf 'foundry_man=%s\n' "foundry_man_${VERSION_NAME}.tar.gz" >> "$GITHUB_OUTPUT" - name: Binaries attestation id: attestation uses: actions/attest-build-provenance@v4 with: subject-path: | ${{ env.anvil_bin_path }} ${{ env.cast_bin_path }} ${{ env.chisel_bin_path }} ${{ env.forge_bin_path }} - name: Record attestation URL env: ATTESTATION_URL: ${{ steps.attestation.outputs.attestation-url }} FOUNDRY_ATTESTATION: ${{ steps.artifacts.outputs.foundry_attestation }} shell: bash run: | set -euo pipefail printf '%s\n' "$ATTESTATION_URL" > "$FOUNDRY_ATTESTATION" # Creates the release for this specific version - name: Create release uses: softprops/action-gh-release@5be0e66d93ac7ed76da52eca8bb058f665c3a5fe # v2.4.2 with: name: ${{ needs.prepare.outputs.release_name }} tag_name: ${{ needs.prepare.outputs.tag_name }} prerelease: ${{ env.IS_NIGHTLY == 'true' }} body: ${{ needs.prepare.outputs.changelog }} files: | ${{ steps.artifacts.outputs.file_name }} ${{ steps.artifacts.outputs.foundry_attestation }} ${{ steps.man.outputs.foundry_man }} # If this is a nightly release, it also updates the release # tagged `nightly` for compatibility with `foundryup` - name: Update nightly release if: ${{ env.IS_NIGHTLY == 'true' }} uses: softprops/action-gh-release@5be0e66d93ac7ed76da52eca8bb058f665c3a5fe # v2.4.2 with: name: "Nightly" tag_name: "nightly" prerelease: true body: ${{ needs.prepare.outputs.changelog }} files: | ${{ steps.artifacts.outputs.file_name }} ${{ steps.artifacts.outputs.foundry_attestation }} ${{ steps.man.outputs.foundry_man }} cleanup: name: Release cleanup runs-on: ubuntu-latest timeout-minutes: 30 permissions: contents: write needs: release if: always() steps: - uses: actions/checkout@v6 with: persist-credentials: false # Moves the `nightly` tag to `HEAD` - name: Move nightly tag if: ${{ env.IS_NIGHTLY == 'true' }} uses: actions/github-script@v8 with: script: | const moveTag = require('./.github/scripts/move-tag.js') await moveTag({ github, context }, 'nightly') - name: Delete old nightlies uses: actions/github-script@v8 with: script: | const prunePrereleases = require('./.github/scripts/prune-prereleases.js') await prunePrereleases({github, context}) # If any of the jobs fail, this will create a high-priority issue to signal so. issue: name: Open an issue runs-on: ubuntu-latest needs: [prepare, release-docker, release, cleanup] if: failure() permissions: contents: read issues: write steps: - uses: actions/checkout@v6 with: persist-credentials: false - uses: JasonEtco/create-an-issue@1b14a70e4d8dc185e5cc76d3bec9eab20257b2c5 # v2 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} WORKFLOW_URL: | ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} with: update_existing: true filename: .github/RELEASE_FAILURE_ISSUE_TEMPLATE.md ================================================ FILE: .github/workflows/test-flaky.yml ================================================ # Daily CI job to run flaky tests that are excluded from regular CI via nextest default-filter name: test-flaky permissions: {} on: schedule: - cron: "0 1 * * *" # Run daily at 1 AM UTC (offset from test-isolate) workflow_dispatch: # Needed so we can run it manually concurrency: group: tests-${{ github.workflow }}-${{ github.head_ref || github.run_id }} cancel-in-progress: true env: CARGO_TERM_COLOR: always RUST_BACKTRACE: full RUSTC_WRAPPER: "sccache" jobs: test: name: flaky tests runs-on: depot-ubuntu-latest-16 timeout-minutes: 60 permissions: contents: read steps: - uses: actions/checkout@v6 with: persist-credentials: false - uses: dtolnay/rust-toolchain@e97e2d8cc328f1b50210efc529dca0028893a2d9 # master with: toolchain: stable - uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # v1 - uses: taiki-e/install-action@94a7388bec5d4c8dd93e3ebf09e0ff448f3f6f4d # v2 with: tool: nextest - uses: mozilla-actions/sccache-action@7d986dd989559c6ecdb630a3fd2557667be217ad # v0.0.9 - uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2 - name: Test flaky tests env: SVM_TARGET_PLATFORM: linux-amd64 HTTP_ARCHIVE_URLS: ${{ secrets.HTTP_ARCHIVE_URLS }} ETHERSCAN_KEY: ${{ secrets.ETHERSCAN_API_KEY }} run: cargo nextest run --profile flaky --no-fail-fast # If any of the jobs fail, this will create a normal-priority issue to signal so. issue: name: Open an issue runs-on: ubuntu-latest needs: [test] if: failure() permissions: contents: read issues: write steps: - uses: actions/checkout@v6 with: persist-credentials: false - uses: JasonEtco/create-an-issue@1b14a70e4d8dc185e5cc76d3bec9eab20257b2c5 # v2 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} WORKFLOW_URL: | ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} with: update_existing: true filename: .github/FLAKY_TEST_FAILURE_TEMPLATE.md ================================================ FILE: .github/workflows/test-isolate.yml ================================================ # Daily CI job to run tests with isolation mode enabled by default name: test-isolate permissions: {} on: schedule: - cron: "0 0 * * *" # Run daily at midnight UTC workflow_dispatch: # Needed so we can run it manually env: CARGO_TERM_COLOR: always RUST_BACKTRACE: full RUSTC_WRAPPER: "sccache" jobs: nextest: uses: ./.github/workflows/test.yml permissions: contents: read with: profile: isolate # Run flaky tests with isolation enabled flaky: name: flaky tests (isolate) runs-on: depot-ubuntu-latest-16 timeout-minutes: 60 permissions: contents: read steps: - uses: actions/checkout@v6 with: persist-credentials: false - uses: dtolnay/rust-toolchain@e97e2d8cc328f1b50210efc529dca0028893a2d9 # master with: toolchain: stable - uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # v1 - uses: taiki-e/install-action@94a7388bec5d4c8dd93e3ebf09e0ff448f3f6f4d # v2 with: tool: nextest - uses: mozilla-actions/sccache-action@7d986dd989559c6ecdb630a3fd2557667be217ad # v0.0.9 - uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2 - name: Test flaky tests with isolation env: SVM_TARGET_PLATFORM: linux-amd64 HTTP_ARCHIVE_URLS: ${{ secrets.HTTP_ARCHIVE_URLS }} ETHERSCAN_KEY: ${{ secrets.ETHERSCAN_API_KEY }} run: cargo nextest run --profile flaky --features=isolate-by-default --no-fail-fast # If nextest fails, create a high-priority issue for isolation failures. issue-isolate: name: Open isolation issue runs-on: ubuntu-latest needs: [nextest] if: failure() permissions: contents: read issues: write steps: - uses: actions/checkout@v6 with: persist-credentials: false - uses: JasonEtco/create-an-issue@1b14a70e4d8dc185e5cc76d3bec9eab20257b2c5 # v2 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} WORKFLOW_URL: | ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} with: update_existing: true filename: .github/TEST_ISOLATE_FAILURE_TEMPLATE.md # If flaky tests fail, create a normal-priority issue for flaky failures. issue-flaky: name: Open flaky issue runs-on: ubuntu-latest needs: [flaky] if: failure() permissions: contents: read issues: write steps: - uses: actions/checkout@v6 with: persist-credentials: false - uses: JasonEtco/create-an-issue@1b14a70e4d8dc185e5cc76d3bec9eab20257b2c5 # v2 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} WORKFLOW_URL: | ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} with: update_existing: true filename: .github/FLAKY_TEST_ISOLATE_FAILURE_TEMPLATE.md ================================================ FILE: .github/workflows/test.yml ================================================ # Reusable workflow for running tests via `cargo nextest` name: test permissions: {} on: workflow_call: inputs: profile: required: true type: string concurrency: group: tests-${{ github.workflow }}-${{ github.head_ref || github.run_id }} cancel-in-progress: true env: CARGO_TERM_COLOR: always RUST_BACKTRACE: full RUSTC_WRAPPER: "sccache" RUST_MIN_STACK: 4194304 # 2 * the default (2 * 1024 * 1024) jobs: matrices: name: build matrices runs-on: ubuntu-latest permissions: contents: read outputs: test-matrix: ${{ steps.gen.outputs.test-matrix }} steps: - uses: actions/checkout@v6 with: persist-credentials: false - uses: actions/setup-python@v6 with: python-version: "3.14" - name: Generate matrices id: gen env: EVENT_NAME: ${{ github.event_name }} PROFILE: ${{ inputs.profile }} shell: bash run: | output=$(python3 .github/scripts/matrices.py) printf '::debug::test-matrix=%s\n' "$output" printf 'test-matrix=%s\n' "$output" >> "$GITHUB_OUTPUT" test: name: test ${{ matrix.name }} runs-on: ${{ matrix.runner_label }} timeout-minutes: 60 permissions: contents: read needs: matrices strategy: fail-fast: false matrix: ${{ fromJson(needs.matrices.outputs.test-matrix) }} steps: - uses: actions/checkout@v6 with: persist-credentials: false - uses: dtolnay/rust-toolchain@e97e2d8cc328f1b50210efc529dca0028893a2d9 # master with: toolchain: stable target: ${{ matrix.target }} - uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # v1 - uses: taiki-e/install-action@94a7388bec5d4c8dd93e3ebf09e0ff448f3f6f4d # v2 with: tool: nextest # External tests dependencies - name: Setup Node.js if: contains(matrix.name, 'external') uses: actions/setup-node@v6 with: node-version: 24 - name: Install Bun if: contains(matrix.name, 'external') uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2 with: bun-version: latest - name: Setup Python uses: actions/setup-python@v6 with: python-version: "3.14" - name: Install Vyper # Also update vyper version in .devcontainer/Dockerfile.dev run: pip --version && pip install vyper==0.4.3 - name: Foundry test cache uses: actions/cache@v5 with: path: | ~/.foundry/cache ~/.config/.foundry/cache testdata/cache testdata/out # Use a unique key for each run to always update the cache. key: ${{ runner.os }}-foundry-${{ matrix.name }}-${{ github.run_id }} restore-keys: | ${{ runner.os }}-foundry-${{ matrix.name }}- - uses: mozilla-actions/sccache-action@7d986dd989559c6ecdb630a3fd2557667be217ad # v0.0.9 - uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2 - name: Setup Git config run: | git config --global user.name "GitHub Actions Bot" git config --global user.email "<>" git config --global url."https://github.com/".insteadOf "git@github.com:" - name: Test env: SVM_TARGET_PLATFORM: ${{ matrix.svm_target_platform }} HTTP_ARCHIVE_URLS: ${{ secrets.HTTP_ARCHIVE_URLS }} WS_ARCHIVE_URLS: ${{ secrets.WS_ARCHIVE_URLS }} ETHERSCAN_KEY: ${{ secrets.ETHERSCAN_API_KEY }} ARBITRUM_RPC: ${{ secrets.ARBITRUM_RPC }} ETH_SEPOLIA_RPC: ${{ secrets.ETH_SEPOLIA_RPC }} run: cargo nextest run ${{ matrix.flags }} ================================================ FILE: .gitignore ================================================ .DS_STORE /target* /*.sol CLAUDE.md # Foundry artifacts out/ snapshots/ # IDEs and extensions .idea .vscode .claude .zed # NPM .env node_modules npm/dist npm/bin _ *.tgz .vercel .vite .wrangler *.zip ================================================ FILE: CONTRIBUTING.md ================================================ ## Contributing to Foundry Thanks for your interest in improving Foundry! There are multiple opportunities to contribute at any level. It doesn't matter if you are just getting started with Rust or are the most weathered expert, we can use your help. This document will help you get started. **Do not let the document intimidate you**. It should be considered as a guide to help you navigate the process. The [dev Telegram][dev-tg] is available for any concerns you may have that are not covered in this guide. ### Code of Conduct The Foundry project adheres to the [Rust Code of Conduct][rust-coc]. This code of conduct describes the _minimum_ behavior expected from all contributors. Instances of violations of the Code of Conduct can be reported by contacting the team at [me@gakonst.com](mailto:me@gakonst.com). ### Ways to contribute There are fundamentally four ways an individual can contribute: 1. **By opening an issue:** For example, if you believe that you have uncovered a bug in Foundry, creating a new issue in the issue tracker is the way to report it. 2. **By adding context:** Providing additional context to existing issues, such as screenshots and code snippets, which help resolve issues. 3. **By resolving issues:** Typically this is done in the form of either demonstrating that the issue reported is not a problem after all, or more often, by opening a pull request that fixes the underlying problem, in a concrete and reviewable manner. **Anybody can participate in any stage of contribution**. We urge you to participate in the discussion around bugs and participate in reviewing PRs. ### Contributions Related to Spelling and Grammar At this time, we will not be accepting contributions that only fix spelling or grammatical errors in documentation, code or elsewhere. ### Contributions Assisted by AI If you are using any kind of AI assistance while contributing to Foundry, **this must be disclosed in the pull request**, along with the extent to which AI assistance was used (e.g. docs only vs. code generation). If PR responses are being generated by an AI, disclose that as well. As a small exception, trivial tab-completion doesn't need to be disclosed, so long as it is limited to single keywords or short phrases. An example disclosure: > This PR was written primarily by Claude Code. Or a more detailed disclosure: > I consulted ChatGPT to understand the codebase but the solution > was fully authored manually by myself. Failure to disclose this makes it difficult to determine how much scrutiny to apply to the contribution as it is unclear how much the contributor themselves understands the code they propose to merge. If you do not disclose your use of AI assistance and reviewers suspect your contribution to be AI generated, it will likely be refused on those grounds. ### Asking for help 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. 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. ### Submitting a bug report When filing a new bug report in the issue tracker, you will be presented with a basic form to fill out. If you believe that you have uncovered a bug, please fill out the form to the best of your ability. Do not worry if you cannot answer every detail; just fill in what you can. Contributors will ask follow-up questions if something is unclear. 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 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! See [this guide][mcve] on how to create a minimal, complete, and verifiable example. ### Submitting a feature request When adding a feature request in the issue tracker, you will be presented with a basic form to fill out. Please include as detailed of an explanation as possible of the feature you would like, adding additional context if necessary. If you have examples of other tools that have the feature you are requesting, please include them as well. ### Resolving an issue Pull requests are the way concrete changes are made to the code, documentation, and dependencies of Foundry. Even minor pull requests, such as those fixing wording, are greatly appreciated. Before making a large change, it is usually a good idea to first open an issue describing the change to solicit feedback and guidance. This will increase the likelihood of the PR getting merged. Please also make sure that the following commands pass if you have changed the code: ```sh cargo check --all cargo test --all --all-features cargo +nightly fmt -- --check cargo +nightly clippy --workspace --all-targets --all-features -- -D warnings ``` 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: ```json "editor.formatOnSave": true, "rust-analyzer.rustfmt.extraArgs": ["+nightly"], "[rust]": { "editor.defaultFormatter": "rust-lang.rust-analyzer" } ``` If you are working on a larger feature, we encourage you to open up a draft pull request, to make sure that other contributors are not duplicating work. If you would like to test the binaries built from your change, see [foundryup](https://github.com/foundry-rs/foundry/tree/HEAD/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/HEAD/Cargo.toml#L15-L18). #### Adding tests If the change being proposed alters code, it is either adding new functionality to Foundry, or fixing existing, broken functionality. In both of these cases, the pull request should include one or more tests to ensure that Foundry does not regress 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. Tests that use forking must contain "fork" in their name. #### Commits It is a recommended best practice to keep your changes as logically grouped as possible within individual commits. There is no limit to the number of commits any single pull request may have, and many contributors find it easier to review changes that are split across multiple commits. That said, if you have a number of commits that are "checkpoints" and don't represent a single logical change, please squash those together. #### Opening the pull request From within GitHub, opening a new pull request will present you with a template that should be filled out. Please try your best at filling out the details, but feel free to skip parts if you're not sure what to put. #### Discuss and update You will probably get feedback or requests for changes to your pull request. This is a big part of the submission process, so don't be discouraged! Some contributors may sign off on the pull request right away, others may have more detailed comments or feedback. This is a necessary part of the process in order to evaluate whether the changes are correct and necessary. **Any community member can review a PR, so you might get conflicting feedback**. Keep an eye out for comments from code owners to provide guidance on conflicting feedback. #### Reviewing pull requests **Any Foundry community member is welcome to review any pull request**. All contributors who choose to review and provide feedback on pull requests have a responsibility to both the project and individual making the contribution. Reviews and feedback must be helpful, insightful, and geared towards improving the contribution as opposed to simply blocking it. If there are reasons why you feel the PR should not be merged, explain what those are. Do not expect to be able to block a PR from advancing simply because you say "no" without giving an explanation. Be open to having your mind changed. Be open to working _with_ the contributor to make the pull request better. Reviews that are dismissive or disrespectful of the contributor or any other reviewers are strictly counter to the Code of Conduct. When reviewing a pull request, the primary goals are for the codebase to improve and for the person submitting the request to succeed. **Even if a pull request is not merged, the submitter should come away from the experience feeling like their effort was not unappreciated**. Every PR from a new contributor is an opportunity to grow the community. ##### Review a bit at a time Do not overwhelm new contributors. It is tempting to micro-optimize and make everything about relative performance, perfect grammar, or exact style matches. Do not succumb to that temptation.. Focus first on the most significant aspects of the change: 1. Does this change make sense for Foundry? 2. Does this change make Foundry better, even if only incrementally? 3. Are there clear bugs or larger scale issues that need attending? 4. Are the commit messages readable and correct? If it contains a breaking change, is it clear enough? Note that only **incremental** improvement is needed to land a PR. This means that the PR does not need to be perfect, only better than the status quo. Follow-up PRs may be opened to continue iterating. When changes are necessary, _request_ them, do not _demand_ them, and **do not assume that the submitter already knows how to add a test or run a benchmark**. Specific performance optimization techniques, coding styles and conventions change over time. The first impression you give to a new contributor never does. Nits (requests for small changes that are not essential) are fine, but try to avoid stalling the pull request. Most nits can typically be fixed by the Foundry maintainers merging the pull request, but they can also be an opportunity for the contributor to learn a bit more about the project. It is always good to clearly indicate nits when you comment, e.g.: `Nit: change foo() to bar(). But this is not blocking`. If your comments were addressed but were not folded after new commits, or if they proved to be mistaken, please, [hide them][hiding-a-comment] with the appropriate reason to keep the conversation flow concise and relevant. ##### Be aware of the person behind the code Be aware that _how_ you communicate requests and reviews in your feedback can have a significant impact on the success of the pull request. Yes, we may merge a particular change that makes Foundry better, but the individual might just not want to have anything to do with Foundry ever again. The goal is not just having good code. ##### Abandoned or stale pull requests If a pull request appears to be abandoned or stalled, it is polite to first check with the contributor to see if they intend to continue the work before checking if they would mind if you took it over (especially if it just has nits left). When doing so, it is courteous to give the original contributor credit for the work they started, either by preserving their name and e-mail address in the commit log, or by using the `Author:` or `Co-authored-by:` metadata tag in the commits. _Adapted from the [ethers-rs contributing guide](https://github.com/gakonst/ethers-rs/blob/master/CONTRIBUTING.md)_. ### Releasing Releases are automatically done by the release workflow when a tag is pushed, however, these steps still need to be taken: 1. Ensure that the versions in the relevant `Cargo.toml` files are up-to-date. 2. Update documentation links 3. Perform a final audit for breaking changes. [rust-coc]: https://www.rust-lang.org/policies/code-of-conduct [dev-tg]: https://t.me/foundry_rs [foundry-book]: https://github.com/foundry-rs/foundry-book [support-tg]: https://t.me/foundry_support [mcve]: https://stackoverflow.com/help/mcve [hiding-a-comment]: https://docs.github.com/en/communities/moderating-comments-and-conversations/managing-disruptive-comments ================================================ FILE: Cargo.toml ================================================ [workspace] members = [ "benches/", "crates/anvil/", "crates/anvil/core/", "crates/anvil/rpc/", "crates/anvil/server/", "crates/cast/", "crates/cheatcodes/", "crates/cheatcodes/spec/", "crates/chisel/", "crates/cli/", "crates/common/", "crates/config/", "crates/debugger/", "crates/doc/", "crates/evm/core/", "crates/evm/coverage/", "crates/evm/evm/", "crates/evm/fuzz/", "crates/evm/traces/", "crates/fmt/", "crates/forge/", "crates/lint/", "crates/macros/", "crates/primitives/", "crates/script-sequence/", "crates/test-utils/", "crates/cli-markdown/", ] resolver = "2" [workspace.package] version = "1.6.0" edition = "2024" rust-version = "1.89" authors = ["Foundry Contributors"] license = "MIT OR Apache-2.0" homepage = "https://getfoundry.sh" 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" uninlined-format-args = "warn" use-self = "warn" redundant-clone = "warn" octal-escapes = "allow" # until is fixed literal-string-with-formatting-args = "allow" result_large_err = "allow" [workspace.lints.rust] redundant_imports = "warn" redundant-lifetimes = "warn" rust-2018-idioms = "warn" unused-must-use = "warn" # unreachable-pub = "warn" [workspace.lints.rustdoc] all = "warn" # Speed up compilation time for dev builds by reducing emitted debug info. # NOTE: Debuggers may provide less useful information with this setting. # Uncomment this section if you're using a debugger. [profile.dev] # https://davidlattimore.github.io/posts/2024/02/04/speeding-up-the-rust-edit-build-run-cycle.html debug = "line-tables-only" split-debuginfo = "unpacked" [profile.release] opt-level = 3 lto = "thin" debug = "none" strip = "debuginfo" panic = "abort" codegen-units = 16 # Use the `--profile profiling` flag to show symbols in release mode. # e.g. `cargo build --profile profiling` [profile.profiling] inherits = "release" debug = "full" strip = "none" [profile.bench] inherits = "profiling" [profile.dist] inherits = "release" lto = "fat" codegen-units = 1 # Speed up tests and dev build. [profile.dev.package] # Solc and artifacts. foundry-compilers-artifacts-solc.opt-level = 3 foundry-compilers-core.opt-level = 3 foundry-compilers.opt-level = 3 serde_json.opt-level = 3 serde.opt-level = 3 foundry-solang-parser.opt-level = 3 lalrpop-util.opt-level = 3 solar-compiler.opt-level = 3 solar-ast.opt-level = 3 solar-data-structures.opt-level = 3 solar-interface.opt-level = 3 solar-parse.opt-level = 3 solar-sema.opt-level = 3 # CLI. clap.opt-level = 3 clap_builder.opt-level = 3 clap_lex.opt-level = 3 # EVM. alloy-dyn-abi.opt-level = 3 alloy-json-abi.opt-level = 3 alloy-primitives.opt-level = 3 alloy-sol-type-parser.opt-level = 3 alloy-sol-types.opt-level = 3 alloy-evm.opt-level = 3 bitvec.opt-level = 3 revm.opt-level = 3 revm-bytecode.opt-level = 3 revm-context.opt-level = 3 revm-context-interface.opt-level = 3 revm-database.opt-level = 3 revm-database-interface.opt-level = 3 revm-handler.opt-level = 3 revm-inspector.opt-level = 3 revm-interpreter.opt-level = 3 revm-precompile.opt-level = 3 revm-primitives.opt-level = 3 revm-state.opt-level = 3 revm-inspectors.opt-level = 3 ruint.opt-level = 3 sha2.opt-level = 3 sha3.opt-level = 3 keccak.opt-level = 3 # Fuzzing. proptest.opt-level = 3 foundry-evm-fuzz.opt-level = 3 rand.opt-level = 3 rand_chacha.opt-level = 3 ppv-lite86.opt-level = 3 # Backtraces. addr2line.opt-level = 3 backtrace.opt-level = 3 gimli.opt-level = 3 # Forking. axum.opt-level = 3 # Keystores. scrypt.opt-level = 3 # Misc. hashbrown.opt-level = 3 foldhash.opt-level = 3 rayon.opt-level = 3 rayon-core.opt-level = 3 regex.opt-level = 3 regex-syntax.opt-level = 3 regex-automata.opt-level = 3 walkdir.opt-level = 3 # Override packages which aren't perf-sensitive for faster compilation speed and smaller binary size. [profile.release.package] alloy-sol-macro-expander.opt-level = "s" figment2.opt-level = "s" foundry-compilers-artifacts-solc.opt-level = "s" foundry-config.opt-level = "s" html5ever.opt-level = "s" mdbook-driver.opt-level = "s" prettyplease.opt-level = "s" protobuf.opt-level = "s" pulldown-cmark.opt-level = "s" syn-solidity.opt-level = "s" syn.opt-level = "s" trezor-client.opt-level = "s" # Networking stack (not perf-critical for CLI tools). reqwest.opt-level = "s" hyper.opt-level = "s" hyper-util.opt-level = "s" h2.opt-level = "s" rustls.opt-level = "s" tower.opt-level = "s" tower-http.opt-level = "s" # Doc generation / templating. mdbook-core.opt-level = "s" mdbook-html.opt-level = "s" font-awesome-as-a-crate.opt-level = "s" handlebars.opt-level = "s" # Watch mode. watchexec.opt-level = "s" watchexec-events.opt-level = "s" watchexec-signals.opt-level = "s" watchexec-supervisor.opt-level = "s" # Soldeer package manager. soldeer-commands.opt-level = "s" soldeer-core.opt-level = "s" # Parsing / editing (not perf-sensitive in CLI). toml_edit.opt-level = "s" toml.opt-level = "s" # Block explorers / verification. foundry-block-explorers.opt-level = "s" # Misc CLI dependencies. clap_builder.opt-level = "s" clap_complete.opt-level = "s" comfy-table.opt-level = "s" indicatif.opt-level = "s" dialoguer.opt-level = "s" ratatui.opt-level = "s" crossterm.opt-level = "s" # Alloy JSON (parsing, not hot path). alloy-json-abi.opt-level = "s" [workspace.dependencies] anvil = { path = "crates/anvil" } cast = { path = "crates/cast" } chisel = { path = "crates/chisel" } 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" } forge-script-sequence = { path = "crates/script-sequence" } foundry-cheatcodes = { path = "crates/cheatcodes" } foundry-cheatcodes-spec = { path = "crates/cheatcodes/spec" } foundry-cli = { path = "crates/cli" } foundry-cli-markdown = { path = "crates/cli-markdown" } foundry-common = { path = "crates/common" } foundry-common-fmt = { path = "crates/common/fmt" } foundry-config = { path = "crates/config" } foundry-debugger = { path = "crates/debugger" } foundry-evm = { path = "crates/evm/evm" } foundry-evm-abi = { path = "crates/evm/abi" } foundry-evm-core = { path = "crates/evm/core" } foundry-evm-coverage = { path = "crates/evm/coverage" } foundry-evm-networks = { path = "crates/evm/networks" } foundry-evm-fuzz = { path = "crates/evm/fuzz" } foundry-evm-traces = { path = "crates/evm/traces" } foundry-macros = { path = "crates/macros" } foundry-test-utils = { path = "crates/test-utils" } foundry-wallets = { path = "crates/wallets" } foundry-linking = { path = "crates/linking" } foundry-primitives = { path = "crates/primitives" } # solc & compilation utilities foundry-block-explorers = { version = "0.22.0", default-features = false } foundry-compilers = { version = "0.19.14", default-features = false, features = [ "rustls", "svm-solc", ] } foundry-fork-db = "0.23" solang-parser = { version = "=0.3.9", package = "foundry-solang-parser" } solar = { package = "solar-compiler", version = "=0.1.8", default-features = false } svm = { package = "svm-rs", version = "0.5", default-features = false, features = [ "rustls", ] } ## alloy alloy-consensus = { version = "2.0.0-rc.0", default-features = false } alloy-contract = { version = "2.0.0-rc.0", default-features = false } alloy-eips = { version = "2.0.0-rc.0", default-features = false } alloy-eip5792 = { version = "2.0.0-rc.0", default-features = false } alloy-ens = { version = "2.0.0-rc.0", default-features = false } alloy-genesis = { version = "2.0.0-rc.0", default-features = false } alloy-json-rpc = { version = "2.0.0-rc.0", default-features = false } alloy-network = { version = "2.0.0-rc.0", default-features = false } alloy-provider = { version = "2.0.0-rc.0", default-features = false } alloy-pubsub = { version = "2.0.0-rc.0", default-features = false } alloy-rpc-client = { version = "2.0.0-rc.0", default-features = false } alloy-rpc-types = { version = "2.0.0-rc.0", default-features = true } alloy-rpc-types-beacon = { version = "2.0.0-rc.0", default-features = true } alloy-rpc-types-eth = { version = "2.0.0-rc.0", default-features = false } alloy-serde = { version = "2.0.0-rc.0", default-features = false } alloy-signer = { version = "2.0.0-rc.0", default-features = false } alloy-signer-aws = { version = "2.0.0-rc.0", default-features = false } alloy-signer-gcp = { version = "2.0.0-rc.0", default-features = false } alloy-signer-ledger = { version = "2.0.0-rc.0", default-features = false } alloy-signer-local = { version = "2.0.0-rc.0", default-features = false } alloy-signer-trezor = { version = "2.0.0-rc.0", default-features = false } alloy-signer-turnkey = { version = "2.0.0-rc.0", default-features = false } alloy-transport = { version = "2.0.0-rc.0", default-features = false } alloy-transport-http = { version = "2.0.0-rc.0", default-features = false } alloy-transport-ipc = { version = "2.0.0-rc.0", default-features = false } alloy-transport-ws = { version = "2.0.0-rc.0", default-features = false } alloy-hardforks = { version = "0.4.7", default-features = false } alloy-op-hardforks = { version = "0.4.7", default-features = false } ## alloy-core alloy-dyn-abi = "1.5.2" alloy-json-abi = "1.5.2" alloy-primitives = { version = "1.5.2", features = [ "getrandom", "rand", "map-fxhash", "map-foldhash", ] } alloy-sol-macro-expander = "1.5.2" alloy-sol-macro-input = "1.5.2" alloy-sol-types = "1.5.2" alloy-chains = "0.2" alloy-rlp = "0.3" alloy-trie = "0.9" ## op-alloy op-alloy-consensus = "0.24.0" op-alloy-network = "0.24.0" op-alloy-rpc-types = "0.24.0" op-alloy-flz = "0.13.1" ## alloy-evm alloy-evm = "0.28.1" alloy-op-evm = "0.26.3" # revm revm = { version = "34.0.0", default-features = false } revm-inspectors = { version = "0.36.0", features = ["serde"] } op-revm = { version = "15.0.0", default-features = false } ## cli anstream = "0.6" anstyle = "1.0" dialoguer = { version = "0.12", default-features = false, features = [ "password", ] } clap_complete = "4" clap_complete_nushell = "4" # macros proc-macro2 = "1.0" quote = "1.0" syn = "2.0" async-trait = "0.1" derive_more = { version = "2.1", features = ["full"] } thiserror = "2" # allocators mimalloc = "0.1" tikv-jemallocator = "0.6" # misc auto_impl = "1" bytes = "1.11" walkdir = "2" prettyplease = "0.2" base64 = "0.22" chrono = { version = "0.4", default-features = false, features = [ "clock", "std", ] } axum = "0.8" ciborium = "0.2" color-eyre = "0.6" comfy-table = "7" dirs = "6" dunce = "1" evm-disassembler = "0.6" evmole = "0.8" eyre = "0.6" figment = { package = "figment2", version = "0.11" } futures = { version = "0.3", default-features = false } hyper = "1.8" indicatif = "0.18" itertools = "0.14" jsonpath_lib = "0.3" k256 = "0.13" mesc = "0.3" memchr = "2.7" num-format = "0.4" parking_lot = "0.12" proptest = "1.9.0" rand = "0.9" rand_08 = { package = "rand", version = "0.8" } rayon = "1" regex = { version = "1", default-features = false } reqwest = { version = "0.13", default-features = false, features = ["rustls"] } rustls = "0.23" semver = "1" serde = { version = "1.0", features = ["derive"] } serde_json = { version = "1.0", features = ["arbitrary_precision"] } similar-asserts = "1.7" soldeer-commands = "=0.10.0" soldeer-core = { version = "=0.10.0", features = ["serde"] } strum = "0.27" tempfile = "3.23" tokio = "1" toml = "0.9" toml_edit = "0.24" tower = "0.5" tower-http = "0.6" tracing = "0.1" tracing-subscriber = "0.3" url = "2" vergen = { package = "vergen-gitcl", version = "10.0.0-beta.5", default-features = false, features = [ "build", "cargo", ] } yansi = { version = "1.0", features = ["detect-tty", "detect-env"] } path-slash = "0.2" jiff = { version = "0.2", default-features = false, features = [ "std", "perf-inline", ] } heck = "0.5" uuid = "1.19.0" flate2 = "1.1" ethereum_ssz = "0.10" # Tempo tempo-primitives = { git = "https://github.com/tempoxyz/tempo", branch = "alloy-2.0", default-features = false, features = ["serde"] } tempo-alloy = { git = "https://github.com/tempoxyz/tempo", branch = "alloy-2.0", default-features = false } ## Pinned dependencies. Enabled for the workspace in crates/test-utils. # testing 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" [patch.crates-io] # https://github.com/rust-cli/rexpect/pull/106 rexpect = { git = "https://github.com/rust-cli/rexpect", rev = "2ed0b1898d7edaf6a85bedbae71a01cc578958fc" } ## alloy-core # alloy-dyn-abi = { path = "../../alloy-rs/core/crates/dyn-abi" } # alloy-json-abi = { path = "../../alloy-rs/core/crates/json-abi" } # alloy-primitives = { path = "../../alloy-rs/core/crates/primitives" } # alloy-sol-macro = { path = "../../alloy-rs/core/crates/sol-macro" } # alloy-sol-macro-expander = { path = "../../alloy-rs/core/crates/sol-macro-expander" } # alloy-sol-macro-input = { path = "../../alloy-rs/core/crates/sol-macro-input" } # alloy-sol-type-parser = { path = "../../alloy-rs/core/crates/sol-type-parser" } # alloy-sol-types = { path = "../../alloy-rs/core/crates/sol-types" } # syn-solidity = { path = "../../alloy-rs/core/crates/syn-solidity" } ## alloy alloy-consensus = { git = "https://github.com/alloy-rs/alloy", rev = "100a3325ac4d624f3eb0bd404125750e76edf8ca" } alloy-contract = { git = "https://github.com/alloy-rs/alloy", rev = "100a3325ac4d624f3eb0bd404125750e76edf8ca" } alloy-eips = { git = "https://github.com/alloy-rs/alloy", rev = "100a3325ac4d624f3eb0bd404125750e76edf8ca" } alloy-eip5792 = { git = "https://github.com/alloy-rs/alloy", rev = "100a3325ac4d624f3eb0bd404125750e76edf8ca" } alloy-ens = { git = "https://github.com/alloy-rs/alloy", rev = "100a3325ac4d624f3eb0bd404125750e76edf8ca" } alloy-genesis = { git = "https://github.com/alloy-rs/alloy", rev = "100a3325ac4d624f3eb0bd404125750e76edf8ca" } alloy-json-rpc = { git = "https://github.com/alloy-rs/alloy", rev = "100a3325ac4d624f3eb0bd404125750e76edf8ca" } alloy-network = { git = "https://github.com/alloy-rs/alloy", rev = "100a3325ac4d624f3eb0bd404125750e76edf8ca" } alloy-network-primitives = { git = "https://github.com/alloy-rs/alloy", rev = "100a3325ac4d624f3eb0bd404125750e76edf8ca" } alloy-provider = { git = "https://github.com/alloy-rs/alloy", rev = "100a3325ac4d624f3eb0bd404125750e76edf8ca" } alloy-pubsub = { git = "https://github.com/alloy-rs/alloy", rev = "100a3325ac4d624f3eb0bd404125750e76edf8ca" } alloy-rpc-client = { git = "https://github.com/alloy-rs/alloy", rev = "100a3325ac4d624f3eb0bd404125750e76edf8ca" } alloy-rpc-types = { git = "https://github.com/alloy-rs/alloy", rev = "100a3325ac4d624f3eb0bd404125750e76edf8ca" } alloy-rpc-types-beacon = { git = "https://github.com/alloy-rs/alloy", rev = "100a3325ac4d624f3eb0bd404125750e76edf8ca" } alloy-rpc-types-eth = { git = "https://github.com/alloy-rs/alloy", rev = "100a3325ac4d624f3eb0bd404125750e76edf8ca" } alloy-rpc-types-trace = { git = "https://github.com/alloy-rs/alloy", rev = "100a3325ac4d624f3eb0bd404125750e76edf8ca" } alloy-rpc-types-engine = { git = "https://github.com/alloy-rs/alloy", rev = "100a3325ac4d624f3eb0bd404125750e76edf8ca" } alloy-serde = { git = "https://github.com/alloy-rs/alloy", rev = "100a3325ac4d624f3eb0bd404125750e76edf8ca" } alloy-signer = { git = "https://github.com/alloy-rs/alloy", rev = "100a3325ac4d624f3eb0bd404125750e76edf8ca" } alloy-signer-aws = { git = "https://github.com/alloy-rs/alloy", rev = "100a3325ac4d624f3eb0bd404125750e76edf8ca" } alloy-signer-gcp = { git = "https://github.com/alloy-rs/alloy", rev = "100a3325ac4d624f3eb0bd404125750e76edf8ca" } alloy-signer-ledger = { git = "https://github.com/alloy-rs/alloy", rev = "100a3325ac4d624f3eb0bd404125750e76edf8ca" } alloy-signer-local = { git = "https://github.com/alloy-rs/alloy", rev = "100a3325ac4d624f3eb0bd404125750e76edf8ca" } alloy-signer-trezor = { git = "https://github.com/alloy-rs/alloy", rev = "100a3325ac4d624f3eb0bd404125750e76edf8ca" } alloy-signer-turnkey = { git = "https://github.com/alloy-rs/alloy", rev = "100a3325ac4d624f3eb0bd404125750e76edf8ca" } alloy-transport = { git = "https://github.com/alloy-rs/alloy", rev = "100a3325ac4d624f3eb0bd404125750e76edf8ca" } alloy-transport-http = { git = "https://github.com/alloy-rs/alloy", rev = "100a3325ac4d624f3eb0bd404125750e76edf8ca" } alloy-transport-ipc = { git = "https://github.com/alloy-rs/alloy", rev = "100a3325ac4d624f3eb0bd404125750e76edf8ca" } alloy-transport-ws = { git = "https://github.com/alloy-rs/alloy", rev = "100a3325ac4d624f3eb0bd404125750e76edf8ca" } ## alloy-evm alloy-evm = { git = "https://github.com/alloy-rs/evm.git", branch = "alloy-2.0" } ## op-alloy / alloy-op-evm op-alloy-consensus = { git = "https://github.com/foundry-rs/optimism", branch = "alloy-2.0" } op-alloy-network = { git = "https://github.com/foundry-rs/optimism", branch = "alloy-2.0" } op-alloy-rpc-types = { git = "https://github.com/foundry-rs/optimism", branch = "alloy-2.0" } op-alloy = { git = "https://github.com/foundry-rs/optimism", branch = "alloy-2.0" } alloy-op-evm = { git = "https://github.com/foundry-rs/optimism", branch = "alloy-2.0" } alloy-op-hardforks = { git = "https://github.com/foundry-rs/optimism", branch = "alloy-2.0" } ## revm # revm = { git = "https://github.com/bluealloy/revm.git", rev = "7e59936" } # op-revm = { git = "https://github.com/bluealloy/revm.git", rev = "7e59936" } revm-inspectors = { git = "https://github.com/paradigmxyz/revm-inspectors.git", branch = "alloy-2.0" } ## foundry foundry-fork-db = { git = "https://github.com/foundry-rs/foundry-fork-db", branch = "alloy-2.0" } # solar solar = { package = "solar-compiler", git = "https://github.com/paradigmxyz/solar", rev = "530f129" } solar-interface = { package = "solar-interface", git = "https://github.com/paradigmxyz/solar", rev = "530f129" } solar-ast = { package = "solar-ast", git = "https://github.com/paradigmxyz/solar", rev = "530f129" } solar-sema = { package = "solar-sema", git = "https://github.com/paradigmxyz/solar", rev = "530f129" } ================================================ FILE: Dockerfile ================================================ # syntax=docker/dockerfile:1 FROM rust:1-bookworm AS chef WORKDIR /app RUN apt update && apt install -y build-essential libssl-dev git pkg-config curl perl RUN curl -L --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/cargo-bins/cargo-binstall/main/install-from-binstall-release.sh | sh RUN cargo binstall cargo-chef sccache # Prepare the cargo-chef recipe. FROM chef AS planner COPY . . RUN cargo chef prepare --recipe-path recipe.json # Build the project. FROM chef AS builder COPY --from=planner /app/recipe.json recipe.json ARG RUST_PROFILE ARG RUST_FEATURES ENV CARGO_INCREMENTAL=0 \ RUSTC_WRAPPER=sccache \ SCCACHE_DIR=/sccache # Build dependencies. RUN --mount=type=cache,target=/usr/local/cargo/registry,sharing=shared \ --mount=type=cache,target=/usr/local/cargo/git,sharing=shared \ --mount=type=cache,target=$SCCACHE_DIR,sharing=shared \ cargo chef cook --recipe-path recipe.json --profile ${RUST_PROFILE} --no-default-features --features "${RUST_FEATURES}" ARG TAG_NAME="dev" ENV TAG_NAME=$TAG_NAME ARG VERGEN_GIT_SHA="ffffffffffffffffffffffffffffffffffffffff" ENV VERGEN_GIT_SHA=$VERGEN_GIT_SHA # Build the project. COPY . . RUN --mount=type=cache,target=/usr/local/cargo/registry,sharing=shared \ --mount=type=cache,target=/usr/local/cargo/git,sharing=shared \ --mount=type=cache,target=$SCCACHE_DIR,sharing=shared \ cargo build --profile ${RUST_PROFILE} --no-default-features --features "${RUST_FEATURES}" \ && sccache --show-stats || true # `dev` profile outputs to the `target/debug` directory. RUN ln -s /app/target/debug /app/target/dev \ && mkdir -p /app/output \ && mv \ /app/target/${RUST_PROFILE}/forge \ /app/target/${RUST_PROFILE}/cast \ /app/target/${RUST_PROFILE}/anvil \ /app/target/${RUST_PROFILE}/chisel \ /app/output/ FROM ubuntu:22.04 AS runtime # Install runtime dependencies. RUN apt update && apt install -y git COPY --from=builder /app/output/* /usr/local/bin/ RUN groupadd -g 1000 foundry && \ useradd -m -u 1000 -g foundry foundry USER foundry ENTRYPOINT ["/bin/sh", "-c"] LABEL org.label-schema.build-date=$BUILD_DATE \ org.label-schema.name="Foundry" \ org.label-schema.description="Foundry" \ org.label-schema.url="https://getfoundry.sh" \ org.label-schema.vcs-ref=$VCS_REF \ org.label-schema.vcs-url="https://github.com/foundry-rs/foundry.git" \ org.label-schema.vendor="Foundry-rs" \ org.label-schema.version=$VERSION \ org.label-schema.schema-version="1.0" ================================================ FILE: FUNDING.json ================================================ { "drips": { "ethereum": { "ownedBy": "0x86308c59a6005d012C51Eef104bBc21786aC5D2E" } }, "opRetro": { "projectId": "0x4562c0630907577f433cad78c7e2cc03349d918b6c14ef982f11a2678f5999ad" } } ================================================ FILE: LICENSE-APACHE ================================================ Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. ================================================ FILE: LICENSE-MIT ================================================ Copyright (c) 2021 Georgios Konstantopoulos Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: Makefile ================================================ # Heavily inspired by: # - Lighthouse: https://github.com/sigp/lighthouse/blob/693886b94176faa4cb450f024696cb69cda2fe58/Makefile # - Reth: https://github.com/paradigmxyz/reth/blob/1f642353ca083b374851ab355b5d80207b36445c/Makefile .DEFAULT_GOAL := help # 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 # List of features to use when building. Can be overridden via the environment. # No jemalloc on Windows ifeq ($(OS),Windows_NT) FEATURES ?= aws-kms gcp-kms turnkey cli asm-keccak else FEATURES ?= jemalloc aws-kms gcp-kms turnkey cli asm-keccak endif ##@ Help .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%-20s\033[0m %s\n", $$1, $$2 } /^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) } ' $(MAKEFILE_LIST) ##@ Build .PHONY: build build: ## Build the project. cargo build --features "$(FEATURES)" --profile "$(PROFILE)" .PHONY: build-docker build-docker: ## Build the docker image. docker build . -t "$(DOCKER_IMAGE_NAME)" \ --build-arg "RUST_PROFILE=$(PROFILE)" \ --build-arg "RUST_FEATURES=$(FEATURES)" \ --build-arg "TAG_NAME=dev" \ --build-arg "VERGEN_GIT_SHA=$(shell git rev-parse HEAD)" ##@ Test ## Run unit/doc tests and generate html coverage report in `target/llvm-cov/html` folder. ## Notice that `llvm-cov` supports doc tests only in nightly builds because the `--doc` flag ## is unstable (https://github.com/taiki-e/cargo-llvm-cov/issues/2). .PHONY: test-coverage test-coverage: cargo +nightly llvm-cov --no-report nextest -E 'kind(test) & !test(/\b(issue|ext_integration|flaky_)/)' && \ cargo +nightly llvm-cov --no-report --doc && \ cargo +nightly llvm-cov report --doctests --open .PHONY: test-unit test-unit: ## Run unit tests. cargo nextest run -E 'kind(test) & !test(/\b(issue|ext_integration|flaky_)/)' .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 .PHONY: lint-clippy lint-clippy: ## Run clippy on the codebase. cargo +nightly clippy \ --workspace \ --all-targets \ --all-features \ -- -D warnings .PHONY: lint-clippy-fix lint-clippy-fix: ## Run clippy on the codebase and fix warnings. cargo +nightly clippy \ --workspace \ --all-targets \ --all-features \ --fix \ --allow-dirty \ --allow-staged \ -- -D warnings .PHONY: lint-typos lint-typos: ## Run typos on the codebase. @command -v typos >/dev/null || { \ echo "typos not found. Please install it by running the command `cargo install typos-cli` or refer to the following link for more information: https://github.com/crate-ci/typos"; \ exit 1; \ } typos .PHONY: lint lint: ## Run all linters. $(MAKE) fmt && \ $(MAKE) lint-clippy && \ $(MAKE) lint-typos ##@ Other .PHONY: clean clean: ## Clean the project. cargo clean .PHONY: deny deny: ## Perform a `cargo` deny check. cargo deny --all-features check all .PHONY: pr pr: ## Run all checks and tests. $(MAKE) deny && \ $(MAKE) lint && \ $(MAKE) test # dprint formatting commands .PHONY: dprint-fmt dprint-fmt: ## Format code with dprint @if ! command -v dprint > /dev/null; then \ echo "Installing dprint..."; \ cargo install dprint; \ fi dprint fmt .PHONY: dprint-check dprint-check: ## Check formatting with dprint @if ! command -v dprint > /dev/null; then \ echo "Installing dprint..."; \ cargo install dprint; \ fi dprint check ================================================ FILE: README.md ================================================
Foundry banner   [![Github Actions][gha-badge]][gha-url] [![Telegram Chat][tg-badge]][tg-url] [![Telegram Support][tg-support-badge]][tg-support-url] [gha-badge]: https://img.shields.io/github/actions/workflow/status/foundry-rs/foundry/test.yml?branch=master&style=flat-square [gha-url]: https://github.com/foundry-rs/foundry/actions [tg-badge]: https://img.shields.io/endpoint?color=neon&logo=telegram&label=chat&style=flat-square&url=https%3A%2F%2Ftg.sumanjay.workers.dev%2Ffoundry_rs [tg-url]: https://t.me/foundry_rs [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://getfoundry.sh/getting-started/installation)** | [Docs][foundry-docs] | [Benchmarks](https://www.getfoundry.sh/benchmarks) | [Developer Guidelines](./docs/dev/README.md) | [Contributing](./CONTRIBUTING.md) | [Crate Docs](https://foundry-rs.github.io/foundry)
--- Blazing fast, portable and modular toolkit for Ethereum application development, written in Rust. - [**Forge**](https://getfoundry.sh/forge) — Build, test, fuzz, debug and deploy Solidity contracts. - [**Cast**](https://getfoundry.sh/cast) — Swiss Army knife for interacting with EVM smart contracts, sending transactions and getting chain data. - [**Anvil**](https://getfoundry.sh/anvil) — Fast local Ethereum development node. - [**Chisel**](https://getfoundry.sh/chisel) — Fast, utilitarian and verbose Solidity REPL. ![Demo](.github/assets/demo.gif) ## Installation ```sh curl -L https://foundry.paradigm.xyz | bash foundryup ``` See the [installation guide](https://getfoundry.sh/getting-started/installation) for more details. ## Getting Started Initialize a new project, build and test: ```sh forge init counter && cd counter forge build forge test ``` Interact with a live network: ```sh cast block-number --rpc-url https://eth.merkle.io cast balance vitalik.eth --ether --rpc-url https://eth.merkle.io ``` Fork mainnet locally: ```sh anvil --fork-url https://eth.merkle.io ``` Read the [Foundry Docs][foundry-docs] to learn more. ## Contributing Contributions are welcome and highly appreciated. To get started, check out the [contributing guidelines](./CONTRIBUTING.md). Join our [Telegram][tg-url] to chat about the development of Foundry. ## Support Having trouble? Check the [Foundry Docs][foundry-docs], join the [support Telegram][tg-support-url], or [open an issue](https://github.com/foundry-rs/foundry/issues/new). #### License Licensed under either of Apache License, Version 2.0 or MIT license at your option.
Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in these crates by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions. [foundry-docs]: https://getfoundry.sh ================================================ FILE: SECURITY.md ================================================ # Security Policy ## Reporting a Vulnerability Contact [security@ithaca.xyz](mailto:security@ithaca.xyz). ================================================ FILE: benches/Cargo.toml ================================================ [package] name = "foundry-bench" description = "Foundry benchmark runner" version.workspace = true edition.workspace = true rust-version.workspace = true authors.workspace = true license.workspace = true homepage.workspace = true repository.workspace = true [lints] workspace = true [[bin]] name = "foundry-bench" path = "src/main.rs" [dependencies] foundry-test-utils.workspace = true foundry-common.workspace = true foundry-compilers = { workspace = true, features = ["project-util"] } eyre.workspace = true color-eyre.workspace = true serde.workspace = true serde_json.workspace = true chrono = { version = "0.4", features = ["serde"] } rayon.workspace = true clap = { version = "4", features = ["derive"] } once_cell = "1.21" ================================================ FILE: benches/LATEST.md ================================================ # Foundry Benchmark Results **Date**: 2025-10-02 12:14:23 ## Repositories Tested 1. [ithacaxyz/account](https://github.com/ithacaxyz/account) 2. [Vectorized/solady](https://github.com/Vectorized/solady) 3. [Uniswap/v4-core](https://github.com/Uniswap/v4-core) 4. [sparkdotfi/spark-psm](https://github.com/sparkdotfi/spark-psm) ## Foundry Versions - **v1.3.6**: forge Version: 1.3.6-v1.3.6 (d241588 2025-09-16) - **v1.4.0-rc1**: forge Version: 1.4.0-v1.4.0-rc1 (bd0e4a7 2025-10-01) ## Forge Test | Repository | v1.3.6 | v1.4.0-rc1 | | -------------------- | ------- | ---------- | | ithacaxyz-account | 3.17 s | 2.94 s | | solady | 2.28 s | 2.10 s | | Uniswap-v4-core | 7.27 s | 6.13 s | | sparkdotfi-spark-psm | 43.04 s | 44.08 s | ## Forge Fuzz Test | Repository | v1.3.6 | v1.4.0-rc1 | | -------------------- | ------ | ---------- | | ithacaxyz-account | 3.18 s | 3.02 s | | solady | 2.39 s | 2.24 s | | Uniswap-v4-core | 6.84 s | 6.20 s | | sparkdotfi-spark-psm | 3.07 s | 2.72 s | ## Forge Test (Isolated) | Repository | v1.3.6 | v1.4.0-rc1 | | -------------------- | ------- | ---------- | | solady | 2.26 s | 2.41 s | | Uniswap-v4-core | 7.22 s | 7.71 s | | sparkdotfi-spark-psm | 45.53 s | 50.49 s | ## Forge Build (No Cache) | Repository | v1.3.6 | v1.4.0-rc1 | | -------------------- | ------- | ---------- | | ithacaxyz-account | 9.16 s | 9.08 s | | solady | 14.62 s | 14.69 s | | Uniswap-v4-core | 2m 3.8s | 2m 5.3s | | sparkdotfi-spark-psm | 13.17 s | 13.14 s | ## Forge Build (With Cache) | Repository | v1.3.6 | v1.4.0-rc1 | | -------------------- | ------- | ---------- | | ithacaxyz-account | 0.156 s | 0.113 s | | solady | 0.089 s | 0.094 s | | Uniswap-v4-core | 0.133 s | 0.127 s | | sparkdotfi-spark-psm | 0.173 s | 0.131 s | ## Forge Coverage | Repository | v1.3.6 | v1.4.0-rc1 | | -------------------- | -------- | ---------- | | ithacaxyz-account | 14.91 s | 13.34 s | | Uniswap-v4-core | 1m 34.8s | 1m 30.3s | | sparkdotfi-spark-psm | 3m 49.3s | 3m 40.2s | ## System Information - **OS**: macos - **CPU**: 8 - **Rustc**: rustc 1.90.0-nightly (3014e79f9 2025-07-15) ================================================ FILE: benches/README.md ================================================ # Foundry Benchmarks This directory contains performance benchmarks for Foundry commands across multiple repositories and Foundry versions. ## Prerequisites Before running the benchmarks, ensure you have the following installed: 1. **Rust and Cargo** - Required for building the benchmark binary ```bash curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh ``` 2. **Foundryup** - The Foundry toolchain installer ```bash curl -L https://foundry.paradigm.xyz | bash foundryup ``` 3. **Git** - For cloning benchmark repositories 4. [**Hyperfine**](https://github.com/sharkdp/hyperfine/blob/master/README.md) - The benchmarking tool used by foundry-bench 5. **Node.js and npm** - Some repositories require npm dependencies ## Running Benchmarks Build and install the benchmark runner: ```bash cargo build --release --bin foundry-bench ``` To install `foundry-bench` to your PATH: ```bash cd benches && cargo install --path . --bin foundry-bench ``` #### Run with default settings ```bash # Run all benchmarks on default repos with stable and nightly versions foundry-bench --versions stable,nightly ``` #### Run with custom configurations ```bash # Bench specific versions foundry-bench --versions stable,nightly,v1.0.0 # Run on specific repositories. Default rev for the repo is "main" foundry-bench --repos ithacaxyz/account,Vectorized/solady # Test specific repository with custom revision foundry-bench --repos ithacaxyz/account:main,Vectorized/solady:v0.0.123 # Run only specific benchmarks foundry-bench --benchmarks forge_build_with_cache,forge_test # Run only fuzz tests foundry-bench --benchmarks forge_fuzz_test # Run coverage benchmark foundry-bench --benchmarks forge_coverage # Combine options foundry-bench \ --versions stable,nightly \ --repos ithacaxyz/account \ --benchmarks forge_build_with_cache # Force install Foundry versions foundry-bench --force-install # Verbose output to see hyperfine logs foundry-bench --verbose # Output to specific directory foundry-bench --output-dir ./results --output-file LATEST_RESULTS.md ``` #### Command-line Options - `--versions ` - Comma-separated list of Foundry versions (default: stable,nightly) - `--repos ` - Comma-separated list of repos in org/repo[:rev] format (default: ithacaxyz/account:v0.3.2,Vectorized/solady:v0.1.22) - `--benchmarks ` - Comma-separated list of benchmarks to run - `--force-install` - Force installation of Foundry versions - `--verbose` - Show detailed benchmark output - `--output-dir ` - Directory for output files (default: benches) - `--output-file ` - Name of the output file (default: LATEST.md) ## Benchmark Structure - `forge_test` - Benchmarks `forge test` command across repos - `forge_build_no_cache` - Benchmarks `forge build` with clean cache - `forge_build_with_cache` - Benchmarks `forge build` with existing cache - `forge_fuzz_test` - Benchmarks `forge test` with only fuzz tests (tests with parameters) - `forge_coverage` - Benchmarks `forge coverage --ir-minimum` command across repos ## Configuration The benchmark binary uses command-line arguments to configure which repositories and versions to test. The default repositories are: - `ithacaxyz/account:v0.3.2` - `Vectorized/solady:v0.1.22` You can override these using the `--repos` flag with the format `org/repo[:rev]`. ## Results Benchmark results are saved to `benches/LATEST.md` (or custom output file specified with `--output-file`). The report includes: - Summary of versions and repositories tested - Performance comparison tables for each benchmark type showing: - Mean execution time - Min/Max times - Standard deviation - Relative performance comparison between versions - System information (OS, CPU cores) - Detailed hyperfine benchmark results in JSON format ## Troubleshooting 1. **Foundry version not found**: Use `--force-install` flag or manually install with `foundryup --install ` 2. **Repository clone fails**: Check network connectivity and repository access 3. **Build failures**: Some repositories may have specific dependencies - check their README files 4. **Hyperfine not found**: Install hyperfine using the instructions in Prerequisites 5. **npm/Node.js errors**: Ensure Node.js and npm are installed for repositories that require them ================================================ FILE: benches/src/lib.rs ================================================ //! Foundry benchmark runner. use crate::results::{HyperfineOutput, HyperfineResult}; use eyre::{Result, WrapErr}; use foundry_common::{sh_eprintln, sh_println}; use foundry_compilers::project_util::TempProject; use foundry_test_utils::util::clone_remote; use once_cell::sync::Lazy; use std::{ path::{Path, PathBuf}, process::Command, str::FromStr, }; pub mod results; /// Default number of runs for benchmarks pub const RUNS: u32 = 5; /// Configuration for repositories to benchmark #[derive(Debug, Clone)] pub struct RepoConfig { pub name: String, pub org: String, pub repo: String, pub rev: String, } impl FromStr for RepoConfig { type Err = eyre::Error; fn from_str(spec: &str) -> Result { // Split by ':' first to separate repo path from optional rev let parts: Vec<&str> = spec.splitn(2, ':').collect(); let repo_path = parts[0]; let custom_rev = parts.get(1).copied(); // Now split the repo path by '/' let path_parts: Vec<&str> = repo_path.split('/').collect(); if path_parts.len() != 2 { eyre::bail!("Invalid repo format '{}'. Expected 'org/repo' or 'org/repo:rev'", spec); } let org = path_parts[0]; let repo = path_parts[1]; // Try to find this repo in BENCHMARK_REPOS to get the full config let existing_config = BENCHMARK_REPOS.iter().find(|r| r.org == org && r.repo == repo); let config = if let Some(existing) = existing_config { // Use existing config but allow custom rev to override let mut config = existing.clone(); if let Some(rev) = custom_rev { config.rev = rev.to_string(); } config } else { // Create new config with custom rev or default // Name should follow the format: org-repo (with hyphen) Self { name: format!("{org}-{repo}"), org: org.to_string(), repo: repo.to_string(), rev: custom_rev.unwrap_or("main").to_string(), } }; let _ = sh_println!("Parsed repo spec '{spec}' -> {config:?}"); Ok(config) } } /// Available repositories for benchmarking pub fn default_benchmark_repos() -> Vec { vec![ RepoConfig { name: "ithacaxyz-account".to_string(), org: "ithacaxyz".to_string(), repo: "account".to_string(), rev: "main".to_string(), }, RepoConfig { name: "solady".to_string(), org: "Vectorized".to_string(), repo: "solady".to_string(), rev: "main".to_string(), }, ] } // Keep a lazy static for compatibility pub static BENCHMARK_REPOS: Lazy> = Lazy::new(default_benchmark_repos); /// Foundry versions to benchmark /// /// To add more versions for comparison, install them first: /// ```bash /// foundryup --install stable /// foundryup --install nightly /// foundryup --install v0.2.0 # Example specific version /// ``` /// /// Then add the version strings to this array. Supported formats: /// - "stable" - Latest stable release /// - "nightly" - Latest nightly build /// - "v0.2.0" - Specific version tag /// - "commit-hash" - Specific commit hash /// - "nightly-rev" - Nightly build with specific revision pub static FOUNDRY_VERSIONS: &[&str] = &["stable", "nightly"]; /// A benchmark project that represents a cloned repository ready for testing pub struct BenchmarkProject { pub name: String, pub temp_project: TempProject, pub root_path: PathBuf, } impl BenchmarkProject { /// Set up a benchmark project by cloning the repository #[allow(unused_must_use)] pub fn setup(config: &RepoConfig) -> Result { let temp_project = TempProject::dapptools().wrap_err("Failed to create temporary project")?; // Get root path before clearing let root_path = temp_project.root().to_path_buf(); let root = root_path.to_str().unwrap(); // Remove all files in the directory for entry in std::fs::read_dir(&root_path)? { let entry = entry?; let path = entry.path(); if path.is_dir() { std::fs::remove_dir_all(&path).ok(); } else { std::fs::remove_file(&path).ok(); } } // Clone the repository let repo_url = format!("https://github.com/{}/{}.git", config.org, config.repo); clone_remote(&repo_url, root, true); // Checkout specific revision if provided if !config.rev.is_empty() && config.rev != "main" && config.rev != "master" { let status = Command::new("git") .current_dir(root) .args(["checkout", &config.rev]) .status() .wrap_err("Failed to checkout revision")?; if !status.success() { eyre::bail!("Git checkout failed for {}", config.name); } } // Git submodules are already cloned via --recursive flag // But npm dependencies still need to be installed Self::install_npm_dependencies(&root_path)?; sh_println!(" ✅ Project {} setup complete at {}", config.name, root); Ok(Self { name: config.name.to_string(), root_path, temp_project }) } /// Install npm dependencies if package.json exists #[allow(unused_must_use)] fn install_npm_dependencies(root: &Path) -> Result<()> { if root.join("package.json").exists() { sh_println!(" 📦 Running npm install..."); let status = Command::new("npm") .current_dir(root) .args(["install"]) .stdout(std::process::Stdio::inherit()) .stderr(std::process::Stdio::inherit()) .status() .wrap_err("Failed to run npm install")?; if !status.success() { sh_println!( " ⚠️ Warning: npm install failed with exit code: {:?}", status.code() ); } else { sh_println!(" ✅ npm install completed successfully"); } } Ok(()) } /// Run a command with hyperfine and return the results /// /// # Arguments /// * `benchmark_name` - Name of the benchmark for organizing output /// * `version` - Foundry version being benchmarked /// * `command` - The command to benchmark /// * `runs` - Number of runs to perform /// * `setup` - Optional setup command to run before the benchmark series (e.g., "forge build") /// * `prepare` - Optional prepare command to run before each timing run (e.g., "forge clean") /// * `conclude` - Optional conclude command to run after each timing run (e.g., cleanup) /// * `verbose` - Whether to show command output /// /// # Hyperfine flags used: /// * `--runs` - Number of timing runs /// * `--setup` - Execute before the benchmark series (not before each run) /// * `--prepare` - Execute before each timing run /// * `--conclude` - Execute after each timing run /// * `--export-json` - Export results to JSON for parsing /// * `--shell=bash` - Use bash for shell command execution /// * `--show-output` - Show command output (when verbose) #[allow(clippy::too_many_arguments)] fn hyperfine( &self, benchmark_name: &str, version: &str, command: &str, runs: u32, setup: Option<&str>, prepare: Option<&str>, conclude: Option<&str>, verbose: bool, ) -> Result { // Create structured temp directory for JSON output // Format: ////.json let temp_dir = std::env::temp_dir(); let json_dir = temp_dir.join("foundry-bench").join(benchmark_name).join(version).join(&self.name); std::fs::create_dir_all(&json_dir)?; let json_path = json_dir.join(format!("{benchmark_name}.json")); // Build hyperfine command let mut hyperfine_cmd = Command::new("hyperfine"); hyperfine_cmd .current_dir(&self.root_path) .arg("--runs") .arg(runs.to_string()) .arg("--export-json") .arg(&json_path) .arg("--shell=bash"); // Add optional setup command if let Some(setup_cmd) = setup { hyperfine_cmd.arg("--setup").arg(setup_cmd); } // Add optional prepare command if let Some(prepare_cmd) = prepare { hyperfine_cmd.arg("--prepare").arg(prepare_cmd); } // Add optional conclude command if let Some(conclude_cmd) = conclude { hyperfine_cmd.arg("--conclude").arg(conclude_cmd); } if verbose { hyperfine_cmd.arg("--show-output"); hyperfine_cmd.stderr(std::process::Stdio::inherit()); hyperfine_cmd.stdout(std::process::Stdio::inherit()); } // Add the benchmark command last hyperfine_cmd.arg(command); let status = hyperfine_cmd.status().wrap_err("Failed to run hyperfine")?; if !status.success() { eyre::bail!("Hyperfine failed for command: {}", command); } // Read and parse the JSON output let json_content = std::fs::read_to_string(json_path)?; let output: HyperfineOutput = serde_json::from_str(&json_content)?; // Extract the first result (we only run one command at a time) output.results.into_iter().next().ok_or_else(|| eyre::eyre!("No results from hyperfine")) } /// Benchmark forge test pub fn bench_forge_test( &self, version: &str, runs: u32, verbose: bool, ) -> Result { // Build before running tests self.hyperfine( "forge_test", version, "forge test", runs, Some("forge build"), None, None, verbose, ) } /// Benchmark forge build with cache pub fn bench_forge_build_with_cache( &self, version: &str, runs: u32, verbose: bool, ) -> Result { self.hyperfine( "forge_build_with_cache", version, "FOUNDRY_LINT_LINT_ON_BUILD=false forge build", runs, None, Some("forge build"), None, verbose, ) } /// Benchmark forge build without cache pub fn bench_forge_build_no_cache( &self, version: &str, runs: u32, verbose: bool, ) -> Result { // Clean before each timing run self.hyperfine( "forge_build_no_cache", version, "FOUNDRY_LINT_LINT_ON_BUILD=false forge build", runs, Some("forge clean"), None, Some("forge clean"), verbose, ) } /// Benchmark forge fuzz tests pub fn bench_forge_fuzz_test( &self, version: &str, runs: u32, verbose: bool, ) -> Result { // Build before running fuzz tests self.hyperfine( "forge_fuzz_test", version, r#"forge test --match-test "test[^(]*\([^)]+\)""#, runs, Some("forge build"), None, None, verbose, ) } /// Benchmark forge coverage pub fn bench_forge_coverage( &self, version: &str, runs: u32, verbose: bool, ) -> Result { // No setup needed, forge coverage builds internally // Use --ir-minimum to avoid "Stack too deep" errors self.hyperfine( "forge_coverage", version, "forge coverage --ir-minimum", runs, None, None, None, verbose, ) } /// Benchmark forge test with --isolate flag pub fn bench_forge_isolate_test( &self, version: &str, runs: u32, verbose: bool, ) -> Result { // Build before running tests self.hyperfine( "forge_isolate_test", version, "forge test --isolate", runs, Some("forge build"), None, None, verbose, ) } /// Get the root path of the project pub fn root(&self) -> &Path { &self.root_path } /// Run a specific benchmark by name pub fn run( &self, benchmark: &str, version: &str, runs: u32, verbose: bool, ) -> Result { match benchmark { "forge_test" => self.bench_forge_test(version, runs, verbose), "forge_build_no_cache" => self.bench_forge_build_no_cache(version, runs, verbose), "forge_build_with_cache" => self.bench_forge_build_with_cache(version, runs, verbose), "forge_fuzz_test" => self.bench_forge_fuzz_test(version, runs, verbose), "forge_coverage" => self.bench_forge_coverage(version, runs, verbose), "forge_isolate_test" => self.bench_forge_isolate_test(version, runs, verbose), _ => eyre::bail!("Unknown benchmark: {}", benchmark), } } } /// Switch to a specific foundry version #[allow(unused_must_use)] pub fn switch_foundry_version(version: &str) -> Result<()> { let output = Command::new("foundryup") .args(["--use", version]) .output() .wrap_err("Failed to run foundryup")?; // Check if the error is about forge --version failing let stderr = String::from_utf8_lossy(&output.stderr); if stderr.contains("command failed") && stderr.contains("forge --version") { eyre::bail!( "Foundry binaries maybe corrupted. Please reinstall by running `foundryup --install `" ); } if !output.status.success() { sh_eprintln!("foundryup stderr: {stderr}"); eyre::bail!("Failed to switch to foundry version: {}", version); } sh_println!(" Successfully switched to version: {version}"); Ok(()) } /// Get the current forge version pub fn get_forge_version() -> Result { let output = Command::new("forge") .args(["--version"]) .output() .wrap_err("Failed to get forge version")?; if !output.status.success() { eyre::bail!("forge --version failed"); } let version = String::from_utf8(output.stdout).wrap_err("Invalid UTF-8 in forge version output")?; Ok(version.lines().next().unwrap_or("unknown").to_string()) } /// Get the full forge version details including commit hash and date pub fn get_forge_version_details() -> Result { let output = Command::new("forge") .args(["--version"]) .output() .wrap_err("Failed to get forge version")?; if !output.status.success() { eyre::bail!("forge --version failed"); } let full_output = String::from_utf8(output.stdout).wrap_err("Invalid UTF-8 in forge version output")?; // Extract relevant lines and format them let lines: Vec<&str> = full_output.lines().collect(); if lines.len() >= 3 { // Extract version, commit, and timestamp let version = lines[0].trim(); let commit = lines[1].trim().replace("Commit SHA: ", ""); let timestamp = lines[2].trim().replace("Build Timestamp: ", ""); // Format as: "forge 1.2.3-nightly (51650ea 2025-06-27)" let short_commit = &commit[..7]; // First 7 chars of commit hash let date = timestamp.split('T').next().unwrap_or(×tamp); Ok(format!("{version} ({short_commit} {date})")) } else { // Fallback to just the first line if format is unexpected Ok(lines.first().unwrap_or(&"unknown").to_string()) } } ================================================ FILE: benches/src/main.rs ================================================ use clap::Parser; use eyre::{Result, WrapErr}; use foundry_bench::{ BENCHMARK_REPOS, BenchmarkProject, FOUNDRY_VERSIONS, RUNS, RepoConfig, get_forge_version, get_forge_version_details, results::{BenchmarkResults, HyperfineResult}, switch_foundry_version, }; use foundry_common::sh_println; use rayon::prelude::*; use std::{fs, path::PathBuf, process::Command, sync::Mutex}; const ALL_BENCHMARKS: [&str; 6] = [ "forge_test", "forge_build_no_cache", "forge_build_with_cache", "forge_fuzz_test", "forge_coverage", "forge_isolate_test", ]; /// Foundry Benchmark Runner #[derive(Parser, Debug)] #[clap(name = "foundry-bench", about = "Run Foundry benchmarks across multiple versions")] struct Cli { /// Comma-separated list of Foundry versions to test (e.g., stable,nightly,v1.2.0) #[clap(long, value_delimiter = ',')] versions: Option>, /// Force install Foundry versions #[clap(long)] force_install: bool, /// Show verbose output #[clap(long)] verbose: bool, /// Directory where the aggregated benchmark results will be written. #[clap(long, default_value = ".")] output_dir: PathBuf, /// Name of the output file (default: LATEST.md) #[clap(long, default_value = "LATEST.md")] output_file: String, /// Run only specific benchmarks (comma-separated: /// forge_test,forge_build_no_cache,forge_build_with_cache,forge_fuzz_test,forge_coverage) #[clap(long, value_delimiter = ',')] benchmarks: Option>, /// Run only on specific repositories (comma-separated in org/repo[:rev] format: /// ithacaxyz/account,Vectorized/solady:main,foundry-rs/foundry:v1.0.0) #[clap(long, value_delimiter = ',')] repos: Option>, } /// Mutex to prevent concurrent foundryup calls static FOUNDRY_LOCK: Mutex<()> = Mutex::new(()); fn switch_version_safe(version: &str) -> Result<()> { let _lock = FOUNDRY_LOCK.lock().unwrap(); switch_foundry_version(version) } #[allow(unused_must_use)] fn main() -> Result<()> { color_eyre::install()?; let cli = Cli::parse(); // Check if hyperfine is installed let hyperfine_check = Command::new("hyperfine").arg("--version").output(); if hyperfine_check.is_err() || !hyperfine_check.unwrap().status.success() { eyre::bail!( "hyperfine is not installed. Please install it first: https://github.com/sharkdp/hyperfine" ); } // Determine versions to test let versions = if let Some(v) = cli.versions { v } else { FOUNDRY_VERSIONS.iter().map(|&s| s.to_string()).collect() }; // Get repo configurations let repos = if let Some(repo_specs) = cli.repos.clone() { repo_specs.iter().map(|spec| spec.parse::()).collect::>>()? } else { BENCHMARK_REPOS.clone() }; sh_println!("🚀 Foundry Benchmark Runner"); sh_println!("Running with versions: {}", versions.join(", ")); sh_println!( "Running on repos: {}", repos.iter().map(|r| format!("{}/{}", r.org, r.repo)).collect::>().join(", ") ); // Install versions if requested if cli.force_install { install_foundry_versions(&versions)?; } // Determine benchmarks to run let benchmarks = if let Some(b) = cli.benchmarks { b.into_iter().filter(|b| ALL_BENCHMARKS.contains(&b.as_str())).collect() } else { // Default: run all benchmarks except fuzz tests and coverage (which can be slow) vec!["forge_test", "forge_build_no_cache", "forge_build_with_cache"] .into_iter() .map(String::from) .collect::>() }; sh_println!("Running benchmarks: {}", benchmarks.join(", ")); let mut results = BenchmarkResults::new(); // Set the first version as baseline if let Some(first_version) = versions.first() { results.set_baseline_version(first_version.clone()); } // Setup all projects upfront before version loop sh_println!("📦 Setting up projects to benchmark"); let projects: Vec<(RepoConfig, BenchmarkProject)> = repos .par_iter() .map(|repo_config| -> Result<(RepoConfig, BenchmarkProject)> { sh_println!("Setting up {}/{}", repo_config.org, repo_config.repo); let project = BenchmarkProject::setup(repo_config).wrap_err(format!( "Failed to setup project for {}/{}", repo_config.org, repo_config.repo ))?; Ok((repo_config.clone(), project)) }) .collect::>>()?; sh_println!("✅ All projects setup complete"); // Create a list of all benchmark tasks (same for all versions) let benchmark_tasks: Vec<_> = projects .iter() .flat_map(|(repo_config, project)| { benchmarks .iter() .map(move |benchmark| (repo_config.clone(), project, benchmark.clone())) }) .collect(); sh_println!("Will run {} benchmark tasks per version", benchmark_tasks.len()); // Run benchmarks for each version for version in &versions { sh_println!("🔧 Switching to Foundry version: {version}"); switch_version_safe(version)?; // Verify the switch and capture full version details let current = get_forge_version()?; sh_println!("Current version: {}", current.trim()); // Get and store the full version details with commit hash and date let version_details = get_forge_version_details()?; results.add_version_details(version, version_details); sh_println!("Running benchmark tasks for version {version}..."); // Run all benchmarks sequentially let version_results = benchmark_tasks .iter() .map(|(repo_config, project, benchmark)| -> Result<(String, String, HyperfineResult)> { sh_println!("Running {} on {}/{}", benchmark, repo_config.org, repo_config.repo); // Determine runs based on benchmark type let runs = match benchmark.as_str() { "forge_coverage" => 1, // Coverage runs only once as an exception _ => RUNS, // Use default RUNS constant for all other benchmarks }; // Run the appropriate benchmark let result = project.run(benchmark, version, runs, cli.verbose); match result { Ok(hyperfine_result) => { sh_println!( " {} on {}/{}: {:.3}s ± {:.3}s", benchmark, repo_config.org, repo_config.repo, hyperfine_result.mean, hyperfine_result.stddev.unwrap_or(0.0) ); Ok((repo_config.name.clone(), benchmark.clone(), hyperfine_result)) } Err(e) => { eyre::bail!( "Benchmark {} failed for {}/{}: {}", benchmark, repo_config.org, repo_config.repo, e ); } } }) .collect::>>()?; // Add all collected results to the main results structure for (repo_name, benchmark, hyperfine_result) in version_results { results.add_result(&benchmark, version, &repo_name, hyperfine_result); } } // Generate markdown report sh_println!("📝 Generating report..."); let markdown = results.generate_markdown(&versions, &repos); let output_path = cli.output_dir.join(cli.output_file); fs::write(&output_path, markdown).wrap_err("Failed to write output file")?; sh_println!("✅ Report written to: {}", output_path.display()); Ok(()) } #[allow(unused_must_use)] fn install_foundry_versions(versions: &[String]) -> Result<()> { sh_println!("Installing Foundry versions..."); for version in versions { sh_println!("Installing {version}..."); let status = Command::new("foundryup") .args(["--install", version, "--force"]) .status() .wrap_err("Failed to run foundryup")?; if !status.success() { eyre::bail!("Failed to install Foundry version: {}", version); } } sh_println!("✅ All versions installed successfully"); Ok(()) } ================================================ FILE: benches/src/results.rs ================================================ use crate::RepoConfig; use eyre::Result; use serde::{Deserialize, Serialize}; use std::{collections::HashMap, process::Command, thread}; /// Hyperfine benchmark result #[derive(Debug, Deserialize, Serialize)] pub struct HyperfineResult { pub command: String, pub mean: f64, pub stddev: Option, pub median: f64, pub user: f64, pub system: f64, pub min: f64, pub max: f64, pub times: Vec, #[serde(skip_serializing_if = "Option::is_none")] pub exit_codes: Option>, #[serde(skip_serializing_if = "Option::is_none")] pub parameters: Option>, } /// Hyperfine JSON output format #[derive(Debug, Deserialize, Serialize)] pub struct HyperfineOutput { pub results: Vec, } /// Aggregated benchmark results #[derive(Debug, Default)] pub struct BenchmarkResults { /// Map of benchmark_name -> version -> repo -> result pub data: HashMap>>, /// Track the baseline version for comparison pub baseline_version: Option, /// Map of version name -> full version details pub version_details: HashMap, } impl BenchmarkResults { pub fn new() -> Self { Self::default() } pub fn set_baseline_version(&mut self, version: String) { self.baseline_version = Some(version); } pub fn add_result( &mut self, benchmark: &str, version: &str, repo: &str, result: HyperfineResult, ) { self.data .entry(benchmark.to_string()) .or_default() .entry(version.to_string()) .or_default() .insert(repo.to_string(), result); } pub fn add_version_details(&mut self, version: &str, details: String) { self.version_details.insert(version.to_string(), details); } pub fn generate_markdown(&self, versions: &[String], repos: &[RepoConfig]) -> String { let mut output = String::new(); // Header output.push_str("# Foundry Benchmark Results\n\n"); output.push_str(&format!( "**Date**: {}\n\n", chrono::Local::now().format("%Y-%m-%d %H:%M:%S") )); // Summary output.push_str("## Summary\n\n"); // Count actual repos that have results let mut repos_with_results = std::collections::HashSet::new(); for version_data in self.data.values() { for repo_data in version_data.values() { for repo_name in repo_data.keys() { repos_with_results.insert(repo_name.clone()); } } } output.push_str(&format!( "Benchmarked {} Foundry versions across {} repositories.\n\n", versions.len(), repos_with_results.len() )); // Repositories tested output.push_str("### Repositories Tested\n\n"); for (i, repo) in repos.iter().enumerate() { output.push_str(&format!( "{}. [{}/{}](https://github.com/{}/{})\n", i + 1, repo.org, repo.repo, repo.org, repo.repo )); } output.push('\n'); // Versions tested output.push_str("### Foundry Versions\n\n"); for version in versions { if let Some(details) = self.version_details.get(version) { output.push_str(&format!("- **{version}**: {}\n", details.trim())); } else { output.push_str(&format!("- {version}\n")); } } output.push('\n'); // Results for each benchmark type for (benchmark_name, version_data) in &self.data { output.push_str(&self.generate_benchmark_table( benchmark_name, version_data, versions, repos, )); } // System info output.push_str("## System Information\n\n"); output.push_str(&format!("- **OS**: {}\n", std::env::consts::OS)); output.push_str(&format!( "- **CPU**: {}\n", thread::available_parallelism().map_or(1, |n| n.get()) )); output.push_str(&format!( "- **Rustc**: {}\n", get_rustc_version().unwrap_or_else(|_| "unknown".to_string()) )); output } /// Generate a complete markdown table for a single benchmark type /// /// This includes the section header, table header, separator, and all rows fn generate_benchmark_table( &self, benchmark_name: &str, version_data: &HashMap>, versions: &[String], repos: &[RepoConfig], ) -> String { let mut output = String::new(); // Section header output.push_str(&format!("## {}\n\n", format_benchmark_name(benchmark_name))); // Create table header output.push_str("| Repository |"); for version in versions { output.push_str(&format!(" {version} |")); } output.push('\n'); // Table separator output.push_str("|------------|"); for _ in versions { output.push_str("----------|"); } output.push('\n'); // Table rows output.push_str(&generate_table_rows(version_data, versions, repos)); output.push('\n'); output } } /// Generate table rows for benchmark results /// /// This function creates the markdown table rows for each repository, /// showing the benchmark results for each version. fn generate_table_rows( version_data: &HashMap>, versions: &[String], repos: &[RepoConfig], ) -> String { let mut output = String::new(); for repo in repos { output.push_str(&format!("| {} |", repo.name)); for version in versions { let cell_content = get_benchmark_cell_content(version_data, version, &repo.name); output.push_str(&format!(" {cell_content} |")); } output.push('\n'); } output } /// Get the content for a single benchmark table cell /// /// Returns the formatted duration or "N/A" if no data is available. /// The nested if-let statements handle the following cases: /// 1. Check if version data exists /// 2. Check if repository data exists for this version fn get_benchmark_cell_content( version_data: &HashMap>, version: &str, repo_name: &str, ) -> String { // Check if we have data for this version if let Some(repo_data) = version_data.get(version) && // Check if we have data for this repository let Some(result) = repo_data.get(repo_name) { return format_duration_seconds(result.mean); } "N/A".to_string() } pub fn format_benchmark_name(name: &str) -> String { match name { "forge_test" => "Forge Test", "forge_build_no_cache" => "Forge Build (No Cache)", "forge_build_with_cache" => "Forge Build (With Cache)", "forge_fuzz_test" => "Forge Fuzz Test", "forge_coverage" => "Forge Coverage", "forge_isolate_test" => "Forge Test (Isolated)", _ => name, } .to_string() } pub fn format_duration_seconds(seconds: f64) -> String { if seconds < 0.001 { format!("{:.2} ms", seconds * 1000.0) } else if seconds < 1.0 { format!("{seconds:.3} s") } else if seconds < 60.0 { format!("{seconds:.2} s") } else { let minutes = (seconds / 60.0).floor(); let remaining_seconds = seconds % 60.0; format!("{minutes:.0}m {remaining_seconds:.1}s") } } pub fn get_rustc_version() -> Result { let output = Command::new("rustc").arg("--version").output()?; Ok(String::from_utf8_lossy(&output.stdout).trim().to_string()) } ================================================ FILE: benchmark.sh ================================================ #!/bin/bash versions="v1.3.6,v1.4.0-rc1" # Repositories export ITHACA_ACCOUNT="ithacaxyz/account:v0.3.2" export SOLADY_REPO="Vectorized/solady:v0.1.22" export UNISWAP_V4_CORE="Uniswap/v4-core:59d3ecf" export SPARK_PSM="sparkdotfi/spark-psm:v1.0.0" # Benches echo "===========FORGE TEST AND BUILD BENCHMARKS===========" foundry-bench --versions $versions \ --repos $ITHACA_ACCOUNT,$SOLADY_REPO,$UNISWAP_V4_CORE,$SPARK_PSM \ --benchmarks forge_test,forge_fuzz_test,forge_build_no_cache,forge_build_with_cache \ --output-dir ./benches/results \ --output-file TEST_BUILD.md echo "===========FORGE COVERAGE BENCHMARKS===========" foundry-bench --versions $versions \ --repos $ITHACA_ACCOUNT,$UNISWAP_V4_CORE,$SPARK_PSM \ --benchmarks forge_coverage \ --output-dir ./benches/results \ --output-file COVERAGE.md echo "===========FORGE ISOLATE TEST BENCHMARKS===========" foundry-bench --versions $versions \ --repos $SOLADY_REPO,$UNISWAP_V4_CORE,$SPARK_PSM \ --benchmarks forge_isolate_test \ --output-dir ./benches/results \ --output-file ISOLATE_TEST.md echo "===========BENCHMARKS COMPLETED===========" ================================================ FILE: clippy.toml ================================================ # `bytes::Bytes` is included by default and `alloy_primitives::Bytes` is a wrapper around it, # so it is safe to ignore it as well. ignore-interior-mutability = ["bytes::Bytes", "alloy_primitives::Bytes"] disallowed-macros = [ # See `foundry_common::shell`. { path = "std::print", reason = "use `sh_print` or similar macros instead" }, { path = "std::eprint", reason = "use `sh_eprint` or similar macros instead" }, { path = "std::println", reason = "use `sh_println` or similar macros instead" }, { path = "std::eprintln", reason = "use `sh_eprintln` or similar macros instead" }, ] ================================================ FILE: crates/anvil/Cargo.toml ================================================ [package] name = "anvil" description = "Local ethereum node" version.workspace = true edition.workspace = true rust-version.workspace = true authors.workspace = true license.workspace = true homepage.workspace = true repository.workspace = true [lints] workspace = true [[bin]] name = "anvil" path = "bin/main.rs" required-features = ["cli"] [dependencies] # foundry internal anvil-core = { path = "core" } anvil-rpc = { path = "rpc" } anvil-server = { path = "server" } foundry-cli.workspace = true foundry-common.workspace = true foundry-config.workspace = true foundry-evm.workspace = true foundry-evm-networks.workspace = true foundry-primitives.workspace = true tempo-primitives.workspace = true # alloy alloy-evm = { workspace = true, features = ["call-util"] } alloy-op-evm.workspace = true alloy-primitives = { workspace = true, features = ["serde"] } alloy-consensus = { workspace = true, features = ["k256", "kzg"] } alloy-contract = { workspace = true, features = ["pubsub"] } alloy-network.workspace = true alloy-eips.workspace = true alloy-rlp.workspace = true alloy-signer = { workspace = true, features = ["eip712"] } alloy-signer-local = { workspace = true, features = ["mnemonic"] } alloy-sol-types = { workspace = true, features = ["std"] } alloy-dyn-abi = { workspace = true, features = ["std", "eip712"] } alloy-rpc-types = { workspace = true, features = ["anvil", "trace", "txpool"] } alloy-rpc-types-beacon.workspace = true alloy-rpc-types-eth.workspace = true alloy-serde.workspace = true alloy-provider = { workspace = true, features = [ "reqwest", "ws", "ipc", "debug-api", "trace-api", ] } alloy-transport.workspace = true alloy-chains.workspace = true alloy-genesis.workspace = true alloy-trie.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 # tracing tracing.workspace = true tracing-subscriber = { workspace = true, features = ["env-filter"] } # async tokio = { workspace = true, features = ["time"] } parking_lot.workspace = true futures.workspace = true async-trait.workspace = true # misc flate2.workspace = true serde_json.workspace = true serde.workspace = true thiserror.workspace = true yansi.workspace = true tempfile.workspace = true itertools.workspace = true rand_08.workspace = true eyre.workspace = true ethereum_ssz.workspace = true # cli clap = { version = "4", features = [ "derive", "env", "wrap_help", ], optional = true } clap_complete = { workspace = true, optional = true } chrono.workspace = true ctrlc = { version = "3", optional = true } fdlimit = { version = "0.3", optional = true } [dev-dependencies] alloy-provider = { workspace = true, features = ["txpool-api"] } alloy-pubsub.workspace = true rand.workspace = true reqwest.workspace = true foundry-test-utils.workspace = true tokio = { workspace = true, features = ["full"] } op-alloy-rpc-types.workspace = true [features] default = ["cli", "jemalloc"] asm-keccak = ["alloy-primitives/asm-keccak"] jemalloc = ["foundry-cli/jemalloc"] mimalloc = ["foundry-cli/mimalloc"] tracy-allocator = ["foundry-cli/tracy-allocator"] cli = ["tokio/full", "cmd"] cmd = [ "clap", "clap_complete", "dep:fdlimit", "dep:ctrlc", "anvil-server/clap", "tokio/signal", ] js-tracer = ["revm-inspectors/js-tracer"] ================================================ FILE: crates/anvil/bin/main.rs ================================================ //! The `anvil` CLI: a fast local Ethereum development node, akin to Hardhat Network, Tenderly. use anvil::args::run; #[global_allocator] static ALLOC: foundry_cli::utils::Allocator = foundry_cli::utils::new_allocator(); fn main() { if let Err(err) = run() { let _ = foundry_common::sh_err!("{err:?}"); std::process::exit(1); } } ================================================ FILE: crates/anvil/core/Cargo.toml ================================================ [package] name = "anvil-core" version.workspace = true edition.workspace = true rust-version.workspace = true authors.workspace = true license.workspace = true homepage.workspace = true repository.workspace = true [lints] workspace = true [dependencies] foundry-common.workspace = true foundry-evm.workspace = true foundry-primitives.workspace = true revm = { workspace = true, default-features = false, features = [ "std", "serde", "memory_limit", "c-kzg", ] } alloy-primitives = { workspace = true, features = ["serde", "rlp"] } alloy-rpc-types = { workspace = true, features = ["anvil", "trace"] } alloy-serde.workspace = true alloy-rlp.workspace = true alloy-eips.workspace = true alloy-eip5792.workspace = true alloy-consensus = { workspace = true, features = ["k256", "kzg"] } alloy-network.workspace = true alloy-dyn-abi = { workspace = true, features = ["std", "eip712"] } serde.workspace = true serde_json.workspace = true bytes.workspace = true # misc rand.workspace = true thiserror.workspace = true ================================================ FILE: crates/anvil/core/src/eth/block.rs ================================================ use super::transaction::TransactionInfo; use alloy_consensus::{ BlockBody, EMPTY_OMMER_ROOT_HASH, Header, proofs::calculate_transaction_root, }; use alloy_eips::eip2718::Encodable2718; use alloy_network::Network; use foundry_primitives::{FoundryNetwork, FoundryTxEnvelope}; use std::fmt::Debug; use crate::eth::transaction::MaybeImpersonatedTransaction; /// Type alias for Ethereum Block with Anvil's transaction type, generic over the transaction /// envelope with a default of [`FoundryTxEnvelope`]. pub type Block = alloy_consensus::Block>; /// Anvil's concrete block info type. pub type BlockInfo = TypedBlockInfo; /// Container type that gathers all block data, generic over a [`Network`]. #[derive(Clone, Debug)] pub struct TypedBlockInfo { pub block: alloy_consensus::Block>, pub transactions: Vec, pub receipts: Vec, } /// Helper function to create a new block with Header and Anvil transactions, generic over the /// transaction envelope with a default of [`FoundryTxEnvelope`]. /// /// Note: if the `impersonate-tx` feature is enabled this will also accept /// `MaybeImpersonatedTransaction`. pub fn create_block( mut header: Header, transactions: impl IntoIterator, ) -> Block where Tx: Encodable2718, T: Into>, { let transactions: Vec<_> = transactions.into_iter().map(Into::into).collect(); let transactions_root = calculate_transaction_root(&transactions); header.transactions_root = transactions_root; header.ommers_hash = EMPTY_OMMER_ROOT_HASH; let body = BlockBody { transactions, ommers: Vec::new(), withdrawals: None }; Block::new(header, body) } /// Generic helper function to create a block with any transaction type that supports encoding. pub fn create_typed_block( mut header: Header, transactions: impl IntoIterator, ) -> alloy_consensus::Block where T: Encodable2718, { let transactions: Vec<_> = transactions.into_iter().collect(); let transactions_root = calculate_transaction_root(&transactions); header.transactions_root = transactions_root; header.ommers_hash = EMPTY_OMMER_ROOT_HASH; let body = BlockBody { transactions, ommers: Vec::new(), withdrawals: None }; alloy_consensus::Block::new(header, body) } #[cfg(test)] mod tests { use alloy_primitives::{ Address, B64, B256, Bloom, U256, b256, hex::{self, FromHex}, }; use alloy_rlp::Decodable; use super::*; use std::str::FromStr; #[test] fn header_rlp_roundtrip() { let mut header = Header { parent_hash: Default::default(), ommers_hash: Default::default(), beneficiary: Default::default(), state_root: Default::default(), transactions_root: Default::default(), receipts_root: Default::default(), logs_bloom: Default::default(), difficulty: Default::default(), number: 124u64, gas_limit: Default::default(), gas_used: 1337u64, timestamp: 0, extra_data: Default::default(), mix_hash: Default::default(), nonce: B64::with_last_byte(99), withdrawals_root: Default::default(), blob_gas_used: Default::default(), excess_blob_gas: Default::default(), parent_beacon_block_root: Default::default(), base_fee_per_gas: None, requests_hash: None, }; let encoded = alloy_rlp::encode(&header); let decoded: Header = Header::decode(&mut encoded.as_ref()).unwrap(); assert_eq!(header, decoded); header.base_fee_per_gas = Some(12345u64); let encoded = alloy_rlp::encode(&header); let decoded: Header = Header::decode(&mut encoded.as_ref()).unwrap(); assert_eq!(header, decoded); } #[test] fn test_encode_block_header() { use alloy_rlp::Encodable; let expected = hex::decode("f901f9a00000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000940000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000b90100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008208ae820d0582115c8215b3821a0a827788a00000000000000000000000000000000000000000000000000000000000000000880000000000000000").unwrap(); let mut data = vec![]; let header = Header { parent_hash: B256::from_str("0000000000000000000000000000000000000000000000000000000000000000").unwrap(), ommers_hash: B256::from_str("0000000000000000000000000000000000000000000000000000000000000000").unwrap(), beneficiary: Address::from_str("0000000000000000000000000000000000000000").unwrap(), state_root: B256::from_str("0000000000000000000000000000000000000000000000000000000000000000").unwrap(), transactions_root: B256::from_str("0000000000000000000000000000000000000000000000000000000000000000").unwrap(), receipts_root: B256::from_str("0000000000000000000000000000000000000000000000000000000000000000").unwrap(), logs_bloom: Bloom::from_hex("00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000").unwrap(), difficulty: U256::from(2222), number: 0xd05u64, gas_limit: 0x115cu64, gas_used: 0x15b3u64, timestamp: 0x1a0au64, extra_data: hex::decode("7788").unwrap().into(), mix_hash: B256::from_str("0000000000000000000000000000000000000000000000000000000000000000").unwrap(), withdrawals_root: None, blob_gas_used: None, excess_blob_gas: None, parent_beacon_block_root: None, nonce: B64::ZERO, base_fee_per_gas: None, requests_hash: None, }; header.encode(&mut data); assert_eq!(hex::encode(&data), hex::encode(expected)); assert_eq!(header.length(), data.len()); } #[test] // Test vector from: https://eips.ethereum.org/EIPS/eip-2481 fn test_decode_block_header() { let data = hex::decode("f901f9a00000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000940000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000b90100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008208ae820d0582115c8215b3821a0a827788a00000000000000000000000000000000000000000000000000000000000000000880000000000000000").unwrap(); let expected = Header { parent_hash: B256::from_str("0000000000000000000000000000000000000000000000000000000000000000").unwrap(), ommers_hash: B256::from_str("0000000000000000000000000000000000000000000000000000000000000000").unwrap(), beneficiary: Address::from_str("0000000000000000000000000000000000000000").unwrap(), state_root: B256::from_str("0000000000000000000000000000000000000000000000000000000000000000").unwrap(), transactions_root: B256::from_str("0000000000000000000000000000000000000000000000000000000000000000").unwrap(), receipts_root: B256::from_str("0000000000000000000000000000000000000000000000000000000000000000").unwrap(), logs_bloom: <[u8; 256]>::from_hex("00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000").unwrap().into(), difficulty: U256::from(2222), number: 0xd05u64, gas_limit: 0x115cu64, gas_used: 0x15b3u64, timestamp: 0x1a0au64, extra_data: hex::decode("7788").unwrap().into(), mix_hash: B256::from_str("0000000000000000000000000000000000000000000000000000000000000000").unwrap(), nonce: B64::ZERO, withdrawals_root: None, blob_gas_used: None, excess_blob_gas: None, parent_beacon_block_root: None, base_fee_per_gas: None, requests_hash: None, }; let header = Header::decode(&mut data.as_slice()).unwrap(); assert_eq!(header, expected); } #[test] // Test vector from: https://github.com/ethereum/tests/blob/f47bbef4da376a49c8fc3166f09ab8a6d182f765/BlockchainTests/ValidBlocks/bcEIP1559/baseFee.json#L15-L36 fn test_eip1559_block_header_hash() { let expected_hash = b256!("0x6a251c7c3c5dca7b42407a3752ff48f3bbca1fab7f9868371d9918daf1988d1f"); let header = Header { parent_hash: B256::from_str("e0a94a7a3c9617401586b1a27025d2d9671332d22d540e0af72b069170380f2a").unwrap(), ommers_hash: B256::from_str("1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347").unwrap(), beneficiary: Address::from_str("ba5e000000000000000000000000000000000000").unwrap(), state_root: B256::from_str("ec3c94b18b8a1cff7d60f8d258ec723312932928626b4c9355eb4ab3568ec7f7").unwrap(), transactions_root: B256::from_str("50f738580ed699f0469702c7ccc63ed2e51bc034be9479b7bff4e68dee84accf").unwrap(), receipts_root: B256::from_str("29b0562f7140574dd0d50dee8a271b22e1a0a7b78fca58f7c60370d8317ba2a9").unwrap(), logs_bloom: Bloom::from_hex("00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000").unwrap(), difficulty: U256::from(0x020000), number: 1u64, gas_limit: U256::from(0x016345785d8a0000u128).to::(), gas_used: U256::from(0x015534).to::(), timestamp: 0x079e, extra_data: hex::decode("42").unwrap().into(), mix_hash: B256::from_str("0000000000000000000000000000000000000000000000000000000000000000").unwrap(), nonce: B64::ZERO, base_fee_per_gas: Some(875), withdrawals_root: None, blob_gas_used: None, excess_blob_gas: None, parent_beacon_block_root: None, requests_hash: None, }; assert_eq!(header.hash_slow(), expected_hash); } #[test] // Test vector from network fn block_network_roundtrip() { use alloy_rlp::Encodable; let data = hex::decode("f9034df90348a0fbdbd8d2d0ac5f14bd5fa90e547fe6f1d15019c724f8e7b60972d381cd5d9cf8a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d4934794c9577e7945db22e38fc060909f2278c7746b0f9ba05017cfa3b0247e35197215ae8d610265ffebc8edca8ea66d6567eb0adecda867a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000018355bb7b871fffffffffffff808462bd0e1ab9014bf90148a00000000000000000000000000000000000000000000000000000000000000000f85494319fa8f1bc4e53410e92d10d918659b16540e60a945a573efb304d04c1224cd012313e827eca5dce5d94a9c831c5a268031176ebf5f3de5051e8cba0dbfe94c9577e7945db22e38fc060909f2278c7746b0f9b808400000000f8c9b841a6946f2d16f68338cbcbd8b117374ab421128ce422467088456bceba9d70c34106128e6d4564659cf6776c08a4186063c0a05f7cffd695c10cf26a6f301b67f800b8412b782100c18c35102dc0a37ece1a152544f04ad7dc1868d18a9570f744ace60870f822f53d35e89a2ea9709ccbf1f4a25ee5003944faa845d02dde0a41d5704601b841d53caebd6c8a82456e85c2806a9e08381f959a31fb94a77e58f00e38ad97b2e0355b8519ab2122662cbe022f2a4ef7ff16adc0b2d5dcd123181ec79705116db300a063746963616c2062797a616e74696e65206661756c7420746f6c6572616e6365880000000000000000c0c0").unwrap(); let block = ::decode(&mut data.as_slice()).unwrap(); // encode and check that it matches the original data let mut encoded = Vec::new(); block.encode(&mut encoded); assert_eq!(data, encoded); // check that length of encoding is the same as the output of `length` assert_eq!(block.length(), encoded.len()); } } ================================================ FILE: crates/anvil/core/src/eth/mod.rs ================================================ use crate::{eth::subscription::SubscriptionId, types::ReorgOptions}; use alloy_primitives::{Address, B64, B256, Bytes, TxHash, U256, map::HashSet}; use alloy_rpc_types::{ BlockId, BlockNumberOrTag as BlockNumber, BlockOverrides, Filter, Index, anvil::{Forking, MineOptions}, pubsub::{Params as SubscriptionParams, SubscriptionKind}, request::TransactionRequest, simulate::SimulatePayload, state::StateOverride, trace::{ filter::TraceFilter, geth::{GethDebugTracingCallOptions, GethDebugTracingOptions}, parity::TraceType, }, }; use alloy_serde::WithOtherFields; use foundry_common::serde_helpers::{ deserialize_number, deserialize_number_opt, deserialize_number_seq, }; pub mod block; pub mod subscription; pub mod transaction; pub mod wallet; pub mod serde_helpers; use self::serde_helpers::*; /// Wrapper type that ensures the type is named `params` #[derive(Clone, Debug, PartialEq, Eq, serde::Deserialize)] pub struct Params { #[serde(default)] pub params: T, } /// Represents ethereum JSON-RPC API #[derive(Clone, Debug, serde::Deserialize)] #[serde(tag = "method", content = "params")] #[allow(clippy::large_enum_variant)] pub enum EthRequest { #[serde(rename = "web3_clientVersion", with = "empty_params")] Web3ClientVersion(()), #[serde(rename = "web3_sha3", with = "sequence")] Web3Sha3(Bytes), /// Returns the current Ethereum protocol version. #[serde(rename = "eth_protocolVersion", with = "empty_params")] EthProtocolVersion(()), #[serde(rename = "eth_chainId", with = "empty_params")] EthChainId(()), #[serde(rename = "eth_networkId", alias = "net_version", with = "empty_params")] EthNetworkId(()), #[serde(rename = "net_listening", with = "empty_params")] NetListening(()), /// Returns the number of hashes per second with which the node is mining. #[serde(rename = "eth_hashrate", with = "empty_params")] EthHashrate(()), #[serde(rename = "eth_gasPrice", with = "empty_params")] EthGasPrice(()), #[serde(rename = "eth_maxPriorityFeePerGas", with = "empty_params")] EthMaxPriorityFeePerGas(()), #[serde(rename = "eth_blobBaseFee", with = "empty_params")] EthBlobBaseFee(()), #[serde(rename = "eth_accounts", alias = "eth_requestAccounts", with = "empty_params")] EthAccounts(()), #[serde(rename = "eth_blockNumber", with = "empty_params")] EthBlockNumber(()), /// Returns the client coinbase address. #[serde(rename = "eth_coinbase", with = "empty_params")] EthCoinbase(()), #[serde(rename = "eth_getBalance")] EthGetBalance(Address, Option), #[serde(rename = "eth_getAccount")] EthGetAccount(Address, Option), #[serde(rename = "eth_getAccountInfo")] EthGetAccountInfo(Address, Option), #[serde(rename = "eth_getStorageAt")] EthGetStorageAt(Address, U256, Option), #[serde(rename = "eth_getBlockByHash")] EthGetBlockByHash(B256, bool), #[serde(rename = "eth_getBlockByNumber")] EthGetBlockByNumber( #[serde(deserialize_with = "lenient_block_number::lenient_block_number")] BlockNumber, bool, ), #[serde(rename = "eth_getTransactionCount")] EthGetTransactionCount(Address, Option), #[serde(rename = "eth_getBlockTransactionCountByHash", with = "sequence")] EthGetTransactionCountByHash(B256), #[serde( rename = "eth_getBlockTransactionCountByNumber", deserialize_with = "lenient_block_number::lenient_block_number_seq" )] EthGetTransactionCountByNumber(BlockNumber), #[serde(rename = "eth_getUncleCountByBlockHash", with = "sequence")] EthGetUnclesCountByHash(B256), #[serde( rename = "eth_getUncleCountByBlockNumber", deserialize_with = "lenient_block_number::lenient_block_number_seq" )] EthGetUnclesCountByNumber(BlockNumber), #[serde(rename = "eth_getCode")] EthGetCodeAt(Address, Option), /// Returns the account and storage values of the specified account including the Merkle-proof. /// This call can be used to verify that the data you are pulling from is not tampered with. #[serde(rename = "eth_getProof")] EthGetProof(Address, Vec, Option), /// The sign method calculates an Ethereum specific signature with: #[serde(rename = "eth_sign")] EthSign(Address, Bytes), /// The sign method calculates an Ethereum specific signature, equivalent to eth_sign: /// #[serde(rename = "personal_sign")] PersonalSign(Bytes, Address), #[serde(rename = "eth_signTransaction", with = "sequence")] EthSignTransaction(Box>), /// Signs data via [EIP-712](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-712.md). #[serde(rename = "eth_signTypedData")] EthSignTypedData(Address, serde_json::Value), /// Signs data via [EIP-712](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-712.md). #[serde(rename = "eth_signTypedData_v3")] EthSignTypedDataV3(Address, serde_json::Value), /// Signs data via [EIP-712](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-712.md), and includes full support of arrays and recursive data structures. #[serde(rename = "eth_signTypedData_v4")] EthSignTypedDataV4(Address, alloy_dyn_abi::TypedData), #[serde(rename = "eth_sendTransaction", with = "sequence")] EthSendTransaction(Box>), #[serde(rename = "eth_sendTransactionSync", with = "sequence")] EthSendTransactionSync(Box>), #[serde(rename = "eth_sendRawTransaction", with = "sequence")] EthSendRawTransaction(Bytes), #[serde(rename = "eth_sendRawTransactionSync", with = "sequence")] EthSendRawTransactionSync(Bytes), #[serde(rename = "eth_call")] EthCall( WithOtherFields, #[serde(default)] Option, #[serde(default)] Option, #[serde(default)] Option>, ), #[serde(rename = "eth_simulateV1")] EthSimulateV1(SimulatePayload, #[serde(default)] Option), #[serde(rename = "eth_createAccessList")] EthCreateAccessList(WithOtherFields, #[serde(default)] Option), #[serde(rename = "eth_estimateGas")] EthEstimateGas( WithOtherFields, #[serde(default)] Option, #[serde(default)] Option, #[serde(default)] Option>, ), #[serde(rename = "eth_fillTransaction", with = "sequence")] EthFillTransaction(WithOtherFields), #[serde(rename = "eth_getTransactionByHash", with = "sequence")] EthGetTransactionByHash(TxHash), /// Returns the blob for a given blob versioned hash. #[serde(rename = "anvil_getBlobByHash", with = "sequence")] GetBlobByHash(B256), /// Returns the blobs for a given transaction hash. #[serde(rename = "anvil_getBlobsByTransactionHash", with = "sequence")] GetBlobByTransactionHash(TxHash), /// Returns the genesis time for the chain #[serde(rename = "anvil_getGenesisTime", with = "empty_params")] GetGenesisTime(()), #[serde(rename = "eth_getTransactionByBlockHashAndIndex")] EthGetTransactionByBlockHashAndIndex(B256, Index), #[serde(rename = "eth_getTransactionByBlockNumberAndIndex")] EthGetTransactionByBlockNumberAndIndex(BlockNumber, Index), #[serde(rename = "eth_getRawTransactionByHash", with = "sequence")] EthGetRawTransactionByHash(TxHash), #[serde(rename = "eth_getRawTransactionByBlockHashAndIndex")] EthGetRawTransactionByBlockHashAndIndex(B256, Index), #[serde(rename = "eth_getRawTransactionByBlockNumberAndIndex")] EthGetRawTransactionByBlockNumberAndIndex(BlockNumber, Index), #[serde(rename = "eth_getTransactionReceipt", with = "sequence")] EthGetTransactionReceipt(B256), #[serde(rename = "eth_getBlockReceipts", with = "sequence")] EthGetBlockReceipts(BlockId), #[serde(rename = "eth_getUncleByBlockHashAndIndex")] EthGetUncleByBlockHashAndIndex(B256, Index), #[serde(rename = "eth_getUncleByBlockNumberAndIndex")] EthGetUncleByBlockNumberAndIndex( #[serde(deserialize_with = "lenient_block_number::lenient_block_number")] BlockNumber, Index, ), #[serde(rename = "eth_getLogs", with = "sequence")] EthGetLogs(Filter), /// Creates a filter object, based on filter options, to notify when the state changes (logs). #[serde(rename = "eth_newFilter", with = "sequence")] EthNewFilter(Filter), /// Polling method for a filter, which returns an array of logs which occurred since last poll. #[serde(rename = "eth_getFilterChanges", with = "sequence")] EthGetFilterChanges(String), /// Creates a filter in the node, to notify when a new block arrives. /// To check if the state has changed, call `eth_getFilterChanges`. #[serde(rename = "eth_newBlockFilter", with = "empty_params")] EthNewBlockFilter(()), /// Creates a filter in the node, to notify when new pending transactions arrive. /// To check if the state has changed, call `eth_getFilterChanges`. #[serde(rename = "eth_newPendingTransactionFilter", with = "empty_params")] EthNewPendingTransactionFilter(()), /// Returns an array of all logs matching filter with given id. #[serde(rename = "eth_getFilterLogs", with = "sequence")] EthGetFilterLogs(String), /// Removes the filter, returns true if the filter was installed #[serde(rename = "eth_uninstallFilter", with = "sequence")] EthUninstallFilter(String), #[serde(rename = "eth_getWork", with = "empty_params")] EthGetWork(()), #[serde(rename = "eth_submitWork")] EthSubmitWork(B64, B256, B256), #[serde(rename = "eth_submitHashrate")] EthSubmitHashRate(U256, B256), #[serde(rename = "eth_feeHistory")] EthFeeHistory( #[serde(deserialize_with = "deserialize_number")] U256, BlockNumber, #[serde(default)] Vec, ), #[serde(rename = "eth_syncing", with = "empty_params")] EthSyncing(()), #[serde(rename = "eth_config", with = "empty_params")] EthConfig(()), /// geth's `debug_getRawTransaction` endpoint #[serde(rename = "debug_getRawTransaction", with = "sequence")] DebugGetRawTransaction(TxHash), /// geth's `debug_traceTransaction` endpoint #[serde(rename = "debug_traceTransaction")] DebugTraceTransaction(B256, #[serde(default)] GethDebugTracingOptions), /// geth's `debug_traceCall` endpoint #[serde(rename = "debug_traceCall")] DebugTraceCall( WithOtherFields, #[serde(default)] Option, #[serde(default)] GethDebugTracingCallOptions, ), /// reth's `debug_codeByHash` endpoint #[serde(rename = "debug_codeByHash")] DebugCodeByHash(B256, #[serde(default)] Option), /// reth's `debug_dbGet` endpoint #[serde(rename = "debug_dbGet")] DebugDbGet(String), /// Trace transaction endpoint for parity's `trace_transaction` #[serde(rename = "trace_transaction", with = "sequence")] TraceTransaction(B256), /// Trace transaction endpoint for parity's `trace_block` #[serde( rename = "trace_block", deserialize_with = "lenient_block_number::lenient_block_number_seq" )] TraceBlock(BlockNumber), // Return filtered traces over blocks #[serde(rename = "trace_filter", with = "sequence")] TraceFilter(TraceFilter), /// Trace transaction endpoint for parity's `trace_replayBlockTransactions` #[serde(rename = "trace_replayBlockTransactions")] TraceReplayBlockTransactions( #[serde(deserialize_with = "lenient_block_number::lenient_block_number")] BlockNumber, HashSet, ), // Custom endpoints, they're not extracted to a separate type out of serde convenience /// send transactions impersonating specific account and contract addresses. #[serde( rename = "anvil_impersonateAccount", alias = "hardhat_impersonateAccount", with = "sequence" )] ImpersonateAccount(Address), /// Stops impersonating an account if previously set with `anvil_impersonateAccount` #[serde( rename = "anvil_stopImpersonatingAccount", alias = "hardhat_stopImpersonatingAccount", with = "sequence" )] StopImpersonatingAccount(Address), /// Will make every account impersonated #[serde( rename = "anvil_autoImpersonateAccount", alias = "hardhat_autoImpersonateAccount", with = "sequence" )] AutoImpersonateAccount(bool), /// Registers a signature/address pair for faking `ecrecover` results #[serde(rename = "anvil_impersonateSignature", with = "sequence")] ImpersonateSignature(Bytes, Address), /// Returns true if automatic mining is enabled, and false. #[serde(rename = "anvil_getAutomine", alias = "hardhat_getAutomine", with = "empty_params")] GetAutoMine(()), /// Mines a series of blocks #[serde(rename = "anvil_mine", alias = "hardhat_mine")] Mine( /// Number of blocks to mine, if not set `1` block is mined #[serde(default, deserialize_with = "deserialize_number_opt")] Option, /// The time interval between each block in seconds, defaults to `1` seconds /// The interval is applied only to blocks mined in the given method invocation, not to /// blocks mined afterwards. Set this to `0` to instantly mine _all_ blocks #[serde(default, deserialize_with = "deserialize_number_opt")] Option, ), /// Enables or disables, based on the single boolean argument, the automatic mining of new /// blocks with each new transaction submitted to the network. #[serde(rename = "anvil_setAutomine", alias = "evm_setAutomine", with = "sequence")] SetAutomine(bool), /// Sets the mining behavior to interval with the given interval (seconds) #[serde(rename = "anvil_setIntervalMining", alias = "evm_setIntervalMining", with = "sequence")] SetIntervalMining(u64), /// Gets the current mining behavior #[serde(rename = "anvil_getIntervalMining", with = "empty_params")] GetIntervalMining(()), /// Removes transactions from the pool #[serde(rename = "anvil_dropTransaction", alias = "hardhat_dropTransaction", with = "sequence")] DropTransaction(B256), /// Removes transactions from the pool #[serde( rename = "anvil_dropAllTransactions", alias = "hardhat_dropAllTransactions", with = "empty_params" )] DropAllTransactions(), /// Reset the fork to a fresh forked state, and optionally update the fork config #[serde(rename = "anvil_reset", alias = "hardhat_reset")] Reset(#[serde(default)] Option>>), /// Sets the backend rpc url #[serde(rename = "anvil_setRpcUrl", with = "sequence")] SetRpcUrl(String), /// Modifies the balance of an account. #[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), /// Sets the nonce of an address #[serde(rename = "anvil_setNonce", alias = "hardhat_setNonce", alias = "evm_setAccountNonce")] SetNonce(Address, #[serde(deserialize_with = "deserialize_number")] U256), /// Writes a single slot of the account's storage #[serde(rename = "anvil_setStorageAt", alias = "hardhat_setStorageAt")] SetStorageAt( Address, /// slot U256, /// value B256, ), /// Sets the coinbase address #[serde(rename = "anvil_setCoinbase", alias = "hardhat_setCoinbase", with = "sequence")] SetCoinbase(Address), /// Sets the chain id #[serde(rename = "anvil_setChainId", with = "sequence")] SetChainId(u64), /// Enable or disable logging #[serde( rename = "anvil_setLoggingEnabled", alias = "hardhat_setLoggingEnabled", with = "sequence" )] SetLogging(bool), /// Set the minimum gas price for the node #[serde( rename = "anvil_setMinGasPrice", alias = "hardhat_setMinGasPrice", deserialize_with = "deserialize_number_seq" )] SetMinGasPrice(U256), /// Sets the base fee of the next block #[serde( rename = "anvil_setNextBlockBaseFeePerGas", alias = "hardhat_setNextBlockBaseFeePerGas", deserialize_with = "deserialize_number_seq" )] SetNextBlockBaseFeePerGas(U256), /// Sets the specific timestamp /// Accepts timestamp (Unix epoch) with millisecond precision and returns the number of seconds /// between the given timestamp and the current time. #[serde( rename = "anvil_setTime", alias = "evm_setTime", deserialize_with = "deserialize_number_seq" )] EvmSetTime(U256), /// Serializes the current state (including contracts code, contract's storage, accounts /// properties, etc.) into a saveable data blob #[serde(rename = "anvil_dumpState", alias = "hardhat_dumpState")] DumpState(#[serde(default)] Option>>), /// Adds state previously dumped with `DumpState` to the current chain #[serde(rename = "anvil_loadState", alias = "hardhat_loadState", with = "sequence")] LoadState(Bytes), /// Retrieves the Anvil node configuration params #[serde(rename = "anvil_nodeInfo", with = "empty_params")] NodeInfo(()), /// Retrieves the Anvil node metadata. #[serde(rename = "anvil_metadata", alias = "hardhat_metadata", with = "empty_params")] AnvilMetadata(()), // Ganache compatible calls /// Snapshot the state of the blockchain at the current block. /// /// Ref #[serde(rename = "anvil_snapshot", alias = "evm_snapshot", with = "empty_params")] EvmSnapshot(()), /// Revert the state of the blockchain to a previous snapshot. /// Takes a single parameter, which is the snapshot id to revert to. /// /// Ref #[serde( rename = "anvil_revert", alias = "evm_revert", deserialize_with = "deserialize_number_seq" )] EvmRevert(U256), /// Jump forward in time by the given amount of time, in seconds. #[serde( rename = "anvil_increaseTime", alias = "evm_increaseTime", deserialize_with = "deserialize_number_seq" )] EvmIncreaseTime(U256), /// Similar to `evm_increaseTime` but takes the exact timestamp that you want in the next block #[serde( rename = "anvil_setNextBlockTimestamp", alias = "evm_setNextBlockTimestamp", deserialize_with = "deserialize_number_seq" )] EvmSetNextBlockTimeStamp(U256), /// Set the exact gas limit that you want in the next block #[serde( rename = "anvil_setBlockGasLimit", alias = "evm_setBlockGasLimit", deserialize_with = "deserialize_number_seq" )] EvmSetBlockGasLimit(U256), /// Similar to `evm_increaseTime` but takes sets a block timestamp `interval`. /// /// The timestamp of the next block will be computed as `lastBlock_timestamp + interval`. #[serde(rename = "anvil_setBlockTimestampInterval", with = "sequence")] EvmSetBlockTimeStampInterval(u64), /// Removes a `anvil_setBlockTimestampInterval` if it exists #[serde(rename = "anvil_removeBlockTimestampInterval", with = "empty_params")] EvmRemoveBlockTimeStampInterval(()), /// Mine a single block #[serde(rename = "evm_mine")] EvmMine(#[serde(default)] Option>>), /// Mine a single block and return detailed data /// /// This behaves exactly as `EvmMine` but returns different output, for compatibility reasons /// this is a separate call since `evm_mine` is not an anvil original. #[serde(rename = "anvil_mine_detailed", alias = "evm_mine_detailed")] EvmMineDetailed(#[serde(default)] Option>>), /// Execute a transaction regardless of signature status #[serde(rename = "eth_sendUnsignedTransaction", with = "sequence")] EthSendUnsignedTransaction(Box>), /// Returns the number of transactions currently pending for inclusion in the next block(s), as /// well as the ones that are being scheduled for future execution only. /// Ref: #[serde(rename = "txpool_status", with = "empty_params")] TxPoolStatus(()), /// Returns a summary of all the transactions currently pending for inclusion in the next /// block(s), as well as the ones that are being scheduled for future execution only. /// Ref: #[serde(rename = "txpool_inspect", with = "empty_params")] TxPoolInspect(()), /// Returns the details of all transactions currently pending for inclusion in the next /// block(s), as well as the ones that are being scheduled for future execution only. /// Ref: #[serde(rename = "txpool_content", with = "empty_params")] TxPoolContent(()), /// Otterscan's `ots_getApiLevel` endpoint /// Otterscan currently requires this endpoint, even though it's not part of the ots_* /// /// Related upstream issue: #[serde(rename = "erigon_getHeaderByNumber")] ErigonGetHeaderByNumber( #[serde(deserialize_with = "lenient_block_number::lenient_block_number_seq")] BlockNumber, ), /// Otterscan's `ots_getApiLevel` endpoint /// Used as a simple API versioning scheme for the ots_* namespace #[serde(rename = "ots_getApiLevel", with = "empty_params")] OtsGetApiLevel(()), /// Otterscan's `ots_getInternalOperations` endpoint /// Traces internal ETH transfers, contracts creation (CREATE/CREATE2) and self-destructs for a /// certain transaction. #[serde(rename = "ots_getInternalOperations", with = "sequence")] OtsGetInternalOperations(B256), /// Otterscan's `ots_hasCode` endpoint /// Check if an ETH address contains code at a certain block number. #[serde(rename = "ots_hasCode")] OtsHasCode( Address, #[serde(deserialize_with = "lenient_block_number::lenient_block_number", default)] BlockNumber, ), /// Otterscan's `ots_traceTransaction` endpoint /// Trace a transaction and generate a trace call tree. #[serde(rename = "ots_traceTransaction", with = "sequence")] OtsTraceTransaction(B256), /// Otterscan's `ots_getTransactionError` endpoint /// Given a transaction hash, returns its raw revert reason. #[serde(rename = "ots_getTransactionError", with = "sequence")] OtsGetTransactionError(B256), /// Otterscan's `ots_getBlockDetails` endpoint /// Given a block number, return its data. Similar to the standard eth_getBlockByNumber/Hash /// method, but can be optimized by excluding unnecessary data such as transactions and /// logBloom #[serde(rename = "ots_getBlockDetails")] OtsGetBlockDetails( #[serde(deserialize_with = "lenient_block_number::lenient_block_number_seq", default)] BlockNumber, ), /// Otterscan's `ots_getBlockDetails` endpoint /// Same as `ots_getBlockDetails`, but receiving a block hash instead of number #[serde(rename = "ots_getBlockDetailsByHash", with = "sequence")] OtsGetBlockDetailsByHash(B256), /// Otterscan's `ots_getBlockTransactions` endpoint /// Gets paginated transaction data for a certain block. Return data is similar to /// eth_getBlockBy* + eth_getTransactionReceipt. #[serde(rename = "ots_getBlockTransactions")] OtsGetBlockTransactions(u64, usize, usize), /// Otterscan's `ots_searchTransactionsBefore` endpoint /// Address history navigation. searches backwards from certain point in time. #[serde(rename = "ots_searchTransactionsBefore")] OtsSearchTransactionsBefore(Address, u64, usize), /// Otterscan's `ots_searchTransactionsAfter` endpoint /// Address history navigation. searches forward from certain point in time. #[serde(rename = "ots_searchTransactionsAfter")] OtsSearchTransactionsAfter(Address, u64, usize), /// Otterscan's `ots_getTransactionBySenderAndNonce` endpoint /// Given a sender address and a nonce, returns the tx hash or null if not found. It returns /// only the tx hash on success, you can use the standard eth_getTransactionByHash after that /// to get the full transaction data. #[serde(rename = "ots_getTransactionBySenderAndNonce")] OtsGetTransactionBySenderAndNonce( Address, #[serde(deserialize_with = "deserialize_number")] U256, ), /// Returns the transaction by sender and nonce /// Returns the full transaction data. #[serde(rename = "eth_getTransactionBySenderAndNonce")] EthGetTransactionBySenderAndNonce( Address, #[serde(deserialize_with = "deserialize_number")] U256, ), /// Otterscan's `ots_getTransactionBySenderAndNonce` endpoint /// Given an ETH contract address, returns the tx hash and the direct address who created the /// contract. #[serde(rename = "ots_getContractCreator", with = "sequence")] OtsGetContractCreator(Address), /// Removes transactions from the pool by sender origin. #[serde(rename = "anvil_removePoolTransactions", with = "sequence")] RemovePoolTransactions(Address), /// Reorg the chain #[serde(rename = "anvil_reorg")] Reorg(ReorgOptions), /// Rollback the chain #[serde(rename = "anvil_rollback", with = "sequence")] Rollback(Option), } /// Represents ethereum JSON-RPC API #[derive(Clone, Debug, PartialEq, Eq, serde::Deserialize)] #[serde(tag = "method", content = "params")] pub enum EthPubSub { /// Subscribe to an eth subscription #[serde(rename = "eth_subscribe")] EthSubscribe(SubscriptionKind, #[serde(default)] Box), /// Unsubscribe from an eth subscription #[serde(rename = "eth_unsubscribe", with = "sequence")] EthUnSubscribe(SubscriptionId), } /// Container type for either a request or a pub sub #[derive(Clone, Debug, serde::Deserialize)] #[serde(untagged)] pub enum EthRpcCall { Request(Box), PubSub(EthPubSub), } #[cfg(test)] mod tests { use super::*; #[test] fn test_web3_client_version() { let s = r#"{"method": "web3_clientVersion", "params":[]}"#; let value: serde_json::Value = serde_json::from_str(s).unwrap(); let _req = serde_json::from_value::(value).unwrap(); } #[test] fn test_web3_sha3() { let s = r#"{"method": "web3_sha3", "params":["0x68656c6c6f20776f726c64"]}"#; let value: serde_json::Value = serde_json::from_str(s).unwrap(); let _req = serde_json::from_value::(value).unwrap(); } #[test] fn test_eth_accounts() { let s = r#"{"method": "eth_accounts", "params":[]}"#; let value: serde_json::Value = serde_json::from_str(s).unwrap(); let _req = serde_json::from_value::(value).unwrap(); } #[test] fn test_eth_network_id() { let s = r#"{"method": "eth_networkId", "params":[]}"#; let value: serde_json::Value = serde_json::from_str(s).unwrap(); let _req = serde_json::from_value::(value).unwrap(); } #[test] fn test_eth_get_proof() { let s = r#"{"method":"eth_getProof","params":["0x7F0d15C7FAae65896648C8273B6d7E43f58Fa842",["0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421"],"latest"]}"#; let value: serde_json::Value = serde_json::from_str(s).unwrap(); let _req = serde_json::from_value::(value).unwrap(); } #[test] fn test_eth_chain_id() { let s = r#"{"method": "eth_chainId", "params":[]}"#; let value: serde_json::Value = serde_json::from_str(s).unwrap(); let _req = serde_json::from_value::(value).unwrap(); } #[test] fn test_net_listening() { let s = r#"{"method": "net_listening", "params":[]}"#; let value: serde_json::Value = serde_json::from_str(s).unwrap(); let _req = serde_json::from_value::(value).unwrap(); } #[test] fn test_eth_block_number() { let s = r#"{"method": "eth_blockNumber", "params":[]}"#; let value: serde_json::Value = serde_json::from_str(s).unwrap(); let _req = serde_json::from_value::(value).unwrap(); } #[test] fn test_eth_max_priority_fee() { let s = r#"{"method": "eth_maxPriorityFeePerGas", "params":[]}"#; let value: serde_json::Value = serde_json::from_str(s).unwrap(); let _req = serde_json::from_value::(value).unwrap(); } #[test] fn test_eth_syncing() { let s = r#"{"method": "eth_syncing", "params":[]}"#; let value: serde_json::Value = serde_json::from_str(s).unwrap(); let _req = serde_json::from_value::(value).unwrap(); } #[test] fn test_custom_impersonate_account() { let s = r#"{"method": "anvil_impersonateAccount", "params": ["0xd84de507f3fada7df80908082d3239466db55a71"]}"#; let value: serde_json::Value = serde_json::from_str(s).unwrap(); let _req = serde_json::from_value::(value).unwrap(); } #[test] fn test_custom_stop_impersonate_account() { let s = r#"{"method": "anvil_stopImpersonatingAccount", "params": ["0x364d6D0333432C3Ac016Ca832fb8594A8cE43Ca6"]}"#; let value: serde_json::Value = serde_json::from_str(s).unwrap(); let _req = serde_json::from_value::(value).unwrap(); } #[test] fn test_custom_auto_impersonate_account() { let s = r#"{"method": "anvil_autoImpersonateAccount", "params": [true]}"#; let value: serde_json::Value = serde_json::from_str(s).unwrap(); let _req = serde_json::from_value::(value).unwrap(); } #[test] fn test_custom_get_automine() { let s = r#"{"method": "anvil_getAutomine", "params": []}"#; let value: serde_json::Value = serde_json::from_str(s).unwrap(); let _req = serde_json::from_value::(value).unwrap(); } #[test] fn test_custom_mine() { let s = r#"{"method": "anvil_mine", "params": []}"#; let value: serde_json::Value = serde_json::from_str(s).unwrap(); let req = serde_json::from_value::(value).unwrap(); match req { EthRequest::Mine(num, time) => { assert!(num.is_none()); assert!(time.is_none()); } _ => unreachable!(), } let s = r#"{"method": "anvil_mine", "params": ["0xd84de507f3fada7df80908082d3239466db55a71"]}"#; let value: serde_json::Value = serde_json::from_str(s).unwrap(); let req = serde_json::from_value::(value).unwrap(); match req { EthRequest::Mine(num, time) => { assert!(num.is_some()); assert!(time.is_none()); } _ => unreachable!(), } let s = r#"{"method": "anvil_mine", "params": ["0xd84de507f3fada7df80908082d3239466db55a71", "0xd84de507f3fada7df80908082d3239466db55a71"]}"#; let value: serde_json::Value = serde_json::from_str(s).unwrap(); let req = serde_json::from_value::(value).unwrap(); match req { EthRequest::Mine(num, time) => { assert!(num.is_some()); assert!(time.is_some()); } _ => unreachable!(), } } #[test] fn test_custom_auto_mine() { let s = r#"{"method": "anvil_setAutomine", "params": [false]}"#; let value: serde_json::Value = serde_json::from_str(s).unwrap(); let _req = serde_json::from_value::(value).unwrap(); let s = r#"{"method": "evm_setAutomine", "params": [false]}"#; let value: serde_json::Value = serde_json::from_str(s).unwrap(); let _req = serde_json::from_value::(value).unwrap(); } #[test] fn test_custom_interval_mining() { let s = r#"{"method": "anvil_setIntervalMining", "params": [100]}"#; let value: serde_json::Value = serde_json::from_str(s).unwrap(); let _req = serde_json::from_value::(value).unwrap(); let s = r#"{"method": "evm_setIntervalMining", "params": [100]}"#; let value: serde_json::Value = serde_json::from_str(s).unwrap(); let _req = serde_json::from_value::(value).unwrap(); } #[test] fn test_custom_drop_tx() { let s = r#"{"method": "anvil_dropTransaction", "params": ["0x4a3b0fce2cb9707b0baa68640cf2fe858c8bb4121b2a8cb904ff369d38a560ff"]}"#; let value: serde_json::Value = serde_json::from_str(s).unwrap(); let _req = serde_json::from_value::(value).unwrap(); } #[test] fn test_custom_reset() { let s = r#"{"method": "anvil_reset", "params": [{"forking": {"jsonRpcUrl": "https://ethereumpublicnode.com", "blockNumber": "18441649" } }]}"#; let value: serde_json::Value = serde_json::from_str(s).unwrap(); let req = serde_json::from_value::(value).unwrap(); match req { EthRequest::Reset(forking) => { let forking = forking.and_then(|f| f.params); assert_eq!( forking, Some(Forking { json_rpc_url: Some("https://ethereumpublicnode.com".into()), block_number: Some(18441649) }) ) } _ => unreachable!(), } let s = r#"{"method": "anvil_reset", "params": [ { "forking": { "jsonRpcUrl": "https://eth-mainnet.alchemyapi.io/v2/", "blockNumber": 11095000 }}]}"#; let value: serde_json::Value = serde_json::from_str(s).unwrap(); let req = serde_json::from_value::(value).unwrap(); match req { EthRequest::Reset(forking) => { let forking = forking.and_then(|f| f.params); assert_eq!( forking, Some(Forking { json_rpc_url: Some( "https://eth-mainnet.alchemyapi.io/v2/".to_string() ), block_number: Some(11095000) }) ) } _ => unreachable!(), } let s = r#"{"method": "anvil_reset", "params": [ { "forking": { "jsonRpcUrl": "https://eth-mainnet.alchemyapi.io/v2/" }}]}"#; let value: serde_json::Value = serde_json::from_str(s).unwrap(); let req = serde_json::from_value::(value).unwrap(); match req { EthRequest::Reset(forking) => { let forking = forking.and_then(|f| f.params); assert_eq!( forking, Some(Forking { json_rpc_url: Some( "https://eth-mainnet.alchemyapi.io/v2/".to_string() ), block_number: None }) ) } _ => unreachable!(), } let s = r#"{"method":"anvil_reset","params":[{"jsonRpcUrl": "http://localhost:8545", "blockNumber": 14000000}]}"#; let value: serde_json::Value = serde_json::from_str(s).unwrap(); let req = serde_json::from_value::(value).unwrap(); match req { EthRequest::Reset(forking) => { let forking = forking.and_then(|f| f.params); assert_eq!( forking, Some(Forking { json_rpc_url: Some("http://localhost:8545".to_string()), block_number: Some(14000000) }) ) } _ => unreachable!(), } let s = r#"{"method":"anvil_reset","params":[{ "blockNumber": 14000000}]}"#; let value: serde_json::Value = serde_json::from_str(s).unwrap(); let req = serde_json::from_value::(value).unwrap(); match req { EthRequest::Reset(forking) => { let forking = forking.and_then(|f| f.params); assert_eq!( forking, Some(Forking { json_rpc_url: None, block_number: Some(14000000) }) ) } _ => unreachable!(), } let s = r#"{"method":"anvil_reset","params":[{ "blockNumber": "14000000"}]}"#; let value: serde_json::Value = serde_json::from_str(s).unwrap(); let req = serde_json::from_value::(value).unwrap(); match req { EthRequest::Reset(forking) => { let forking = forking.and_then(|f| f.params); assert_eq!( forking, Some(Forking { json_rpc_url: None, block_number: Some(14000000) }) ) } _ => unreachable!(), } let s = r#"{"method":"anvil_reset","params":[{"jsonRpcUrl": "http://localhost:8545"}]}"#; let value: serde_json::Value = serde_json::from_str(s).unwrap(); let req = serde_json::from_value::(value).unwrap(); match req { EthRequest::Reset(forking) => { let forking = forking.and_then(|f| f.params); assert_eq!( forking, Some(Forking { json_rpc_url: Some("http://localhost:8545".to_string()), block_number: None }) ) } _ => unreachable!(), } let s = r#"{"method": "anvil_reset"}"#; let value: serde_json::Value = serde_json::from_str(s).unwrap(); let req = serde_json::from_value::(value).unwrap(); match req { EthRequest::Reset(forking) => { assert!(forking.is_none()) } _ => unreachable!(), } } #[test] fn test_custom_set_balance() { let s = r#"{"method": "anvil_setBalance", "params": ["0xd84de507f3fada7df80908082d3239466db55a71", "0x0"]}"#; let value: serde_json::Value = serde_json::from_str(s).unwrap(); let _req = serde_json::from_value::(value).unwrap(); let s = r#"{"method": "anvil_setBalance", "params": ["0xd84de507f3fada7df80908082d3239466db55a71", 1337]}"#; let value: serde_json::Value = serde_json::from_str(s).unwrap(); let _req = serde_json::from_value::(value).unwrap(); } #[test] fn test_custom_set_code() { let s = r#"{"method": "anvil_setCode", "params": ["0xd84de507f3fada7df80908082d3239466db55a71", "0x0123456789abcdef"]}"#; let value: serde_json::Value = serde_json::from_str(s).unwrap(); let _req = serde_json::from_value::(value).unwrap(); let s = r#"{"method": "anvil_setCode", "params": ["0xd84de507f3fada7df80908082d3239466db55a71", "0x"]}"#; let value: serde_json::Value = serde_json::from_str(s).unwrap(); let _req = serde_json::from_value::(value).unwrap(); let s = r#"{"method": "anvil_setCode", "params": ["0xd84de507f3fada7df80908082d3239466db55a71", ""]}"#; let value: serde_json::Value = serde_json::from_str(s).unwrap(); let _req = serde_json::from_value::(value).unwrap(); } #[test] fn test_custom_set_nonce() { let s = r#"{"method": "anvil_setNonce", "params": ["0xd84de507f3fada7df80908082d3239466db55a71", "0x0"]}"#; let value: serde_json::Value = serde_json::from_str(s).unwrap(); let _req = serde_json::from_value::(value).unwrap(); let s = r#"{"method": "hardhat_setNonce", "params": ["0xd84de507f3fada7df80908082d3239466db55a71", "0x0"]}"#; let value: serde_json::Value = serde_json::from_str(s).unwrap(); let _req = serde_json::from_value::(value).unwrap(); let s = r#"{"method": "evm_setAccountNonce", "params": ["0xd84de507f3fada7df80908082d3239466db55a71", "0x0"]}"#; let value: serde_json::Value = serde_json::from_str(s).unwrap(); let _req = serde_json::from_value::(value).unwrap(); } #[test] fn test_serde_custom_set_storage_at() { let s = r#"{"method": "anvil_setStorageAt", "params": ["0x295a70b2de5e3953354a6a8344e616ed314d7251", "0x0", "0x0000000000000000000000000000000000000000000000000000000000003039"]}"#; let value: serde_json::Value = serde_json::from_str(s).unwrap(); let _req = serde_json::from_value::(value).unwrap(); let s = r#"{"method": "hardhat_setStorageAt", "params": ["0xe9e7CEA3DedcA5984780Bafc599bD69ADd087D56", "0xa6eef7e35abe7026729641147f7915573c7e97b47efa546f5f6e3230263bcb49", "0x0000000000000000000000000000000000000000000000000000000000003039"]}"#; let value: serde_json::Value = serde_json::from_str(s).unwrap(); let _req = serde_json::from_value::(value).unwrap(); } #[test] fn test_serde_custom_coinbase() { let s = r#"{"method": "anvil_setCoinbase", "params": ["0x295a70b2de5e3953354a6a8344e616ed314d7251"]}"#; let value: serde_json::Value = serde_json::from_str(s).unwrap(); let _req = serde_json::from_value::(value).unwrap(); } #[test] fn test_serde_custom_logging() { let s = r#"{"method": "anvil_setLoggingEnabled", "params": [false]}"#; let value: serde_json::Value = serde_json::from_str(s).unwrap(); let _req = serde_json::from_value::(value).unwrap(); } #[test] fn test_serde_custom_min_gas_price() { let s = r#"{"method": "anvil_setMinGasPrice", "params": ["0x0"]}"#; let value: serde_json::Value = serde_json::from_str(s).unwrap(); let _req = serde_json::from_value::(value).unwrap(); } #[test] fn test_serde_custom_next_block_base_fee() { let s = r#"{"method": "anvil_setNextBlockBaseFeePerGas", "params": ["0x0"]}"#; let value: serde_json::Value = serde_json::from_str(s).unwrap(); let _req = serde_json::from_value::(value).unwrap(); } #[test] fn test_serde_set_time() { let s = r#"{"method": "anvil_setTime", "params": ["0x0"]}"#; let value: serde_json::Value = serde_json::from_str(s).unwrap(); let _req = serde_json::from_value::(value).unwrap(); let s = r#"{"method": "anvil_increaseTime", "params": 1}"#; let value: serde_json::Value = serde_json::from_str(s).unwrap(); let _req = serde_json::from_value::(value).unwrap(); } #[test] fn test_serde_custom_dump_state() { let s = r#"{"method": "anvil_dumpState", "params": [true]}"#; let value: serde_json::Value = serde_json::from_str(s).unwrap(); let _req = serde_json::from_value::(value).unwrap(); let s = r#"{"method": "anvil_dumpState"}"#; let value: serde_json::Value = serde_json::from_str(s).unwrap(); let req = serde_json::from_value::(value).unwrap(); match req { EthRequest::DumpState(param) => { assert!(param.is_none()); } _ => unreachable!(), } } #[test] fn test_serde_custom_load_state() { let s = r#"{"method": "anvil_loadState", "params": ["0x0001"] }"#; let value: serde_json::Value = serde_json::from_str(s).unwrap(); let _req = serde_json::from_value::(value).unwrap(); } #[test] fn test_serde_custom_snapshot() { let s = r#"{"method": "anvil_snapshot", "params": [] }"#; let value: serde_json::Value = serde_json::from_str(s).unwrap(); let _req = serde_json::from_value::(value).unwrap(); let s = r#"{"method": "evm_snapshot", "params": [] }"#; let value: serde_json::Value = serde_json::from_str(s).unwrap(); let _req = serde_json::from_value::(value).unwrap(); } #[test] fn test_serde_custom_revert() { let s = r#"{"method": "anvil_revert", "params": ["0x0"]}"#; let value: serde_json::Value = serde_json::from_str(s).unwrap(); let _req = serde_json::from_value::(value).unwrap(); } #[test] fn test_serde_custom_increase_time() { let s = r#"{"method": "anvil_increaseTime", "params": ["0x0"]}"#; let value: serde_json::Value = serde_json::from_str(s).unwrap(); let _req = serde_json::from_value::(value).unwrap(); let s = r#"{"method": "anvil_increaseTime", "params": [1]}"#; let value: serde_json::Value = serde_json::from_str(s).unwrap(); let _req = serde_json::from_value::(value).unwrap(); let s = r#"{"method": "anvil_increaseTime", "params": 1}"#; let value: serde_json::Value = serde_json::from_str(s).unwrap(); let _req = serde_json::from_value::(value).unwrap(); let s = r#"{"method": "evm_increaseTime", "params": ["0x0"]}"#; let value: serde_json::Value = serde_json::from_str(s).unwrap(); let _req = serde_json::from_value::(value).unwrap(); let s = r#"{"method": "evm_increaseTime", "params": [1]}"#; let value: serde_json::Value = serde_json::from_str(s).unwrap(); let _req = serde_json::from_value::(value).unwrap(); let s = r#"{"method": "evm_increaseTime", "params": 1}"#; let value: serde_json::Value = serde_json::from_str(s).unwrap(); let _req = serde_json::from_value::(value).unwrap(); } #[test] fn test_serde_custom_next_timestamp() { let s = r#"{"method": "anvil_setNextBlockTimestamp", "params": [100]}"#; let value: serde_json::Value = serde_json::from_str(s).unwrap(); let _req = serde_json::from_value::(value).unwrap(); let s = r#"{"method": "evm_setNextBlockTimestamp", "params": [100]}"#; let value: serde_json::Value = serde_json::from_str(s).unwrap(); let _req = serde_json::from_value::(value).unwrap(); let s = r#"{"method": "evm_setNextBlockTimestamp", "params": ["0x64e0f308"]}"#; let value: serde_json::Value = serde_json::from_str(s).unwrap(); let _req = serde_json::from_value::(value).unwrap(); } #[test] fn test_serde_custom_timestamp_interval() { let s = r#"{"method": "anvil_setBlockTimestampInterval", "params": [100]}"#; let value: serde_json::Value = serde_json::from_str(s).unwrap(); let _req = serde_json::from_value::(value).unwrap(); } #[test] fn test_serde_custom_remove_timestamp_interval() { let s = r#"{"method": "anvil_removeBlockTimestampInterval", "params": []}"#; let value: serde_json::Value = serde_json::from_str(s).unwrap(); let _req = serde_json::from_value::(value).unwrap(); } #[test] fn test_serde_custom_evm_mine() { let s = r#"{"method": "evm_mine", "params": [100]}"#; let value: serde_json::Value = serde_json::from_str(s).unwrap(); let _req = serde_json::from_value::(value).unwrap(); let s = r#"{"method": "evm_mine", "params": [{ "timestamp": 100, "blocks": 100 }]}"#; let value: serde_json::Value = serde_json::from_str(s).unwrap(); let req = serde_json::from_value::(value).unwrap(); match req { EthRequest::EvmMine(params) => { assert_eq!( params.unwrap().params.unwrap_or_default(), MineOptions::Options { timestamp: Some(100), blocks: Some(100) } ) } _ => unreachable!(), } let s = r#"{"method": "evm_mine"}"#; let value: serde_json::Value = serde_json::from_str(s).unwrap(); let req = serde_json::from_value::(value).unwrap(); match req { EthRequest::EvmMine(params) => { assert!(params.is_none()) } _ => unreachable!(), } let s = r#"{"method": "evm_mine", "params": []}"#; let value: serde_json::Value = serde_json::from_str(s).unwrap(); let _req = serde_json::from_value::(value).unwrap(); } #[test] fn test_serde_custom_evm_mine_detailed() { let s = r#"{"method": "anvil_mine_detailed", "params": [100]}"#; let value: serde_json::Value = serde_json::from_str(s).unwrap(); let _req = serde_json::from_value::(value).unwrap(); let s = r#"{"method": "anvil_mine_detailed", "params": [{ "timestamp": 100, "blocks": 100 }]}"#; let value: serde_json::Value = serde_json::from_str(s).unwrap(); let req = serde_json::from_value::(value).unwrap(); match req { EthRequest::EvmMineDetailed(params) => { assert_eq!( params.unwrap().params.unwrap_or_default(), MineOptions::Options { timestamp: Some(100), blocks: Some(100) } ) } _ => unreachable!(), } let s = r#"{"method": "evm_mine_detailed"}"#; let value: serde_json::Value = serde_json::from_str(s).unwrap(); let req = serde_json::from_value::(value).unwrap(); match req { EthRequest::EvmMineDetailed(params) => { assert!(params.is_none()) } _ => unreachable!(), } let s = r#"{"method": "anvil_mine_detailed", "params": []}"#; let value: serde_json::Value = serde_json::from_str(s).unwrap(); let _req = serde_json::from_value::(value).unwrap(); } #[test] fn test_serde_custom_evm_mine_hex() { let s = r#"{"method": "evm_mine", "params": ["0x63b6ff08"]}"#; let value: serde_json::Value = serde_json::from_str(s).unwrap(); let req = serde_json::from_value::(value).unwrap(); match req { EthRequest::EvmMine(params) => { assert_eq!( params.unwrap().params.unwrap_or_default(), MineOptions::Timestamp(Some(1672937224)) ) } _ => unreachable!(), } let s = r#"{"method": "evm_mine", "params": [{"timestamp": "0x63b6ff08"}]}"#; let value: serde_json::Value = serde_json::from_str(s).unwrap(); let req = serde_json::from_value::(value).unwrap(); match req { EthRequest::EvmMine(params) => { assert_eq!( params.unwrap().params.unwrap_or_default(), MineOptions::Options { timestamp: Some(1672937224), blocks: None } ) } _ => unreachable!(), } } #[test] fn test_eth_uncle_count_by_block_hash() { let s = r#"{"jsonrpc":"2.0","method":"eth_getUncleCountByBlockHash","params":["0x4a3b0fce2cb9707b0baa68640cf2fe858c8bb4121b2a8cb904ff369d38a560ff"]}"#; let value: serde_json::Value = serde_json::from_str(s).unwrap(); let _req = serde_json::from_value::(value).unwrap(); } #[test] fn test_eth_block_tx_count_by_block_hash() { let s = r#"{"jsonrpc":"2.0","method":"eth_getBlockTransactionCountByHash","params":["0x4a3b0fce2cb9707b0baa68640cf2fe858c8bb4121b2a8cb904ff369d38a560ff"]}"#; let value: serde_json::Value = serde_json::from_str(s).unwrap(); let _req = serde_json::from_value::(value).unwrap(); } #[test] fn test_eth_get_logs() { let s = r#"{"jsonrpc":"2.0","method":"eth_getLogs","params":[{"topics":["0x000000000000000000000000a94f5374fce5edbc8e2a8697c15331677e6ebf0b"]}],"id":74}"#; let value: serde_json::Value = serde_json::from_str(s).unwrap(); let _req = serde_json::from_value::(value).unwrap(); } #[test] fn test_eth_new_filter() { let s = r#"{"method": "eth_newFilter", "params": [{"topics":["0x000000000000000000000000a94f5374fce5edbc8e2a8697c15331677e6ebf0b"]}],"id":73}"#; let value: serde_json::Value = serde_json::from_str(s).unwrap(); let _req = serde_json::from_value::(value).unwrap(); } #[test] fn test_serde_eth_unsubscribe() { let s = r#"{"id": 1, "method": "eth_unsubscribe", "params": ["0x9cef478923ff08bf67fde6c64013158d"]}"#; let value: serde_json::Value = serde_json::from_str(s).unwrap(); let _req = serde_json::from_value::(value).unwrap(); } #[test] fn test_serde_eth_subscribe() { let s = r#"{"id": 1, "method": "eth_subscribe", "params": ["newHeads"]}"#; let value: serde_json::Value = serde_json::from_str(s).unwrap(); let _req = serde_json::from_value::(value).unwrap(); let s = r#"{"id": 1, "method": "eth_subscribe", "params": ["logs", {"address": "0x8320fe7702b96808f7bbc0d4a888ed1468216cfd", "topics": ["0xd78a0cb8bb633d06981248b816e7bd33c2a35a6089241d099fa519e361cab902"]}]}"#; let value: serde_json::Value = serde_json::from_str(s).unwrap(); let _req = serde_json::from_value::(value).unwrap(); let s = r#"{"id": 1, "method": "eth_subscribe", "params": ["newPendingTransactions"]}"#; let value: serde_json::Value = serde_json::from_str(s).unwrap(); let _req = serde_json::from_value::(value).unwrap(); let s = r#"{"id": 1, "method": "eth_subscribe", "params": ["syncing"]}"#; let value: serde_json::Value = serde_json::from_str(s).unwrap(); let _req = serde_json::from_value::(value).unwrap(); } #[test] fn test_serde_debug_raw_transaction() { let s = r#"{"jsonrpc":"2.0","method":"debug_getRawTransaction","params":["0x3ed3a89bc10115a321aee238c02de214009f8532a65368e5df5eaf732ee7167c"],"id":1}"#; let value: serde_json::Value = serde_json::from_str(s).unwrap(); let _req = serde_json::from_value::(value).unwrap(); let s = r#"{"jsonrpc":"2.0","method":"eth_getRawTransactionByHash","params":["0x3ed3a89bc10115a321aee238c02de214009f8532a65368e5df5eaf732ee7167c"],"id":1}"#; let value: serde_json::Value = serde_json::from_str(s).unwrap(); let _req = serde_json::from_value::(value).unwrap(); let s = r#"{"jsonrpc":"2.0","method":"eth_getRawTransactionByBlockHashAndIndex","params":["0x3ed3a89bc10115a321aee238c02de214009f8532a65368e5df5eaf732ee7167c",1],"id":1}"#; let value: serde_json::Value = serde_json::from_str(s).unwrap(); let _req = serde_json::from_value::(value).unwrap(); let s = r#"{"jsonrpc":"2.0","method":"eth_getRawTransactionByBlockNumberAndIndex","params":["0x3ed3a89b",0],"id":1}"#; let value: serde_json::Value = serde_json::from_str(s).unwrap(); let _req = serde_json::from_value::(value).unwrap(); } #[test] fn test_serde_debug_trace_transaction() { let s = r#"{"method": "debug_traceTransaction", "params": ["0x4a3b0fce2cb9707b0baa68640cf2fe858c8bb4121b2a8cb904ff369d38a560ff"]}"#; let value: serde_json::Value = serde_json::from_str(s).unwrap(); let _req = serde_json::from_value::(value).unwrap(); let s = r#"{"method": "debug_traceTransaction", "params": ["0x4a3b0fce2cb9707b0baa68640cf2fe858c8bb4121b2a8cb904ff369d38a560ff", {}]}"#; let value: serde_json::Value = serde_json::from_str(s).unwrap(); let _req = serde_json::from_value::(value).unwrap(); let s = r#"{"method": "debug_traceTransaction", "params": ["0x4a3b0fce2cb9707b0baa68640cf2fe858c8bb4121b2a8cb904ff369d38a560ff", {"disableStorage": true}]}"#; let value: serde_json::Value = serde_json::from_str(s).unwrap(); let _req = serde_json::from_value::(value).unwrap(); } #[test] fn test_serde_debug_trace_call() { let s = r#"{"method": "debug_traceCall", "params": [{"data":"0xcfae3217","from":"0xd84de507f3fada7df80908082d3239466db55a71","to":"0xcbe828fdc46e3b1c351ec90b1a5e7d9742c0398d"}]}"#; let value: serde_json::Value = serde_json::from_str(s).unwrap(); let _req = serde_json::from_value::(value).unwrap(); let s = r#"{"method": "debug_traceCall", "params": [{"data":"0xcfae3217","from":"0xd84de507f3fada7df80908082d3239466db55a71","to":"0xcbe828fdc46e3b1c351ec90b1a5e7d9742c0398d"}, { "blockNumber": "latest" }]}"#; let value: serde_json::Value = serde_json::from_str(s).unwrap(); let _req = serde_json::from_value::(value).unwrap(); let s = r#"{"method": "debug_traceCall", "params": [{"data":"0xcfae3217","from":"0xd84de507f3fada7df80908082d3239466db55a71","to":"0xcbe828fdc46e3b1c351ec90b1a5e7d9742c0398d"}, { "blockNumber": "0x0" }]}"#; let value: serde_json::Value = serde_json::from_str(s).unwrap(); let _req = serde_json::from_value::(value).unwrap(); let s = r#"{"method": "debug_traceCall", "params": [{"data":"0xcfae3217","from":"0xd84de507f3fada7df80908082d3239466db55a71","to":"0xcbe828fdc46e3b1c351ec90b1a5e7d9742c0398d"}, { "blockHash": "0xd4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3" }]}"#; let value: serde_json::Value = serde_json::from_str(s).unwrap(); let _req = serde_json::from_value::(value).unwrap(); let s = r#"{"method": "debug_traceCall", "params": [{"data":"0xcfae3217","from":"0xd84de507f3fada7df80908082d3239466db55a71","to":"0xcbe828fdc46e3b1c351ec90b1a5e7d9742c0398d"}, { "blockNumber": "0x0" }, {"disableStorage": true}]}"#; let value: serde_json::Value = serde_json::from_str(s).unwrap(); let _req = serde_json::from_value::(value).unwrap(); } #[test] fn test_serde_eth_storage() { let s = r#"{"method": "eth_getStorageAt", "params": ["0x295a70b2de5e3953354a6a8344e616ed314d7251", "0x0", "latest"]}"#; let value: serde_json::Value = serde_json::from_str(s).unwrap(); let _req = serde_json::from_value::(value).unwrap(); } #[test] fn test_eth_call() { let req = r#"{"data":"0xcfae3217","from":"0xd84de507f3fada7df80908082d3239466db55a71","to":"0xcbe828fdc46e3b1c351ec90b1a5e7d9742c0398d"}"#; let _req = serde_json::from_str::(req).unwrap(); let s = r#"{"method": "eth_call", "params":[{"data":"0xcfae3217","from":"0xd84de507f3fada7df80908082d3239466db55a71","to":"0xcbe828fdc46e3b1c351ec90b1a5e7d9742c0398d"},"latest"]}"#; let _req = serde_json::from_str::(s).unwrap(); let s = r#"{"method": "eth_call", "params":[{"data":"0xcfae3217","from":"0xd84de507f3fada7df80908082d3239466db55a71","to":"0xcbe828fdc46e3b1c351ec90b1a5e7d9742c0398d"}]}"#; let _req = serde_json::from_str::(s).unwrap(); let s = r#"{"method": "eth_call", "params":[{"data":"0xcfae3217","from":"0xd84de507f3fada7df80908082d3239466db55a71","to":"0xcbe828fdc46e3b1c351ec90b1a5e7d9742c0398d"}, { "blockNumber": "latest" }]}"#; let _req = serde_json::from_str::(s).unwrap(); let s = r#"{"method": "eth_call", "params":[{"data":"0xcfae3217","from":"0xd84de507f3fada7df80908082d3239466db55a71","to":"0xcbe828fdc46e3b1c351ec90b1a5e7d9742c0398d"}, { "blockNumber": "0x0" }]}"#; let _req = serde_json::from_str::(s).unwrap(); let s = r#"{"method": "eth_call", "params":[{"data":"0xcfae3217","from":"0xd84de507f3fada7df80908082d3239466db55a71","to":"0xcbe828fdc46e3b1c351ec90b1a5e7d9742c0398d"}, { "blockHash":"0xd4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3" }]}"#; let _req = serde_json::from_str::(s).unwrap(); } #[test] fn test_serde_eth_balance() { let s = r#"{"method": "eth_getBalance", "params": ["0x295a70b2de5e3953354a6a8344e616ed314d7251", "latest"]}"#; let value: serde_json::Value = serde_json::from_str(s).unwrap(); let _req = serde_json::from_value::(value).unwrap(); } #[test] fn test_serde_eth_block_by_number() { let s = r#"{"method": "eth_getBlockByNumber", "params": ["0x0", true]}"#; let value: serde_json::Value = serde_json::from_str(s).unwrap(); let _req = serde_json::from_value::(value).unwrap(); let s = r#"{"method": "eth_getBlockByNumber", "params": ["latest", true]}"#; let value: serde_json::Value = serde_json::from_str(s).unwrap(); let _req = serde_json::from_value::(value).unwrap(); let s = r#"{"method": "eth_getBlockByNumber", "params": ["earliest", true]}"#; let value: serde_json::Value = serde_json::from_str(s).unwrap(); let _req = serde_json::from_value::(value).unwrap(); let s = r#"{"method": "eth_getBlockByNumber", "params": ["pending", true]}"#; let value: serde_json::Value = serde_json::from_str(s).unwrap(); let _req = serde_json::from_value::(value).unwrap(); // this case deviates from the spec, but we're supporting this for legacy reasons: let s = r#"{"method": "eth_getBlockByNumber", "params": [0, true]}"#; let value: serde_json::Value = serde_json::from_str(s).unwrap(); let _req = serde_json::from_value::(value).unwrap(); } #[test] fn test_eth_sign() { let s = r#"{"method": "eth_sign", "params": ["0xd84de507f3fada7df80908082d3239466db55a71", "0x00"]}"#; let value: serde_json::Value = serde_json::from_str(s).unwrap(); let _req = serde_json::from_value::(value).unwrap(); let s = r#"{"method": "personal_sign", "params": ["0x00", "0xd84de507f3fada7df80908082d3239466db55a71"]}"#; let value: serde_json::Value = serde_json::from_str(s).unwrap(); let _req = serde_json::from_value::(value).unwrap(); } #[test] fn test_eth_sign_typed_data() { let s = r#"{"method":"eth_signTypedData_v4","params":["0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826", {"types":{"EIP712Domain":[{"name":"name","type":"string"},{"name":"version","type":"string"},{"name":"chainId","type":"uint256"},{"name":"verifyingContract","type":"address"}],"Person":[{"name":"name","type":"string"},{"name":"wallet","type":"address"}],"Mail":[{"name":"from","type":"Person"},{"name":"to","type":"Person"},{"name":"contents","type":"string"}]},"primaryType":"Mail","domain":{"name":"Ether Mail","version":"1","chainId":1,"verifyingContract":"0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC"},"message":{"from":{"name":"Cow","wallet":"0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826"},"to":{"name":"Bob","wallet":"0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB"},"contents":"Hello, Bob!"}}]}"#; let value: serde_json::Value = serde_json::from_str(s).unwrap(); let _req = serde_json::from_value::(value).unwrap(); } #[test] fn test_remove_pool_transactions() { let s = r#"{"method": "anvil_removePoolTransactions", "params":["0x364d6D0333432C3Ac016Ca832fb8594A8cE43Ca6"]}"#; let value: serde_json::Value = serde_json::from_str(s).unwrap(); let _req = serde_json::from_value::(value).unwrap(); } #[test] fn test_serde_anvil_reorg() { // TransactionData::JSON let s = r#" { "method": "anvil_reorg", "params": [ 5, [ [ { "from": "0x976EA74026E726554dB657fA54763abd0C3a0aa9", "to": "0x1199bc69f16FDD6690DC40339EC445FaE1b6DD11", "value": 100 }, 1 ], [ { "from": "0x976EA74026E726554dB657fA54763abd0C3a0aa9", "to": "0x1199bc69f16FDD6690DC40339EC445FaE1b6DD11", "value": 200 }, 2 ] ] ] } "#; let value: serde_json::Value = serde_json::from_str(s).unwrap(); let _req = serde_json::from_value::(value).unwrap(); // TransactionData::Raw let s = r#" { "method": "anvil_reorg", "params": [ 5, [ [ "0x19d55c67e1ba8f1bbdfed75f8ad524ebf087e4ecb848a2d19881d7a5e3d2c54e1732cb1b462da3b3fdb05bdf4c4d3c8e3c9fcebdc2ab5fa5d59a3f752888f27e1b", 1 ] ] ] } "#; let value: serde_json::Value = serde_json::from_str(s).unwrap(); let _req = serde_json::from_value::(value).unwrap(); // TransactionData::Raw and TransactionData::JSON let s = r#" { "method": "anvil_reorg", "params": [ 5, [ [ "0x19d55c67e1ba8f1bbdfed75f8ad524ebf087e4ecb848a2d19881d7a5e3d2c54e1732cb1b462da3b3fdb05bdf4c4d3c8e3c9fcebdc2ab5fa5d59a3f752888f27e1b", 1 ], [ { "from": "0x976EA74026E726554dB657fA54763abd0C3a0aa9", "to": "0x1199bc69f16FDD6690DC40339EC445FaE1b6DD11", "value": 200 }, 2 ] ] ] } "#; let value: serde_json::Value = serde_json::from_str(s).unwrap(); let _req = serde_json::from_value::(value).unwrap(); } } ================================================ FILE: crates/anvil/core/src/eth/serde_helpers.rs ================================================ //! custom serde helper functions pub mod sequence { use serde::{ Deserialize, Deserializer, Serialize, Serializer, de::DeserializeOwned, ser::SerializeSeq, }; pub fn serialize(val: &T, s: S) -> Result where S: Serializer, T: Serialize, { let mut seq = s.serialize_seq(Some(1))?; seq.serialize_element(val)?; seq.end() } pub fn deserialize<'de, T, D>(d: D) -> Result where D: Deserializer<'de>, T: DeserializeOwned, { let mut seq = Vec::::deserialize(d)?; if seq.len() != 1 { return Err(serde::de::Error::custom(format!( "expected params sequence with length 1 but got {}", seq.len() ))); } Ok(seq.remove(0)) } } /// A module that deserializes `[]` optionally pub mod empty_params { use serde::{Deserialize, Deserializer}; pub fn deserialize<'de, D>(d: D) -> Result<(), D::Error> where D: Deserializer<'de>, { let seq = Option::>::deserialize(d)?.unwrap_or_default(); if !seq.is_empty() { return Err(serde::de::Error::custom(format!( "expected params sequence with length 0 but got {}", seq.len() ))); } Ok(()) } } /// A module that deserializes either a BlockNumberOrTag, or a simple number. pub mod lenient_block_number { pub use alloy_eips::eip1898::LenientBlockNumberOrTag; use alloy_rpc_types::BlockNumberOrTag; use serde::{Deserialize, Deserializer}; /// deserializes either a BlockNumberOrTag, or a simple number. pub use alloy_eips::eip1898::lenient_block_number_or_tag::deserialize as lenient_block_number; /// Same as `lenient_block_number` but requires to be `[num; 1]` pub fn lenient_block_number_seq<'de, D>(deserializer: D) -> Result where D: Deserializer<'de>, { let num = <[LenientBlockNumberOrTag; 1]>::deserialize(deserializer)?[0].into(); Ok(num) } } ================================================ FILE: crates/anvil/core/src/eth/subscription.rs ================================================ //! Subscription types use alloy_primitives::hex; use rand::{Rng, distr::Alphanumeric, rng}; use std::fmt; /// Unique subscription id #[derive(Clone, PartialEq, Eq, Hash, serde::Deserialize, serde::Serialize)] #[serde(untagged)] pub enum SubscriptionId { /// numerical sub id Number(u64), /// string sub id, a hash for example String(String), } impl SubscriptionId { /// Generates a new random hex identifier pub fn random_hex() -> Self { Self::String(hex_id()) } } impl fmt::Display for SubscriptionId { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::Number(num) => num.fmt(f), Self::String(s) => s.fmt(f), } } } impl fmt::Debug for SubscriptionId { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::Number(num) => num.fmt(f), Self::String(s) => s.fmt(f), } } } /// Provides random hex identifier with a certain length #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] pub struct HexIdProvider { len: usize, } impl HexIdProvider { /// Generates a random hex encoded Id pub fn generate(&self) -> String { let id: String = (&mut rng()).sample_iter(Alphanumeric).map(char::from).take(self.len).collect(); let out = hex::encode(id); format!("0x{out}") } } impl Default for HexIdProvider { fn default() -> Self { Self { len: 16 } } } /// Returns a new random hex identifier pub fn hex_id() -> String { HexIdProvider::default().generate() } ================================================ FILE: crates/anvil/core/src/eth/transaction/mod.rs ================================================ //! Transaction related types use alloy_consensus::{ Transaction, Typed2718, crypto::RecoveryError, transaction::{SignerRecoverable, TxHashRef}, }; use alloy_eips::eip2718::Encodable2718; use alloy_primitives::{Address, B256, Bytes, TxHash}; use alloy_rlp::{Decodable, Encodable}; use bytes::BufMut; use foundry_evm::traces::CallTraceNode; use foundry_primitives::FoundryTxEnvelope; use revm::interpreter::InstructionResult; use serde::{Deserialize, Serialize}; use std::ops::Deref; /// A wrapper for a transaction envelope that allows impersonating accounts. /// /// This is a helper that carries the `impersonated` sender so that the right hash /// can be created. #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] pub struct MaybeImpersonatedTransaction { transaction: T, impersonated_sender: Option
, } impl Typed2718 for MaybeImpersonatedTransaction { fn ty(&self) -> u8 { self.transaction.ty() } } impl MaybeImpersonatedTransaction { /// Creates a new wrapper for the given transaction pub fn new(transaction: T) -> Self { Self { transaction, impersonated_sender: None } } /// Creates a new impersonated transaction wrapper using the given sender pub fn impersonated(transaction: T, impersonated_sender: Address) -> Self { Self { transaction, impersonated_sender: Some(impersonated_sender) } } /// Returns whether the transaction is impersonated pub fn is_impersonated(&self) -> bool { self.impersonated_sender.is_some() } /// Returns the inner transaction. pub fn into_inner(self) -> T { self.transaction } } impl MaybeImpersonatedTransaction { /// Recovers the Ethereum address which was used to sign the transaction. pub fn recover(&self) -> Result { if let Some(sender) = self.impersonated_sender { return Ok(sender); } self.transaction.recover_signer() } /// Returns the hash of the transaction. /// /// If the transaction is impersonated, returns a unique hash derived by appending the /// impersonated sender address to the encoded transaction before hashing. pub fn hash(&self) -> B256 { if let Some(sender) = self.impersonated_sender { let mut buffer = Vec::new(); self.transaction.encode(&mut buffer); buffer.extend_from_slice(sender.as_ref()); return B256::from_slice(alloy_primitives::utils::keccak256(&buffer).as_slice()); } *self.transaction.tx_hash() } } impl Encodable2718 for MaybeImpersonatedTransaction { fn encode_2718_len(&self) -> usize { self.transaction.encode_2718_len() } fn encode_2718(&self, out: &mut dyn BufMut) { self.transaction.encode_2718(out) } } impl Encodable for MaybeImpersonatedTransaction { fn encode(&self, out: &mut dyn bytes::BufMut) { self.transaction.encode(out) } } impl From> for FoundryTxEnvelope { fn from(value: MaybeImpersonatedTransaction) -> Self { value.transaction } } impl From for MaybeImpersonatedTransaction { fn from(value: FoundryTxEnvelope) -> Self { Self::new(value) } } impl Decodable for MaybeImpersonatedTransaction { fn decode(buf: &mut &[u8]) -> alloy_rlp::Result { T::decode(buf).map(Self::new) } } impl AsRef for MaybeImpersonatedTransaction { fn as_ref(&self) -> &T { &self.transaction } } impl Deref for MaybeImpersonatedTransaction { type Target = T; fn deref(&self) -> &Self::Target { &self.transaction } } /// Queued transaction #[derive(Clone, Debug, PartialEq, Eq)] pub struct PendingTransaction { /// The actual transaction pub transaction: MaybeImpersonatedTransaction, /// the recovered sender of this transaction sender: Address, /// hash of `transaction`, so it can easily be reused with encoding and hashing again hash: TxHash, } impl PendingTransaction { pub fn hash(&self) -> &TxHash { &self.hash } pub fn sender(&self) -> &Address { &self.sender } } impl PendingTransaction { pub fn new(transaction: T) -> Result { let transaction = MaybeImpersonatedTransaction::new(transaction); let sender = transaction.recover()?; let hash = transaction.hash(); Ok(Self { transaction, sender, hash }) } pub fn with_impersonated(transaction: T, sender: Address) -> Self { let transaction = MaybeImpersonatedTransaction::impersonated(transaction, sender); let hash = transaction.hash(); Self { transaction, sender, hash } } /// Converts a [`MaybeImpersonatedTransaction`] into a [`PendingTransaction`]. pub fn from_maybe_impersonated( transaction: MaybeImpersonatedTransaction, ) -> Result { if let Some(impersonated) = transaction.impersonated_sender { Ok(Self::with_impersonated(transaction.transaction, impersonated)) } else { Self::new(transaction.transaction) } } } impl PendingTransaction { pub fn nonce(&self) -> u64 { self.transaction.nonce() } } /// Represents all relevant information of an executed transaction #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] pub struct TransactionInfo { pub transaction_hash: B256, pub transaction_index: u64, pub from: Address, pub to: Option
, pub contract_address: Option
, pub traces: Vec, pub exit: InstructionResult, pub out: Option, pub nonce: u64, pub gas_used: u64, } ================================================ FILE: crates/anvil/core/src/eth/wallet.rs ================================================ pub use alloy_eip5792::*; #[derive(Debug, thiserror::Error)] pub enum WalletError { /// The transaction value is not 0. /// /// The value should be 0 to prevent draining the sequencer. #[error("transaction value must be zero for delegated transactions")] ValueNotZero, /// The from field is set on the transaction. /// /// Requests with the from field are rejected, since it is implied that it will always be the /// sequencer. #[error("transaction 'from' field should not be set for delegated transactions")] FromSet, /// The nonce field is set on the transaction. /// /// Requests with the nonce field set are rejected, as this is managed by the sequencer. #[error("transaction nonce should not be set for delegated transactions")] NonceSet, /// An authorization item was invalid. /// /// The item is invalid if it tries to delegate an account to a contract that is not /// whitelisted. #[error("invalid authorization address: contract is not whitelisted for delegation")] InvalidAuthorization, /// The to field of the transaction was invalid. /// /// The destination is invalid if: /// /// - There is no bytecode at the destination, or /// - The bytecode is not an EIP-7702 delegation designator, or /// - The delegation designator points to a contract that is not whitelisted #[error("transaction destination is not a valid delegated account")] IllegalDestination, /// The transaction request was invalid. /// /// This is likely an internal error, as most of the request is built by the sequencer. #[error("invalid transaction request format")] InvalidTransactionRequest, /// An internal error occurred. #[error("internal server error occurred")] InternalError, } ================================================ FILE: crates/anvil/core/src/lib.rs ================================================ //! # anvil-core //! //! Core Ethereum types for Anvil. #![cfg_attr(not(test), warn(unused_crate_dependencies))] #![cfg_attr(docsrs, feature(doc_cfg))] /// Various Ethereum types pub mod eth; /// Additional useful types pub mod types; ================================================ FILE: crates/anvil/core/src/types.rs ================================================ use alloy_primitives::Bytes; use alloy_rpc_types::TransactionRequest; use serde::Deserialize; /// Represents the options used in `anvil_reorg` #[derive(Debug, Clone, Deserialize)] pub struct ReorgOptions { // The depth of the reorg pub depth: u64, // List of transaction requests and blocks pairs to be mined into the new chain pub tx_block_pairs: Vec<(TransactionData, u64)>, } #[derive(Debug, Clone, Deserialize)] #[serde(untagged)] #[expect(clippy::large_enum_variant)] pub enum TransactionData { JSON(TransactionRequest), Raw(Bytes), } ================================================ FILE: crates/anvil/rpc/Cargo.toml ================================================ [package] name = "anvil-rpc" version.workspace = true edition.workspace = true rust-version.workspace = true authors.workspace = true license.workspace = true homepage.workspace = true repository.workspace = true [lints] workspace = true [dependencies] serde.workspace = true serde_json.workspace = true ================================================ FILE: crates/anvil/rpc/src/error.rs ================================================ //! JSON-RPC error bindings use serde::{Deserialize, Deserializer, Serialize, Serializer}; use std::{borrow::Cow, fmt}; /// Represents a JSON-RPC error #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] #[serde(deny_unknown_fields)] pub struct RpcError { pub code: ErrorCode, /// error message pub message: Cow<'static, str>, #[serde(skip_serializing_if = "Option::is_none")] pub data: Option, } impl RpcError { /// New [`RpcError`] with the given [`ErrorCode`]. pub const fn new(code: ErrorCode) -> Self { Self { message: Cow::Borrowed(code.message()), code, data: None } } /// Creates a new `ParseError` error. pub const fn parse_error() -> Self { Self::new(ErrorCode::ParseError) } /// Creates a new `MethodNotFound` error. pub const fn method_not_found() -> Self { Self::new(ErrorCode::MethodNotFound) } /// Creates a new `InvalidRequest` error. pub const fn invalid_request() -> Self { Self::new(ErrorCode::InvalidRequest) } /// Creates a new `InternalError` error. pub const fn internal_error() -> Self { Self::new(ErrorCode::InternalError) } /// Creates a new `InvalidParams` error. pub fn invalid_params(message: M) -> Self where M: Into, { Self { code: ErrorCode::InvalidParams, message: message.into().into(), data: None } } /// Creates a new `InternalError` error with a message. pub fn internal_error_with(message: M) -> Self where M: Into, { Self { code: ErrorCode::InternalError, message: message.into().into(), data: None } } /// Creates a new RPC error for when a transaction was rejected. pub fn transaction_rejected(message: M) -> Self where M: Into, { Self { code: ErrorCode::TransactionRejected, message: message.into().into(), data: None } } } impl fmt::Display for RpcError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}: {}", self.code.message(), self.message) } } /// List of JSON-RPC error codes #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum ErrorCode { /// Server received Invalid JSON. /// server side error while parsing JSON ParseError, /// send invalid request object. InvalidRequest, /// method does not exist or valid MethodNotFound, /// invalid method parameter. InvalidParams, /// internal call error InternalError, /// Failed to send transaction, See also TransactionRejected, /// Custom geth error code, ExecutionError, /// Used for server specific errors. ServerError(i64), } impl ErrorCode { /// Returns the error code as `i64` pub fn code(&self) -> i64 { match *self { Self::ParseError => -32700, Self::InvalidRequest => -32600, Self::MethodNotFound => -32601, Self::InvalidParams => -32602, Self::InternalError => -32603, Self::TransactionRejected => -32003, Self::ExecutionError => 3, Self::ServerError(c) => c, } } /// Returns the message associated with the error pub const fn message(&self) -> &'static str { match *self { Self::ParseError => "Parse error", Self::InvalidRequest => "Invalid request", Self::MethodNotFound => "Method not found", Self::InvalidParams => "Invalid params", Self::InternalError => "Internal error", Self::TransactionRejected => "Transaction rejected", Self::ServerError(_) => "Server error", Self::ExecutionError => "Execution error", } } } impl Serialize for ErrorCode { fn serialize(&self, serializer: S) -> Result where S: Serializer, { serializer.serialize_i64(self.code()) } } impl<'a> Deserialize<'a> for ErrorCode { fn deserialize(deserializer: D) -> Result where D: Deserializer<'a>, { i64::deserialize(deserializer).map(Into::into) } } impl From for ErrorCode { fn from(code: i64) -> Self { match code { -32700 => Self::ParseError, -32600 => Self::InvalidRequest, -32601 => Self::MethodNotFound, -32602 => Self::InvalidParams, -32603 => Self::InternalError, -32003 => Self::TransactionRejected, 3 => Self::ExecutionError, _ => Self::ServerError(code), } } } ================================================ FILE: crates/anvil/rpc/src/lib.rs ================================================ //! # anvil-rpc //! //! JSON-RPC types. #![cfg_attr(not(test), warn(unused_crate_dependencies))] #![cfg_attr(docsrs, feature(doc_cfg))] /// JSON-RPC request bindings pub mod request; /// JSON-RPC response bindings pub mod response; /// JSON-RPC error bindings pub mod error; ================================================ FILE: crates/anvil/rpc/src/request.rs ================================================ use serde::{Deserialize, Serialize}; use std::fmt; /// A JSON-RPC request object, a method call #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] #[serde(deny_unknown_fields)] pub struct RpcMethodCall { /// The version of the protocol pub jsonrpc: Version, /// The name of the method to execute pub method: String, /// An array or object containing the parameters to be passed to the function. #[serde(default = "no_params")] pub params: RequestParams, /// The identifier for this request issued by the client, /// An [Id] must be a String, null or a number. /// If missing it's considered a notification in [Version::V2] pub id: Id, } impl RpcMethodCall { pub fn id(&self) -> Id { self.id.clone() } } /// Represents a JSON-RPC request which is considered a notification (missing [Id] optional /// [Version]) #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] #[serde(deny_unknown_fields)] pub struct RpcNotification { pub jsonrpc: Option, pub method: String, #[serde(default = "no_params")] pub params: RequestParams, } /// Representation of a single JSON-RPC call #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] #[serde(untagged)] pub enum RpcCall { /// the RPC method to invoke MethodCall(RpcMethodCall), /// A notification (no [Id] provided) Notification(RpcNotification), /// Invalid call Invalid { /// id or [Id::Null] #[serde(default = "null_id")] id: Id, }, } /// Represents a JSON-RPC request. #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] #[serde(deny_unknown_fields)] #[serde(untagged)] pub enum Request { /// single json rpc request [RpcCall] Single(RpcCall), /// batch of several requests Batch(Vec), } /// Request parameters #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] #[serde(untagged, deny_unknown_fields)] pub enum RequestParams { /// no parameters provided None, /// An array of JSON values Array(Vec), /// a map of JSON values Object(serde_json::Map), } impl From for serde_json::Value { fn from(params: RequestParams) -> Self { match params { RequestParams::None => Self::Null, RequestParams::Array(arr) => arr.into(), RequestParams::Object(obj) => obj.into(), } } } fn no_params() -> RequestParams { RequestParams::None } /// Represents the version of the RPC protocol #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] pub enum Version { #[serde(rename = "2.0")] V2, } #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] #[serde(untagged)] pub enum Id { String(String), Number(i64), Null, } impl fmt::Display for Id { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::String(s) => s.fmt(f), Self::Number(n) => n.fmt(f), Self::Null => f.write_str("null"), } } } fn null_id() -> Id { Id::Null } #[cfg(test)] mod tests { use super::*; #[test] fn can_serialize_batch() { let batch = Request::Batch(vec![ RpcCall::MethodCall(RpcMethodCall { jsonrpc: Version::V2, method: "eth_method".to_owned(), params: RequestParams::Array(vec![ serde_json::Value::from(999), serde_json::Value::from(1337), ]), id: Id::Number(1), }), RpcCall::Notification(RpcNotification { jsonrpc: Some(Version::V2), method: "eth_method".to_owned(), params: RequestParams::Array(vec![serde_json::Value::from(999)]), }), ]); let obj = serde_json::to_string(&batch).unwrap(); assert_eq!( obj, r#"[{"jsonrpc":"2.0","method":"eth_method","params":[999,1337],"id":1},{"jsonrpc":"2.0","method":"eth_method","params":[999]}]"# ); } #[test] fn can_deserialize_batch() { let s = r#"[{}, {"jsonrpc": "2.0", "method": "eth_call", "params": [1337,420], "id": 1},{"jsonrpc": "2.0", "method": "notify", "params": [999]}]"#; let obj: Request = serde_json::from_str(s).unwrap(); assert_eq!( obj, Request::Batch(vec![ RpcCall::Invalid { id: Id::Null }, RpcCall::MethodCall(RpcMethodCall { jsonrpc: Version::V2, method: "eth_call".to_owned(), params: RequestParams::Array(vec![ serde_json::Value::from(1337), serde_json::Value::from(420) ]), id: Id::Number(1) }), RpcCall::Notification(RpcNotification { jsonrpc: Some(Version::V2), method: "notify".to_owned(), params: RequestParams::Array(vec![serde_json::Value::from(999)]) }) ]) ) } #[test] fn can_serialize_method() { let m = RpcMethodCall { jsonrpc: Version::V2, method: "eth_method".to_owned(), params: RequestParams::Array(vec![ serde_json::Value::from(999), serde_json::Value::from(1337), ]), id: Id::Number(1), }; let obj = serde_json::to_string(&m).unwrap(); assert_eq!(obj, r#"{"jsonrpc":"2.0","method":"eth_method","params":[999,1337],"id":1}"#); } #[test] fn can_serialize_call_notification() { let n = RpcCall::Notification(RpcNotification { jsonrpc: Some(Version::V2), method: "eth_method".to_owned(), params: RequestParams::Array(vec![serde_json::Value::from(999)]), }); let obj = serde_json::to_string(&n).unwrap(); assert_eq!(obj, r#"{"jsonrpc":"2.0","method":"eth_method","params":[999]}"#); } #[test] fn can_serialize_notification() { let n = RpcNotification { jsonrpc: Some(Version::V2), method: "eth_method".to_owned(), params: RequestParams::Array(vec![ serde_json::Value::from(999), serde_json::Value::from(1337), ]), }; let obj = serde_json::to_string(&n).unwrap(); assert_eq!(obj, r#"{"jsonrpc":"2.0","method":"eth_method","params":[999,1337]}"#); } #[test] fn can_deserialize_notification() { let s = r#"{"jsonrpc": "2.0", "method": "eth_method", "params": [999,1337]}"#; let obj: RpcNotification = serde_json::from_str(s).unwrap(); assert_eq!( obj, RpcNotification { jsonrpc: Some(Version::V2), method: "eth_method".to_owned(), params: RequestParams::Array(vec![ serde_json::Value::from(999), serde_json::Value::from(1337) ]) } ); let s = r#"{"jsonrpc": "2.0", "method": "foobar"}"#; let obj: RpcNotification = serde_json::from_str(s).unwrap(); assert_eq!( obj, RpcNotification { jsonrpc: Some(Version::V2), method: "foobar".to_owned(), params: RequestParams::None, } ); let s = r#"{"jsonrpc": "2.0", "method": "eth_method", "params": [999,1337], "id": 1}"#; let obj: Result = serde_json::from_str(s); assert!(obj.is_err()); } #[test] fn can_deserialize_call() { let s = r#"{"jsonrpc": "2.0", "method": "eth_method", "params": [999]}"#; let obj: RpcCall = serde_json::from_str(s).unwrap(); assert_eq!( obj, RpcCall::Notification(RpcNotification { jsonrpc: Some(Version::V2), method: "eth_method".to_owned(), params: RequestParams::Array(vec![serde_json::Value::from(999)]) }) ); let s = r#"{"jsonrpc": "2.0", "method": "eth_method", "params": [999], "id": 1}"#; let obj: RpcCall = serde_json::from_str(s).unwrap(); assert_eq!( obj, RpcCall::MethodCall(RpcMethodCall { jsonrpc: Version::V2, method: "eth_method".to_owned(), params: RequestParams::Array(vec![serde_json::Value::from(999)]), id: Id::Number(1) }) ); let s = r#"{"jsonrpc": "2.0", "method": "eth_method", "params": [], "id": 1}"#; let obj: RpcCall = serde_json::from_str(s).unwrap(); assert_eq!( obj, RpcCall::MethodCall(RpcMethodCall { jsonrpc: Version::V2, method: "eth_method".to_owned(), params: RequestParams::Array(vec![]), id: Id::Number(1) }) ); let s = r#"{"jsonrpc": "2.0", "method": "eth_method", "params": null, "id": 1}"#; let obj: RpcCall = serde_json::from_str(s).unwrap(); assert_eq!( obj, RpcCall::MethodCall(RpcMethodCall { jsonrpc: Version::V2, method: "eth_method".to_owned(), params: RequestParams::None, id: Id::Number(1) }) ); let s = r#"{"jsonrpc": "2.0", "method": "eth_method", "id": 1}"#; let obj: RpcCall = serde_json::from_str(s).unwrap(); assert_eq!( obj, RpcCall::MethodCall(RpcMethodCall { jsonrpc: Version::V2, method: "eth_method".to_owned(), params: RequestParams::None, id: Id::Number(1) }) ); } } ================================================ FILE: crates/anvil/rpc/src/response.rs ================================================ use crate::{ error::RpcError, request::{Id, Version}, }; use serde::{Deserialize, Serialize}; /// Response of a _single_ rpc call #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] #[serde(deny_unknown_fields)] pub struct RpcResponse { // JSON RPC version jsonrpc: Version, id: Id, #[serde(flatten)] result: ResponseResult, } impl From for RpcResponse { fn from(e: RpcError) -> Self { Self { jsonrpc: Version::V2, id: Id::Null, result: ResponseResult::Error(e) } } } impl RpcResponse { pub fn new(id: Id, content: impl Into) -> Self { Self { jsonrpc: Version::V2, id, result: content.into() } } pub fn invalid_request(id: Id) -> Self { Self::new(id, RpcError::invalid_request()) } } /// Represents the result of a call either success or error #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] #[serde(deny_unknown_fields)] pub enum ResponseResult { #[serde(rename = "result")] Success(serde_json::Value), #[serde(rename = "error")] Error(RpcError), } impl ResponseResult { pub fn success(content: S) -> Self where S: Serialize + 'static, { Self::Success(serde_json::to_value(&content).unwrap()) } pub fn error(error: RpcError) -> Self { Self::Error(error) } } impl From for ResponseResult { fn from(err: RpcError) -> Self { Self::error(err) } } /// Synchronous response #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] #[serde(deny_unknown_fields)] #[serde(untagged)] pub enum Response { /// single json rpc response Single(RpcResponse), /// batch of several responses Batch(Vec), } impl Response { /// Creates new [`Response`] with the given [`RpcError`]. pub fn error(error: RpcError) -> Self { RpcResponse::new(Id::Null, ResponseResult::Error(error)).into() } } impl From for Response { fn from(err: RpcError) -> Self { Self::error(err) } } impl From for Response { fn from(resp: RpcResponse) -> Self { Self::Single(resp) } } ================================================ FILE: crates/anvil/server/Cargo.toml ================================================ [package] name = "anvil-server" description = "Customizable RPC server" version.workspace = true edition.workspace = true rust-version.workspace = true authors.workspace = true license.workspace = true homepage.workspace = true repository.workspace = true [lints] workspace = true [dependencies] anvil-rpc = { path = "../rpc" } # axum related axum = { workspace = true, features = ["ws"] } tower-http = { workspace = true, features = ["trace", "cors"] } # tracing tracing.workspace = true # async parking_lot.workspace = true futures.workspace = true # ipc interprocess = { version = "2", optional = true, features = ["tokio"] } bytes = { workspace = true, optional = true } tokio-util = { version = "0.7", features = ["codec"], optional = true } # misc serde_json.workspace = true serde.workspace = true async-trait.workspace = true thiserror.workspace = true clap = { version = "4", features = ["derive", "env"], optional = true } pin-project = "1" [features] default = ["ipc"] ipc = ["dep:interprocess", "dep:bytes", "dep:tokio-util"] ================================================ FILE: crates/anvil/server/src/config.rs ================================================ use crate::HeaderValue; use serde::{Deserialize, Deserializer, Serialize, Serializer}; use std::str::FromStr; /// Additional server options. #[derive(Clone, Debug, Serialize, Deserialize)] #[cfg_attr(feature = "clap", derive(clap::Parser), command(next_help_heading = "Server options"))] pub struct ServerConfig { /// The cors `allow_origin` header #[cfg_attr(feature = "clap", arg(long, default_value = "*"))] pub allow_origin: HeaderValueWrapper, /// Disable CORS. #[cfg_attr(feature = "clap", arg(long, conflicts_with = "allow_origin"))] pub no_cors: bool, /// Disable the default request body size limit. At time of writing the default limit is 2MB. #[cfg_attr(feature = "clap", arg(long))] pub no_request_size_limit: bool, } impl ServerConfig { /// Sets the "allow origin" header for CORS. pub fn with_allow_origin(mut self, allow_origin: impl Into) -> Self { self.allow_origin = allow_origin.into(); self } /// Whether to enable CORS. pub fn set_cors(mut self, cors: bool) -> Self { self.no_cors = !cors; self } } impl Default for ServerConfig { fn default() -> Self { Self { allow_origin: "*".parse::().unwrap().into(), no_cors: false, no_request_size_limit: false, } } } #[derive(Clone, Debug)] pub struct HeaderValueWrapper(pub HeaderValue); impl FromStr for HeaderValueWrapper { type Err = ::Err; fn from_str(s: &str) -> Result { Ok(Self(s.parse()?)) } } impl Serialize for HeaderValueWrapper { fn serialize(&self, serializer: S) -> Result where S: Serializer, { serializer.serialize_str(self.0.to_str().map_err(serde::ser::Error::custom)?) } } impl<'de> Deserialize<'de> for HeaderValueWrapper { fn deserialize(deserializer: D) -> Result where D: Deserializer<'de>, { let s = String::deserialize(deserializer)?; Ok(Self(s.parse().map_err(serde::de::Error::custom)?)) } } impl std::ops::Deref for HeaderValueWrapper { type Target = HeaderValue; fn deref(&self) -> &Self::Target { &self.0 } } impl From for HeaderValue { fn from(wrapper: HeaderValueWrapper) -> Self { wrapper.0 } } impl From for HeaderValueWrapper { fn from(header: HeaderValue) -> Self { Self(header) } } ================================================ FILE: crates/anvil/server/src/error.rs ================================================ //! Error variants used to unify different connection streams /// An error that can occur when reading an incoming request #[derive(Debug, thiserror::Error)] pub enum RequestError { #[error(transparent)] Axum(#[from] axum::Error), #[error(transparent)] Serde(#[from] serde_json::Error), #[error(transparent)] Io(#[from] std::io::Error), #[error("Disconnect")] Disconnect, } ================================================ FILE: crates/anvil/server/src/handler.rs ================================================ use crate::RpcHandler; use anvil_rpc::{ error::RpcError, request::{Request, RpcCall}, response::{Response, RpcResponse}, }; use axum::{ Json, extract::{State, rejection::JsonRejection}, }; use futures::{FutureExt, future}; /// Handles incoming JSON-RPC Request. // NOTE: `handler` must come first because the `request` extractor consumes the request body. pub async fn handle( State((handler, _)): State<(Http, Ws)>, request: Result, JsonRejection>, ) -> Json { Json(match request { Ok(Json(req)) => handle_request(req, handler) .await .unwrap_or_else(|| Response::error(RpcError::invalid_request())), Err(err) => { warn!(target: "rpc", ?err, "invalid request"); Response::error(RpcError::invalid_request()) } }) } /// Handle the JSON-RPC [Request] /// /// This will try to deserialize the payload into the request type of the handler and if successful /// invoke the handler pub async fn handle_request( req: Request, handler: Handler, ) -> Option { /// processes batch calls fn responses_as_batch(outs: Vec>) -> Option { let batch: Vec<_> = outs.into_iter().flatten().collect(); (!batch.is_empty()).then_some(Response::Batch(batch)) } match req { Request::Single(call) => handle_call(call, handler).await.map(Response::Single), Request::Batch(calls) => { future::join_all(calls.into_iter().map(move |call| handle_call(call, handler.clone()))) .map(responses_as_batch) .await } } } /// handle a single RPC method call async fn handle_call(call: RpcCall, handler: Handler) -> Option { match call { RpcCall::MethodCall(call) => { trace!(target: "rpc", id = ?call.id , method = ?call.method, "handling call"); Some(handler.on_call(call).await) } RpcCall::Notification(notification) => { trace!(target: "rpc", method = ?notification.method, "received rpc notification"); None } RpcCall::Invalid { id } => { warn!(target: "rpc", ?id, "invalid rpc call"); Some(RpcResponse::invalid_request(id)) } } } ================================================ FILE: crates/anvil/server/src/ipc.rs ================================================ //! IPC handling use crate::{PubSubRpcHandler, error::RequestError, pubsub::PubSubConnection}; use anvil_rpc::request::Request; use bytes::{BufMut, BytesMut}; use futures::{Sink, Stream, StreamExt, ready}; use interprocess::local_socket::{self as ls, tokio::prelude::*}; use std::{ io, pin::Pin, task::{Context, Poll}, }; /// An IPC connection for anvil /// /// A Future that listens for incoming connections and spawns new connections pub struct IpcEndpoint { /// the handler for the websocket connection handler: Handler, /// The path to the socket path: String, } impl IpcEndpoint { /// Creates a new endpoint with the given handler pub fn new(handler: Handler, path: String) -> Self { Self { handler, path } } /// Returns a stream of incoming connection handlers. /// /// This establishes the IPC endpoint, converts the incoming connections into handled /// connections. #[instrument(target = "ipc", skip_all)] pub fn incoming(self) -> io::Result>> { let Self { handler, path } = self; trace!(%path, "starting IPC server"); if cfg!(unix) { // ensure the file does not exist if std::fs::remove_file(&path).is_ok() { warn!(%path, "removed existing file"); } } let name = to_name(path.as_ref())?; let listener = ls::ListenerOptions::new().name(name).create_tokio()?; let connections = futures::stream::unfold(listener, |listener| async move { let conn = listener.accept().await; Some((conn, listener)) }); trace!("established connection listener"); Ok(connections.filter_map(move |stream| { let handler = handler.clone(); async move { match stream { Ok(stream) => { trace!("successful incoming IPC connection"); let framed = tokio_util::codec::Decoder::framed(JsonRpcCodec, stream); Some(PubSubConnection::new(IpcConn(framed), handler)) } Err(err) => { trace!(%err, "unsuccessful incoming IPC connection"); None } } } })) } } #[pin_project::pin_project] struct IpcConn(#[pin] T); impl Stream for IpcConn where T: Stream>, { type Item = Result, RequestError>; fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { fn on_request(msg: io::Result) -> Result, RequestError> { let text = msg?; Ok(Some(serde_json::from_str(&text)?)) } match ready!(self.project().0.poll_next(cx)) { Some(req) => Poll::Ready(Some(on_request(req))), _ => Poll::Ready(None), } } } impl Sink for IpcConn where T: Sink, { type Error = io::Error; fn poll_ready(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { // NOTE: we always flush here this prevents any backpressure buffer in the underlying // `Framed` impl that would cause stalled requests self.project().0.poll_flush(cx) } fn start_send(self: Pin<&mut Self>, item: String) -> Result<(), Self::Error> { self.project().0.start_send(item) } fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { self.project().0.poll_flush(cx) } fn poll_close(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { self.project().0.poll_close(cx) } } struct JsonRpcCodec; // Adapted from impl tokio_util::codec::Decoder for JsonRpcCodec { type Item = String; type Error = io::Error; fn decode(&mut self, buf: &mut BytesMut) -> io::Result> { const fn is_whitespace(byte: u8) -> bool { matches!(byte, 0x0D | 0x0A | 0x20 | 0x09) } let mut depth = 0; let mut in_str = false; let mut is_escaped = false; let mut start_idx = 0; let mut whitespaces = 0; for idx in 0..buf.as_ref().len() { let byte = buf.as_ref()[idx]; if (byte == b'{' || byte == b'[') && !in_str { if depth == 0 { start_idx = idx; } depth += 1; } else if (byte == b'}' || byte == b']') && !in_str { depth -= 1; } else if byte == b'"' && !is_escaped { in_str = !in_str; } else if is_whitespace(byte) { whitespaces += 1; } is_escaped = byte == b'\\' && !is_escaped && in_str; if depth == 0 && idx != start_idx && idx - start_idx + 1 > whitespaces { let bts = buf.split_to(idx + 1); return match String::from_utf8(bts.as_ref().to_vec()) { Ok(val) => Ok(Some(val)), Err(_) => Ok(None), }; } } Ok(None) } } impl tokio_util::codec::Encoder for JsonRpcCodec { type Error = io::Error; fn encode(&mut self, msg: String, buf: &mut BytesMut) -> io::Result<()> { buf.extend_from_slice(msg.as_bytes()); // Add newline character buf.put_u8(b'\n'); Ok(()) } } fn to_name(path: &std::ffi::OsStr) -> io::Result> { if cfg!(windows) && !path.as_encoded_bytes().starts_with(br"\\.\pipe\") { ls::ToNsName::to_ns_name::(path) } else { ls::ToFsName::to_fs_name::(path) } } ================================================ FILE: crates/anvil/server/src/lib.rs ================================================ //! Bootstrap [axum] RPC servers. #![cfg_attr(not(test), warn(unused_crate_dependencies))] #![cfg_attr(docsrs, feature(doc_cfg))] #[macro_use] extern crate tracing; use anvil_rpc::{ error::RpcError, request::RpcMethodCall, response::{ResponseResult, RpcResponse}, }; use axum::{ Router, extract::DefaultBodyLimit, http::{HeaderValue, Method, header}, routing::{MethodRouter, post}, }; use serde::de::DeserializeOwned; use std::fmt; use tower_http::{cors::CorsLayer, trace::TraceLayer}; mod config; pub use config::ServerConfig; mod error; mod handler; mod pubsub; pub use pubsub::{PubSubContext, PubSubRpcHandler}; mod ws; #[cfg(feature = "ipc")] pub mod ipc; /// Configures an [`axum::Router`] that handles JSON-RPC calls via both HTTP and WS. pub fn http_ws_router(config: ServerConfig, http: Http, ws: Ws) -> Router where Http: RpcHandler, Ws: PubSubRpcHandler, { router_inner(config, post(handler::handle).get(ws::handle_ws), (http, ws)) } /// Configures an [`axum::Router`] that handles JSON-RPC calls via HTTP. pub fn http_router(config: ServerConfig, http: Http) -> Router where Http: RpcHandler, { router_inner(config, post(handler::handle), (http, ())) } fn router_inner( config: ServerConfig, root_method_router: MethodRouter, state: S, ) -> Router { let ServerConfig { allow_origin, no_cors, no_request_size_limit } = config; let mut router = Router::new() .route("/", root_method_router) .with_state(state) .layer(TraceLayer::new_for_http()); if !no_cors { // See [`tower_http::cors`](https://docs.rs/tower-http/latest/tower_http/cors/index.html) // for more details. router = router.layer( CorsLayer::new() .allow_origin(allow_origin.0) .allow_headers([header::CONTENT_TYPE]) .allow_methods([Method::GET, Method::POST]), ); } if no_request_size_limit { router = router.layer(DefaultBodyLimit::disable()); } router } /// Helper trait that is used to execute ethereum rpc calls #[async_trait::async_trait] pub trait RpcHandler: Clone + Send + Sync + 'static { /// The request type to expect type Request: DeserializeOwned + Send + Sync + fmt::Debug; /// Invoked when the request was received async fn on_request(&self, request: Self::Request) -> ResponseResult; /// Invoked for every incoming `RpcMethodCall` /// /// This will attempt to deserialize a `{ "method" : "", "params": "" }` message /// into the `Request` type of this handler. If a `Request` instance was deserialized /// successfully, [`Self::on_request`] will be invoked. /// /// **Note**: override this function if the expected `Request` deviates from `{ "method" : /// "", "params": "" }` async fn on_call(&self, call: RpcMethodCall) -> RpcResponse { trace!(target: "rpc", id = ?call.id , method = ?call.method, params = ?call.params, "received method call"); let RpcMethodCall { method, params, id, .. } = call; let params: serde_json::Value = params.into(); let call = serde_json::json!({ "method": &method, "params": params }); match serde_json::from_value::(call) { Ok(req) => { let result = self.on_request(req).await; RpcResponse::new(id, result) } Err(err) => { let err = err.to_string(); if err.contains("unknown variant") { error!(target: "rpc", ?method, "failed to deserialize method due to unknown variant"); RpcResponse::new(id, RpcError::method_not_found()) } else { error!(target: "rpc", ?method, ?err, "failed to deserialize method"); RpcResponse::new(id, RpcError::invalid_params(err)) } } } } } ================================================ FILE: crates/anvil/server/src/pubsub.rs ================================================ use crate::{RpcHandler, error::RequestError, handler::handle_request}; use anvil_rpc::{ error::RpcError, request::Request, response::{Response, ResponseResult}, }; use futures::{FutureExt, Sink, SinkExt, Stream, StreamExt}; use parking_lot::Mutex; use serde::de::DeserializeOwned; use std::{ collections::VecDeque, fmt, hash::Hash, pin::Pin, sync::Arc, task::{Context, Poll}, }; /// The general purpose trait for handling RPC requests and subscriptions #[async_trait::async_trait] pub trait PubSubRpcHandler: Clone + Send + Sync + Unpin + 'static { /// The request type to expect type Request: DeserializeOwned + Send + Sync + fmt::Debug; /// The identifier to use for subscriptions type SubscriptionId: Hash + PartialEq + Eq + Send + Sync + fmt::Debug; /// The subscription type this handle may create type Subscription: Stream + Send + Sync + Unpin; /// Invoked when the request was received async fn on_request(&self, request: Self::Request, cx: PubSubContext) -> ResponseResult; } type Subscriptions = Arc>>; /// Contains additional context and tracks subscriptions pub struct PubSubContext { /// all active subscriptions `id -> Stream` subscriptions: Subscriptions, } impl PubSubContext { /// Adds new active subscription /// /// Returns the previous subscription, if any pub fn add_subscription( &self, id: Handler::SubscriptionId, subscription: Handler::Subscription, ) -> Option { let mut subscriptions = self.subscriptions.lock(); let mut removed = None; if let Some(idx) = subscriptions.iter().position(|(i, _)| id == *i) { trace!(target: "rpc", ?id, "removed subscription"); removed = Some(subscriptions.swap_remove(idx).1); } trace!(target: "rpc", ?id, "added subscription"); subscriptions.push((id, subscription)); removed } /// Removes an existing subscription pub fn remove_subscription( &self, id: &Handler::SubscriptionId, ) -> Option { let mut subscriptions = self.subscriptions.lock(); if let Some(idx) = subscriptions.iter().position(|(i, _)| id == i) { trace!(target: "rpc", ?id, "removed subscription"); return Some(subscriptions.swap_remove(idx).1); } None } } impl Clone for PubSubContext { fn clone(&self) -> Self { Self { subscriptions: Arc::clone(&self.subscriptions) } } } impl Default for PubSubContext { fn default() -> Self { Self { subscriptions: Arc::new(Mutex::new(Vec::new())) } } } /// A compatibility helper type to use common `RpcHandler` functions struct ContextAwareHandler { handler: Handler, context: PubSubContext, } impl Clone for ContextAwareHandler { fn clone(&self) -> Self { Self { handler: self.handler.clone(), context: self.context.clone() } } } #[async_trait::async_trait] impl RpcHandler for ContextAwareHandler { type Request = Handler::Request; async fn on_request(&self, request: Self::Request) -> ResponseResult { self.handler.on_request(request, self.context.clone()).await } } /// Represents a connection to a client via websocket /// /// Contains the state for the entire connection pub struct PubSubConnection { /// the handler for the websocket connection handler: Handler, /// contains all the subscription related context context: PubSubContext, /// The established connection connection: Connection, /// currently in progress requests processing: Vec + Send>>>, /// pending messages to send pending: VecDeque, } impl PubSubConnection { pub fn new(connection: Connection, handler: Handler) -> Self { Self { connection, handler, context: Default::default(), pending: Default::default(), processing: Default::default(), } } /// Returns a compatibility `RpcHandler` fn compat_helper(&self) -> ContextAwareHandler { ContextAwareHandler { handler: self.handler.clone(), context: self.context.clone() } } fn process_request(&mut self, req: serde_json::Result) { let handler = self.compat_helper(); self.processing.push(Box::pin(async move { match req { Ok(req) => handle_request(req, handler) .await .unwrap_or_else(|| Response::error(RpcError::invalid_request())), Err(err) => { error!(target: "rpc", ?err, "invalid request"); Response::error(RpcError::invalid_request()) } } })); } } impl Future for PubSubConnection where Handler: PubSubRpcHandler, Connection: Sink + Stream, RequestError>> + Unpin, >::Error: fmt::Debug, { type Output = (); fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { let pin = self.get_mut(); loop { // drive the websocket while matches!(pin.connection.poll_ready_unpin(cx), Poll::Ready(Ok(()))) { // only start sending if socket is ready if let Some(msg) = pin.pending.pop_front() { if let Err(err) = pin.connection.start_send_unpin(msg) { error!(target: "rpc", ?err, "Failed to send message"); } } else { break; } } // Ensure any pending messages are flushed // this needs to be called manually for tungsenite websocket: if let Poll::Ready(Err(err)) = pin.connection.poll_flush_unpin(cx) { trace!(target: "rpc", ?err, "websocket err"); // close the connection return Poll::Ready(()); } loop { match pin.connection.poll_next_unpin(cx) { Poll::Ready(Some(req)) => match req { Ok(Some(req)) => { pin.process_request(Ok(req)); } Err(err) => match err { RequestError::Axum(err) => { trace!(target: "rpc", ?err, "client disconnected"); return Poll::Ready(()); } RequestError::Io(err) => { trace!(target: "rpc", ?err, "client disconnected"); return Poll::Ready(()); } RequestError::Serde(err) => { pin.process_request(Err(err)); } RequestError::Disconnect => { trace!(target: "rpc", "client disconnected"); return Poll::Ready(()); } }, _ => {} }, Poll::Ready(None) => { trace!(target: "rpc", "socket connection finished"); return Poll::Ready(()); } Poll::Pending => break, } } let mut progress = false; for n in (0..pin.processing.len()).rev() { let mut req = pin.processing.swap_remove(n); #[allow(clippy::collapsible_match)] match req.poll_unpin(cx) { Poll::Ready(resp) => { if let Ok(text) = serde_json::to_string(&resp) { pin.pending.push_back(text); progress = true; } } Poll::Pending => pin.processing.push(req), } } { // process subscription events let mut subscriptions = pin.context.subscriptions.lock(); 'outer: for n in (0..subscriptions.len()).rev() { let (id, mut sub) = subscriptions.swap_remove(n); 'inner: loop { #[allow(clippy::collapsible_match)] match sub.poll_next_unpin(cx) { Poll::Ready(Some(res)) => { if let Ok(text) = serde_json::to_string(&res) { pin.pending.push_back(text); progress = true; } } Poll::Ready(None) => continue 'outer, Poll::Pending => break 'inner, } } subscriptions.push((id, sub)); } } if !progress { return Poll::Pending; } } } } ================================================ FILE: crates/anvil/server/src/ws.rs ================================================ use crate::{PubSubRpcHandler, error::RequestError, pubsub::PubSubConnection}; use anvil_rpc::request::Request; use axum::{ extract::{ State, WebSocketUpgrade, ws::{Message, WebSocket}, }, response::Response, }; use futures::{Sink, Stream, ready}; use std::{ pin::Pin, task::{Context, Poll}, }; /// Handles incoming Websocket upgrade /// /// This is the entrypoint invoked by the axum server for a websocket request pub async fn handle_ws( ws: WebSocketUpgrade, State((_, handler)): State<(Http, Ws)>, ) -> Response { ws.on_upgrade(|socket| PubSubConnection::new(SocketConn(socket), handler)) } #[pin_project::pin_project] struct SocketConn(#[pin] WebSocket); impl Stream for SocketConn { type Item = Result, RequestError>; fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { match ready!(self.project().0.poll_next(cx)) { Some(msg) => Poll::Ready(Some(on_message(msg))), _ => Poll::Ready(None), } } } impl Sink for SocketConn { type Error = axum::Error; fn poll_ready(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { self.project().0.poll_ready(cx) } fn start_send(self: Pin<&mut Self>, item: String) -> Result<(), Self::Error> { self.project().0.start_send(Message::Text(item.into())) } fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { self.project().0.poll_flush(cx) } fn poll_close(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { self.project().0.poll_close(cx) } } fn on_message(msg: Result) -> Result, RequestError> { match msg? { Message::Text(text) => Ok(Some(serde_json::from_str(&text)?)), Message::Binary(data) => { // the binary payload type is the request as-is but as bytes, if this is a valid // `Request` then we can deserialize the Json from the data Vec Ok(Some(serde_json::from_slice(&data)?)) } Message::Close(_) => { trace!(target: "rpc::ws", "ws client disconnected"); Err(RequestError::Disconnect) } _ => Ok(None), } } ================================================ FILE: crates/anvil/src/args.rs ================================================ use crate::opts::{Anvil, AnvilSubcommand}; use clap::{CommandFactory, Parser}; use eyre::Result; use foundry_cli::utils; /// Run the `anvil` command line interface. pub fn run() -> Result<()> { setup()?; foundry_cli::opts::GlobalArgs::check_markdown_help::(); let mut args = Anvil::parse(); args.global.init()?; args.node.evm.resolve_rpc_alias(); run_command(args) } /// Setup the exception handler and other utilities. pub fn setup() -> Result<()> { utils::common_setup(); Ok(()) } /// Run the subcommand. pub fn run_command(args: Anvil) -> Result<()> { if let Some(cmd) = &args.cmd { match cmd { AnvilSubcommand::Completions { shell } => { clap_complete::generate( *shell, &mut Anvil::command(), "anvil", &mut std::io::stdout(), ); } } return Ok(()); } let _ = fdlimit::raise_fd_limit(); args.global.tokio_runtime().block_on(args.node.run()) } #[cfg(test)] mod tests { use super::*; #[test] fn verify_cli() { Anvil::command().debug_assert(); } #[test] fn can_parse_help() { let _: Anvil = Anvil::parse_from(["anvil", "--help"]); } #[test] fn can_parse_short_version() { let _: Anvil = Anvil::parse_from(["anvil", "-V"]); } #[test] fn can_parse_long_version() { let _: Anvil = Anvil::parse_from(["anvil", "--version"]); } #[test] fn can_parse_completions() { let args: Anvil = Anvil::parse_from(["anvil", "completions", "bash"]); assert!(matches!( args.cmd, Some(AnvilSubcommand::Completions { shell: foundry_cli::clap::Shell::ClapCompleteShell(clap_complete::Shell::Bash) }) )); } } ================================================ FILE: crates/anvil/src/cmd.rs ================================================ use crate::{ AccountGenerator, CHAIN_ID, NodeConfig, config::{DEFAULT_MNEMONIC, ForkChoice}, eth::{EthApi, backend::db::SerializableState, pool::transactions::TransactionOrder}, }; use alloy_genesis::Genesis; use alloy_primitives::{B256, U256, utils::Unit}; use alloy_signer_local::coins_bip39::{English, Mnemonic}; use anvil_server::ServerConfig; use clap::Parser; use core::fmt; use foundry_common::shell; use foundry_config::{Chain, Config, FigmentProviders}; use foundry_evm::hardfork::{EthereumHardfork, OpHardfork}; use foundry_evm_networks::NetworkConfigs; use foundry_primitives::FoundryNetwork; use futures::FutureExt; use rand_08::{SeedableRng, rngs::StdRng}; use std::{ net::IpAddr, path::{Path, PathBuf}, pin::Pin, str::FromStr, sync::{ Arc, atomic::{AtomicUsize, Ordering}, }, task::{Context, Poll}, time::Duration, }; use tokio::time::{Instant, Interval}; #[derive(Clone, Debug, Parser)] pub struct NodeArgs { /// Port number to listen on. #[arg(long, short, default_value = "8545", value_name = "NUM")] pub port: u16, /// Number of dev accounts to generate and configure. #[arg(long, short, default_value = "10", value_name = "NUM")] pub accounts: u64, /// The balance of every dev account in Ether. #[arg(long, default_value = "10000", value_name = "NUM")] pub balance: u64, /// The timestamp of the genesis block. #[arg(long, value_name = "NUM")] pub timestamp: Option, /// The number of the genesis block. #[arg(long, value_name = "NUM")] pub number: Option, /// BIP39 mnemonic phrase used for generating accounts. /// Cannot be used if `mnemonic_random` or `mnemonic_seed` are used. #[arg(long, short, conflicts_with_all = &["mnemonic_seed", "mnemonic_random"])] pub mnemonic: Option, /// Automatically generates a BIP39 mnemonic phrase, and derives accounts from it. /// Cannot be used with other `mnemonic` options. /// You can specify the number of words you want in the mnemonic. /// [default: 12] #[arg(long, conflicts_with_all = &["mnemonic", "mnemonic_seed"], default_missing_value = "12", num_args(0..=1))] pub mnemonic_random: Option, /// Generates a BIP39 mnemonic phrase from a given seed /// Cannot be used with other `mnemonic` options. /// /// CAREFUL: This is NOT SAFE and should only be used for testing. /// Never use the private keys generated in production. #[arg(long = "mnemonic-seed-unsafe", conflicts_with_all = &["mnemonic", "mnemonic_random"])] pub mnemonic_seed: Option, /// Sets the derivation path of the child key to be derived. /// /// [default: m/44'/60'/0'/0/] #[arg(long)] pub derivation_path: Option, /// The EVM hardfork to use. /// /// Choose the hardfork by name, e.g. `prague`, `cancun`, `shanghai`, `paris`, `london`, etc... /// [default: latest] #[arg(long)] pub hardfork: Option, /// Block time in seconds for interval mining. #[arg(short, long, visible_alias = "blockTime", value_name = "SECONDS", value_parser = duration_from_secs_f64)] pub block_time: Option, /// Slots in an epoch #[arg(long, value_name = "SLOTS_IN_AN_EPOCH", default_value_t = 32)] pub slots_in_an_epoch: u64, /// Writes output of `anvil` as json to user-specified file. #[arg(long, value_name = "FILE", value_hint = clap::ValueHint::FilePath)] pub config_out: Option, /// Disable auto and interval mining, and mine on demand instead. #[arg(long, visible_alias = "no-mine", conflicts_with = "block_time")] pub no_mining: bool, #[arg(long, requires = "block_time")] pub mixed_mining: bool, /// The hosts the server will listen on. #[arg( long, value_name = "IP_ADDR", env = "ANVIL_IP_ADDR", default_value = "127.0.0.1", help_heading = "Server options", value_delimiter = ',' )] pub host: Vec, /// How transactions are sorted in the mempool. #[arg(long, default_value = "fees")] pub order: TransactionOrder, /// Initialize the genesis block with the given `genesis.json` file. #[arg(long, value_name = "PATH", value_parser= read_genesis_file)] pub init: Option, /// This is an alias for both --load-state and --dump-state. /// /// It initializes the chain with the state and block environment stored at the file, if it /// exists, and dumps the chain's state on exit. #[arg( long, value_name = "PATH", value_parser = StateFile::parse, conflicts_with_all = &[ "init", "dump_state", "load_state" ] )] pub state: Option, /// Interval in seconds at which the state and block environment is to be dumped to disk. /// /// See --state and --dump-state #[arg(short, long, value_name = "SECONDS")] pub state_interval: Option, /// Dump the state and block environment of chain on exit to the given file. /// /// If the value is a directory, the state will be written to `/state.json`. #[arg(long, value_name = "PATH", conflicts_with = "init")] pub dump_state: Option, /// Preserve historical state snapshots when dumping the state. /// /// This will save the in-memory states of the chain at particular block hashes. /// /// These historical states will be loaded into the memory when `--load-state` / `--state`, and /// aids in RPC calls beyond the block at which state was dumped. #[arg(long, conflicts_with = "init", default_value = "false")] pub preserve_historical_states: bool, /// Initialize the chain from a previously saved state snapshot. #[arg( long, value_name = "PATH", value_parser = SerializableState::parse, conflicts_with = "init" )] pub load_state: Option, #[arg(long, help = IPC_HELP, value_name = "PATH", visible_alias = "ipcpath")] pub ipc: Option>, /// Don't keep full chain history. /// If a number argument is specified, at most this number of states is kept in memory. /// /// If enabled, no state will be persisted on disk, so `max_persisted_states` will be 0. #[arg(long)] pub prune_history: Option>, /// Max number of states to persist on disk. /// /// Note that `prune_history` will overwrite `max_persisted_states` to 0. #[arg(long, conflicts_with = "prune_history")] pub max_persisted_states: Option, /// Number of blocks with transactions to keep in memory. #[arg(long)] pub transaction_block_keeper: Option, /// Maximum number of transactions in a block. #[arg(long)] pub max_transactions: Option, #[command(flatten)] pub evm: AnvilEvmArgs, #[command(flatten)] pub server_config: ServerConfig, /// Path to the cache directory where persisted states are stored (see /// `--max-persisted-states`). /// /// Note: This does not affect the fork RPC cache location (`storage.json`), which is stored in /// `~/.foundry/cache/rpc///`. #[arg(long, value_name = "PATH")] pub cache_path: Option, } #[cfg(windows)] const IPC_HELP: &str = "Launch an ipc server at the given path or default path = `\\.\\pipe\\anvil.ipc`"; /// The default IPC endpoint #[cfg(not(windows))] const IPC_HELP: &str = "Launch an ipc server at the given path or default path = `/tmp/anvil.ipc`"; /// Default interval for periodically dumping the state. const DEFAULT_DUMP_INTERVAL: Duration = Duration::from_secs(60); impl NodeArgs { pub fn into_node_config(self) -> eyre::Result { let genesis_balance = Unit::ETHER.wei().saturating_mul(U256::from(self.balance)); let compute_units_per_second = if self.evm.no_rate_limit { Some(u64::MAX) } else { self.evm.compute_units_per_second }; let hardfork = match &self.hardfork { Some(hf) => { if self.evm.networks.is_optimism() { Some(OpHardfork::from_str(hf)?.into()) } else { Some(EthereumHardfork::from_str(hf)?.into()) } } None => None, }; Ok(NodeConfig::default() .with_gas_limit(self.evm.gas_limit) .disable_block_gas_limit(self.evm.disable_block_gas_limit) .enable_tx_gas_limit(self.evm.enable_tx_gas_limit) .with_gas_price(self.evm.gas_price) .with_hardfork(hardfork) .with_blocktime(self.block_time) .with_no_mining(self.no_mining) .with_mixed_mining(self.mixed_mining, self.block_time) .with_account_generator(self.account_generator())? .with_genesis_balance(genesis_balance) .with_genesis_timestamp(self.timestamp) .with_genesis_block_number(self.number) .with_port(self.port) .with_fork_choice(match (self.evm.fork_block_number, self.evm.fork_transaction_hash) { (Some(block), None) => Some(ForkChoice::Block(block)), (None, Some(hash)) => Some(ForkChoice::Transaction(hash)), _ => self .evm .fork_url .as_ref() .and_then(|f| f.block) .map(|num| ForkChoice::Block(num as i128)), }) .with_fork_headers(self.evm.fork_headers) .with_fork_chain_id(self.evm.fork_chain_id.map(u64::from).map(U256::from)) .fork_request_timeout(self.evm.fork_request_timeout.map(Duration::from_millis)) .fork_request_retries(self.evm.fork_request_retries) .fork_retry_backoff(self.evm.fork_retry_backoff.map(Duration::from_millis)) .fork_compute_units_per_second(compute_units_per_second) .with_eth_rpc_url(self.evm.fork_url.map(|fork| fork.url)) .with_base_fee(self.evm.block_base_fee_per_gas) .disable_min_priority_fee(self.evm.disable_min_priority_fee) .with_no_storage_caching(self.evm.no_storage_caching) .with_server_config(self.server_config) .with_host(self.host) .set_silent(shell::is_quiet()) .set_config_out(self.config_out) .with_chain_id(self.evm.chain_id) .with_transaction_order(self.order) .with_genesis(self.init) .with_steps_tracing(self.evm.steps_tracing) .with_print_logs(!self.evm.disable_console_log) .with_print_traces(self.evm.print_traces) .with_auto_impersonate(self.evm.auto_impersonate) .with_ipc(self.ipc) .with_code_size_limit(self.evm.code_size_limit) .disable_code_size_limit(self.evm.disable_code_size_limit) .set_pruned_history(self.prune_history) .with_init_state(self.load_state.or_else(|| self.state.and_then(|s| s.state))) .with_transaction_block_keeper(self.transaction_block_keeper) .with_max_transactions(self.max_transactions) .with_max_persisted_states(self.max_persisted_states) .with_networks(self.evm.networks) .with_disable_default_create2_deployer(self.evm.disable_default_create2_deployer) .with_disable_pool_balance_checks(self.evm.disable_pool_balance_checks) .with_slots_in_an_epoch(self.slots_in_an_epoch) .with_memory_limit(self.evm.memory_limit) .with_cache_path(self.cache_path)) } fn account_generator(&self) -> AccountGenerator { let mut generator = AccountGenerator::new(self.accounts as usize) .phrase(DEFAULT_MNEMONIC) .chain_id(self.evm.chain_id.unwrap_or(CHAIN_ID.into())); if let Some(ref mnemonic) = self.mnemonic { generator = generator.phrase(mnemonic); } else if let Some(count) = self.mnemonic_random { let mut rng = rand_08::thread_rng(); let mnemonic = match Mnemonic::::new_with_count(&mut rng, count) { Ok(mnemonic) => mnemonic.to_phrase(), Err(err) => { warn!(target: "node", ?count, %err, "failed to generate mnemonic, falling back to 12-word random mnemonic"); // Fallback: generate a valid 12-word random mnemonic instead of using // DEFAULT_MNEMONIC Mnemonic::::new_with_count(&mut rng, 12) .expect("valid default word count") .to_phrase() } }; generator = generator.phrase(mnemonic); } else if let Some(seed) = self.mnemonic_seed { let mut seed = StdRng::seed_from_u64(seed); let mnemonic = Mnemonic::::new(&mut seed).to_phrase(); generator = generator.phrase(mnemonic); } if let Some(ref derivation) = self.derivation_path { generator = generator.derivation_path(derivation); } generator } /// Returns the location where to dump the state to. fn dump_state_path(&self) -> Option { self.dump_state.as_ref().or_else(|| self.state.as_ref().map(|s| &s.path)).cloned() } /// Starts the node /// /// See also [crate::spawn()] pub async fn run(self) -> eyre::Result<()> { let dump_state = self.dump_state_path(); let dump_interval = self.state_interval.map(Duration::from_secs).unwrap_or(DEFAULT_DUMP_INTERVAL); let preserve_historical_states = self.preserve_historical_states; let (api, mut handle) = crate::try_spawn(self.into_node_config()?).await?; // sets the signal handler to gracefully shutdown. let mut fork = api.get_fork(); let running = Arc::new(AtomicUsize::new(0)); // handle for the currently running rt, this must be obtained before setting the crtlc // handler, See [Handle::current] let mut signal = handle.shutdown_signal_mut().take(); let task_manager = handle.task_manager(); let mut on_shutdown = task_manager.on_shutdown(); let mut state_dumper = PeriodicStateDumper::new(api, dump_state, dump_interval, preserve_historical_states); task_manager.spawn(async move { // wait for the SIGTERM signal on unix systems #[cfg(unix)] let mut sigterm = Box::pin(async { if let Ok(mut stream) = tokio::signal::unix::signal(tokio::signal::unix::SignalKind::terminate()) { stream.recv().await; } else { futures::future::pending::<()>().await; } }); // On windows, this will never fire. #[cfg(not(unix))] let mut sigterm = Box::pin(futures::future::pending::<()>()); // await shutdown signal but also periodically flush state tokio::select! { _ = &mut sigterm => { trace!("received sigterm signal, shutting down"); } _ = &mut on_shutdown => {} _ = &mut state_dumper => {} } // shutdown received state_dumper.dump().await; // cleaning up and shutting down // this will make sure that the fork RPC cache is flushed if caching is configured if let Some(fork) = fork.take() { trace!("flushing cache on shutdown"); fork.database .read() .await .maybe_flush_cache() .expect("Could not flush cache on fork DB"); // cleaning up and shutting down // this will make sure that the fork RPC cache is flushed if caching is configured } std::process::exit(0); }); ctrlc::set_handler(move || { let prev = running.fetch_add(1, Ordering::SeqCst); if prev == 0 { trace!("received shutdown signal, shutting down"); let _ = signal.take(); } }) .expect("Error setting Ctrl-C handler"); Ok(handle.await??) } } /// Anvil's EVM related arguments. #[derive(Clone, Debug, Parser)] #[command(next_help_heading = "EVM options")] pub struct AnvilEvmArgs { /// Fetch state over a remote endpoint instead of starting from an empty state. /// /// If you want to fetch state from a specific block number, add a block number like `http://localhost:8545@1400000` or use the `--fork-block-number` argument. #[arg( long, short, visible_alias = "rpc-url", value_name = "URL", help_heading = "Fork config" )] pub fork_url: Option, /// Headers to use for the rpc client, e.g. "User-Agent: test-agent" /// /// See --fork-url. #[arg( long = "fork-header", value_name = "HEADERS", help_heading = "Fork config", requires = "fork_url" )] pub fork_headers: Vec, /// Timeout in ms for requests sent to remote JSON-RPC server in forking mode. /// /// Default value 45000 #[arg(id = "timeout", long = "timeout", help_heading = "Fork config", requires = "fork_url")] pub fork_request_timeout: Option, /// Number of retry requests for spurious networks (timed out requests) /// /// Default value 5 #[arg(id = "retries", long = "retries", help_heading = "Fork config", requires = "fork_url")] pub fork_request_retries: Option, /// Fetch state from a specific block number over a remote endpoint. /// /// If negative, the given value is subtracted from the `latest` block number. /// /// See --fork-url. #[arg( long, requires = "fork_url", value_name = "BLOCK", help_heading = "Fork config", allow_hyphen_values = true )] pub fork_block_number: Option, /// Fetch state from after a specific transaction hash has been applied over a remote endpoint. /// /// See --fork-url. #[arg( long, requires = "fork_url", value_name = "TRANSACTION", help_heading = "Fork config", conflicts_with = "fork_block_number" )] pub fork_transaction_hash: Option, /// Initial retry backoff on encountering errors. /// /// See --fork-url. #[arg(long, requires = "fork_url", value_name = "BACKOFF", help_heading = "Fork config")] pub fork_retry_backoff: Option, /// Specify chain id to skip fetching it from remote endpoint. This enables offline-start mode. /// /// You still must pass both `--fork-url` and `--fork-block-number`, and already have your /// required state cached on disk, anything missing locally would be fetched from the /// remote. #[arg( long, help_heading = "Fork config", value_name = "CHAIN", requires = "fork_block_number" )] pub fork_chain_id: Option, /// Sets the number of assumed available compute units per second for this provider /// /// default value: 330 /// /// See also --fork-url and #[arg( long, requires = "fork_url", alias = "cups", value_name = "CUPS", help_heading = "Fork config" )] pub compute_units_per_second: Option, /// Disables rate limiting for this node's provider. /// /// default value: false /// /// See also --fork-url and #[arg( long, requires = "fork_url", value_name = "NO_RATE_LIMITS", help_heading = "Fork config", visible_alias = "no-rpc-rate-limit" )] pub no_rate_limit: bool, /// Explicitly disables the use of RPC caching. /// /// All storage slots are read entirely from the endpoint. /// /// This flag overrides the project's configuration file. /// /// See --fork-url. #[arg(long, requires = "fork_url", help_heading = "Fork config")] pub no_storage_caching: bool, /// The block gas limit. #[arg(long, alias = "block-gas-limit", help_heading = "Environment config")] pub gas_limit: Option, /// Disable the `call.gas_limit <= block.gas_limit` constraint. #[arg( long, value_name = "DISABLE_GAS_LIMIT", help_heading = "Environment config", alias = "disable-gas-limit", conflicts_with = "gas_limit" )] pub disable_block_gas_limit: bool, /// Enable the transaction gas limit check as imposed by EIP-7825 (Osaka hardfork). #[arg(long, visible_alias = "tx-gas-limit", help_heading = "Environment config")] pub enable_tx_gas_limit: bool, /// EIP-170: Contract code size limit in bytes. Useful to increase this because of tests. To /// disable entirely, use `--disable-code-size-limit`. By default, it is 0x6000 (~25kb). #[arg(long, value_name = "CODE_SIZE", help_heading = "Environment config")] pub code_size_limit: Option, /// Disable EIP-170: Contract code size limit. #[arg( long, value_name = "DISABLE_CODE_SIZE_LIMIT", conflicts_with = "code_size_limit", help_heading = "Environment config" )] pub disable_code_size_limit: bool, /// The gas price. #[arg(long, help_heading = "Environment config")] pub gas_price: Option, /// The base fee in a block. #[arg( long, visible_alias = "base-fee", value_name = "FEE", help_heading = "Environment config" )] pub block_base_fee_per_gas: Option, /// Disable the enforcement of a minimum suggested priority fee. #[arg(long, visible_alias = "no-priority-fee", help_heading = "Environment config")] pub disable_min_priority_fee: bool, /// The chain ID. #[arg(long, alias = "chain", help_heading = "Environment config")] pub chain_id: Option, /// Enable steps tracing used for debug calls returning geth-style traces #[arg(long, visible_alias = "tracing")] pub steps_tracing: bool, /// Disable printing of `console.log` invocations to stdout. #[arg(long, visible_alias = "no-console-log")] pub disable_console_log: bool, /// Enable printing of traces for executed transactions and `eth_call` to stdout. #[arg(long, visible_alias = "enable-trace-printing")] pub print_traces: bool, /// Enables automatic impersonation on startup. This allows any transaction sender to be /// simulated as different accounts, which is useful for testing contract behavior. #[arg(long, visible_alias = "auto-unlock")] pub auto_impersonate: bool, /// Disable the default create2 deployer #[arg(long, visible_alias = "no-create2")] pub disable_default_create2_deployer: bool, /// Disable pool balance checks #[arg(long)] pub disable_pool_balance_checks: bool, /// The memory limit per EVM execution in bytes. #[arg(long)] pub memory_limit: Option, #[command(flatten)] pub networks: NetworkConfigs, } /// Resolves an alias passed as fork-url to the matching url defined in the rpc_endpoints section /// of the project configuration file. /// Does nothing if the fork-url is not a configured alias. impl AnvilEvmArgs { pub fn resolve_rpc_alias(&mut self) { if let Some(fork_url) = &self.fork_url && let Ok(config) = Config::load_with_providers(FigmentProviders::Anvil) && let Some(Ok(url)) = config.get_rpc_url_with_alias(&fork_url.url) { self.fork_url = Some(ForkUrl { url: url.to_string(), block: fork_url.block }); } } } /// Helper type to periodically dump the state of the chain to disk struct PeriodicStateDumper { in_progress_dump: Option + Send + Sync + 'static>>>, api: EthApi, dump_state: Option, preserve_historical_states: bool, interval: Interval, } impl PeriodicStateDumper { fn new( api: EthApi, dump_state: Option, interval: Duration, preserve_historical_states: bool, ) -> Self { let dump_state = dump_state.map(|mut dump_state| { if dump_state.is_dir() { dump_state = dump_state.join("state.json"); } dump_state }); // periodically flush the state let interval = tokio::time::interval_at(Instant::now() + interval, interval); Self { in_progress_dump: None, api, dump_state, preserve_historical_states, interval } } async fn dump(&self) { if let Some(state) = self.dump_state.clone() { Self::dump_state(self.api.clone(), state, self.preserve_historical_states).await } } /// Infallible state dump async fn dump_state( api: EthApi, dump_state: PathBuf, preserve_historical_states: bool, ) { trace!(path=?dump_state, "Dumping state on shutdown"); match api.serialized_state(preserve_historical_states).await { Ok(state) => { if let Err(err) = foundry_common::fs::write_json_file(&dump_state, &state) { error!(?err, "Failed to dump state"); } else { trace!(path=?dump_state, "Dumped state on shutdown"); } } Err(err) => { error!(?err, "Failed to extract state"); } } } } // An endless future that periodically dumps the state to disk if configured. impl Future for PeriodicStateDumper { type Output = (); fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { let this = self.get_mut(); if this.dump_state.is_none() { return Poll::Pending; } loop { if let Some(mut flush) = this.in_progress_dump.take() { match flush.poll_unpin(cx) { Poll::Ready(_) => { this.interval.reset(); } Poll::Pending => { this.in_progress_dump = Some(flush); return Poll::Pending; } } } if this.interval.poll_tick(cx).is_ready() { let api = this.api.clone(); let path = this.dump_state.clone().expect("exists; see above"); this.in_progress_dump = Some(Box::pin(Self::dump_state(api, path, this.preserve_historical_states))); } else { break; } } Poll::Pending } } /// Represents the --state flag and where to load from, or dump the state to #[derive(Clone, Debug)] pub struct StateFile { pub path: PathBuf, pub state: Option, } impl StateFile { /// This is used as the clap `value_parser` implementation to parse from file but only if it /// exists fn parse(path: &str) -> Result { Self::parse_path(path) } /// Parse from file but only if it exists pub fn parse_path(path: impl AsRef) -> Result { let mut path = path.as_ref().to_path_buf(); if path.is_dir() { path = path.join("state.json"); } let mut state = Self { path, state: None }; if !state.path.exists() { return Ok(state); } state.state = Some(SerializableState::load(&state.path).map_err(|err| err.to_string())?); Ok(state) } } /// Represents the input URL for a fork with an optional trailing block number: /// `http://localhost:8545@1000000` #[derive(Clone, Debug, PartialEq, Eq)] pub struct ForkUrl { /// The endpoint url pub url: String, /// Optional trailing block pub block: Option, } impl fmt::Display for ForkUrl { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { self.url.fmt(f)?; if let Some(block) = self.block { write!(f, "@{block}")?; } Ok(()) } } impl FromStr for ForkUrl { type Err = String; fn from_str(s: &str) -> Result { if let Some((url, block)) = s.rsplit_once('@') { if block == "latest" { return Ok(Self { url: url.to_string(), block: None }); } // this will prevent false positives for auths `user:password@example.com` if !block.is_empty() && !block.contains(':') && !block.contains('.') { let block: u64 = block .parse() .map_err(|_| format!("Failed to parse block number: `{block}`"))?; return Ok(Self { url: url.to_string(), block: Some(block) }); } } Ok(Self { url: s.to_string(), block: None }) } } /// Clap's value parser for genesis. Loads a genesis.json file. fn read_genesis_file(path: &str) -> Result { foundry_common::fs::read_json_file(path.as_ref()).map_err(|err| err.to_string()) } fn duration_from_secs_f64(s: &str) -> Result { let s = s.parse::().map_err(|e| e.to_string())?; if s == 0.0 { return Err("Duration must be greater than 0".to_string()); } Duration::try_from_secs_f64(s).map_err(|e| e.to_string()) } #[cfg(test)] mod tests { use super::*; use std::{env, net::Ipv4Addr}; #[test] fn test_parse_fork_url() { let fork: ForkUrl = "http://localhost:8545@1000000".parse().unwrap(); assert_eq!( fork, ForkUrl { url: "http://localhost:8545".to_string(), block: Some(1000000) } ); let fork: ForkUrl = "http://localhost:8545".parse().unwrap(); assert_eq!(fork, ForkUrl { url: "http://localhost:8545".to_string(), block: None }); let fork: ForkUrl = "wss://user:password@example.com/".parse().unwrap(); assert_eq!( fork, ForkUrl { url: "wss://user:password@example.com/".to_string(), block: None } ); let fork: ForkUrl = "wss://user:password@example.com/@latest".parse().unwrap(); assert_eq!( fork, ForkUrl { url: "wss://user:password@example.com/".to_string(), block: None } ); let fork: ForkUrl = "wss://user:password@example.com/@100000".parse().unwrap(); assert_eq!( fork, ForkUrl { url: "wss://user:password@example.com/".to_string(), block: Some(100000) } ); } #[test] fn can_parse_ethereum_hardfork() { let args: NodeArgs = NodeArgs::parse_from(["anvil", "--hardfork", "berlin"]); let config = args.into_node_config().unwrap(); assert_eq!(config.hardfork, Some(EthereumHardfork::Berlin.into())); } #[test] fn can_parse_optimism_hardfork() { let args: NodeArgs = NodeArgs::parse_from(["anvil", "--optimism", "--hardfork", "Regolith"]); let config = args.into_node_config().unwrap(); assert_eq!(config.hardfork, Some(OpHardfork::Regolith.into())); } #[test] fn cant_parse_invalid_hardfork() { let args: NodeArgs = NodeArgs::parse_from(["anvil", "--hardfork", "Regolith"]); let config = args.into_node_config(); assert!(config.is_err()); } #[test] fn can_parse_fork_headers() { let args: NodeArgs = NodeArgs::parse_from([ "anvil", "--fork-url", "http,://localhost:8545", "--fork-header", "User-Agent: test-agent", "--fork-header", "Referrer: example.com", ]); assert_eq!(args.evm.fork_headers, vec!["User-Agent: test-agent", "Referrer: example.com"]); } #[test] fn can_parse_prune_config() { let args: NodeArgs = NodeArgs::parse_from(["anvil", "--prune-history"]); assert!(args.prune_history.is_some()); let args: NodeArgs = NodeArgs::parse_from(["anvil", "--prune-history", "100"]); assert_eq!(args.prune_history, Some(Some(100))); } #[test] fn can_parse_max_persisted_states_config() { let args: NodeArgs = NodeArgs::parse_from(["anvil", "--max-persisted-states", "500"]); assert_eq!(args.max_persisted_states, (Some(500))); } #[test] fn can_parse_disable_block_gas_limit() { let args: NodeArgs = NodeArgs::parse_from(["anvil", "--disable-block-gas-limit"]); assert!(args.evm.disable_block_gas_limit); let args = NodeArgs::try_parse_from(["anvil", "--disable-block-gas-limit", "--gas-limit", "100"]); assert!(args.is_err()); } #[test] fn can_parse_enable_tx_gas_limit() { let args: NodeArgs = NodeArgs::parse_from(["anvil", "--enable-tx-gas-limit"]); assert!(args.evm.enable_tx_gas_limit); // Also test the alias let args: NodeArgs = NodeArgs::parse_from(["anvil", "--tx-gas-limit"]); assert!(args.evm.enable_tx_gas_limit); } #[test] fn can_parse_disable_code_size_limit() { let args: NodeArgs = NodeArgs::parse_from(["anvil", "--disable-code-size-limit"]); assert!(args.evm.disable_code_size_limit); let args = NodeArgs::try_parse_from([ "anvil", "--disable-code-size-limit", "--code-size-limit", "100", ]); // can't be used together assert!(args.is_err()); } #[test] fn can_parse_host() { let args = NodeArgs::parse_from(["anvil"]); assert_eq!(args.host, vec![IpAddr::V4(Ipv4Addr::LOCALHOST)]); let args = NodeArgs::parse_from([ "anvil", "--host", "::1", "--host", "1.1.1.1", "--host", "2.2.2.2", ]); assert_eq!( args.host, ["::1", "1.1.1.1", "2.2.2.2"].map(|ip| ip.parse::().unwrap()).to_vec() ); let args = NodeArgs::parse_from(["anvil", "--host", "::1,1.1.1.1,2.2.2.2"]); assert_eq!( args.host, ["::1", "1.1.1.1", "2.2.2.2"].map(|ip| ip.parse::().unwrap()).to_vec() ); unsafe { env::set_var("ANVIL_IP_ADDR", "1.1.1.1") }; let args = NodeArgs::parse_from(["anvil"]); assert_eq!(args.host, vec!["1.1.1.1".parse::().unwrap()]); unsafe { env::set_var("ANVIL_IP_ADDR", "::1,1.1.1.1,2.2.2.2") }; let args = NodeArgs::parse_from(["anvil"]); assert_eq!( args.host, ["::1", "1.1.1.1", "2.2.2.2"].map(|ip| ip.parse::().unwrap()).to_vec() ); } } ================================================ FILE: crates/anvil/src/config.rs ================================================ use crate::{ EthereumHardfork, FeeManager, PrecompileFactory, eth::{ backend::{ db::{Db, SerializableState}, env::Env, fork::{ClientFork, ClientForkConfig}, genesis::GenesisConfig, mem::fork_db::ForkedDatabase, time::duration_since_unix_epoch, }, fees::{INITIAL_BASE_FEE, INITIAL_GAS_PRICE}, pool::transactions::{PoolTransaction, TransactionOrder}, }, mem::{self, in_memory_db::MemDb}, }; use alloy_consensus::BlockHeader; use alloy_eips::{eip1559::BaseFeeParams, eip7840::BlobParams}; use alloy_evm::EvmEnv; use alloy_genesis::Genesis; use alloy_network::{AnyNetwork, BlockResponse, TransactionResponse}; use alloy_primitives::{BlockNumber, TxHash, U256, hex, map::HashMap, utils::Unit}; use alloy_provider::Provider; use alloy_rpc_types::BlockNumberOrTag; use alloy_signer::Signer; use alloy_signer_local::{ MnemonicBuilder, PrivateKeySigner, coins_bip39::{English, Mnemonic}, }; use alloy_transport::TransportError; use anvil_server::ServerConfig; use eyre::{Context, Result}; use foundry_common::{ ALCHEMY_FREE_TIER_CUPS, NON_ARCHIVE_NODE_WARNING, REQUEST_TIMEOUT, provider::{ProviderBuilder, RetryProvider}, }; use foundry_config::Config; use foundry_evm::{ backend::{BlockchainDb, BlockchainDbMeta, SharedBackend}, constants::DEFAULT_CREATE2_DEPLOYER, hardfork::{ FoundryHardfork, OpHardfork, ethereum_hardfork_from_block_tag, spec_id_from_ethereum_hardfork, }, utils::{ apply_chain_and_block_specific_env_changes, block_env_from_header, get_blob_base_fee_update_fraction, }, }; use foundry_primitives::FoundryTxEnvelope; use itertools::Itertools; use op_revm::OpTransaction; use parking_lot::RwLock; use rand_08::thread_rng; use revm::{ context::{BlockEnv, CfgEnv, TxEnv}, context_interface::block::BlobExcessGasAndPrice, primitives::hardfork::SpecId, }; use serde_json::{Value, json}; use std::{ fmt::Write as FmtWrite, net::{IpAddr, Ipv4Addr}, path::PathBuf, sync::Arc, time::Duration, }; use tokio::sync::RwLock as TokioRwLock; use yansi::Paint; pub use foundry_common::version::SHORT_VERSION as VERSION_MESSAGE; use foundry_evm::{ traces::{CallTraceDecoderBuilder, identifier::SignaturesIdentifier}, utils::get_blob_params, }; use foundry_evm_networks::NetworkConfigs; /// Default port the rpc will open 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: 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"; /// The default IPC endpoint pub const DEFAULT_IPC_ENDPOINT: &str = if cfg!(unix) { "/tmp/anvil.ipc" } else { r"\\.\pipe\anvil.ipc" }; const BANNER: &str = r" _ _ (_) | | __ _ _ __ __ __ _ | | / _` | | '_ \ \ \ / / | | | | | (_| | | | | | \ V / | | | | \__,_| |_| |_| \_/ |_| |_| "; /// Configurations of the EVM node #[derive(Clone, Debug)] pub struct NodeConfig { /// Chain ID of the EVM chain pub chain_id: Option, /// Default gas limit for all txs pub gas_limit: Option, /// If set to `true`, disables the block gas limit pub disable_block_gas_limit: bool, /// If set to `true`, enables the tx gas limit as imposed by Osaka (EIP-7825) pub enable_tx_gas_limit: bool, /// Default gas price for all txs pub gas_price: Option, /// Default base fee pub base_fee: Option, /// If set to `true`, disables the enforcement of a minimum suggested priority fee pub disable_min_priority_fee: bool, /// Default blob excess gas and price pub blob_excess_gas_and_price: Option, /// The hardfork to use pub hardfork: Option, /// Signer accounts that will be initialised with `genesis_balance` in the genesis block pub genesis_accounts: Vec, /// Native token balance of every genesis account in the genesis block pub genesis_balance: U256, /// Genesis block timestamp pub genesis_timestamp: Option, /// Genesis block number pub genesis_block_number: Option, /// Signer accounts that can sign messages/transactions from the EVM node pub signer_accounts: Vec, /// Configured block time for the EVM chain. Use `None` to mine a new block for every tx pub block_time: Option, /// Disable auto, interval mining mode uns use `MiningMode::None` instead pub no_mining: bool, /// Enables auto and interval mining mode pub mixed_mining: bool, /// port to use for the server pub port: u16, /// maximum number of transactions in a block pub max_transactions: usize, /// url of the rpc server that should be used for any rpc calls pub eth_rpc_url: Option, /// pins the block number or transaction hash for the state fork pub fork_choice: Option, /// headers to use with `eth_rpc_url` pub fork_headers: Vec, /// specifies chain id for cache to skip fetching from remote in offline-start mode pub fork_chain_id: Option, /// The generator used to generate the dev accounts pub account_generator: Option, /// whether to enable tracing pub enable_tracing: bool, /// Explicitly disables the use of RPC caching. pub no_storage_caching: bool, /// How to configure the server pub server_config: ServerConfig, /// The host the server will listen on pub host: Vec, /// How transactions are sorted in the mempool pub transaction_order: TransactionOrder, /// Filename to write anvil output as json pub config_out: Option, /// The genesis to use to initialize the node pub genesis: Option, /// Timeout in for requests sent to remote JSON-RPC server in forking mode pub fork_request_timeout: Duration, /// Number of request retries for spurious networks pub fork_request_retries: u32, /// The initial retry backoff pub fork_retry_backoff: Duration, /// available CUPS pub compute_units_per_second: u64, /// The ipc path pub ipc_path: Option>, /// Enable transaction/call steps tracing for debug calls returning geth-style traces pub enable_steps_tracing: bool, /// Enable printing of `console.log` invocations. pub print_logs: bool, /// Enable printing of traces. pub print_traces: bool, /// Enable auto impersonation of accounts on startup pub enable_auto_impersonate: bool, /// Configure the code size limit pub code_size_limit: Option, /// Configures how to remove historic state. /// /// If set to `Some(num)` keep latest num state in memory only. pub prune_history: PruneStateHistoryConfig, /// Max number of states cached on disk. pub max_persisted_states: Option, /// The file where to load the state from pub init_state: Option, /// max number of blocks with transactions in memory pub transaction_block_keeper: Option, /// Disable the default CREATE2 deployer pub disable_default_create2_deployer: bool, /// Disable pool balance checks pub disable_pool_balance_checks: bool, /// Slots in an epoch pub slots_in_an_epoch: u64, /// The memory limit per EVM execution in bytes. pub memory_limit: Option, /// Factory used by `anvil` to extend the EVM's precompiles. pub precompile_factory: Option>, /// Networks to enable features for. pub networks: NetworkConfigs, /// Do not print log messages. pub silent: bool, /// The path where persisted states are cached (used with `max_persisted_states`). /// This does not affect the fork RPC cache location. pub cache_path: Option, } impl NodeConfig { fn as_string(&self, fork: Option<&ClientFork>) -> String { let mut s: String = String::new(); let _ = write!(s, "\n{}", BANNER.green()); let _ = write!(s, "\n {VERSION_MESSAGE}"); let _ = write!(s, "\n {}", "https://github.com/foundry-rs/foundry".green()); let _ = write!( s, r#" Available Accounts ================== "# ); let balance = alloy_primitives::utils::format_ether(self.genesis_balance); for (idx, wallet) in self.genesis_accounts.iter().enumerate() { write!(s, "\n({idx}) {} ({balance} ETH)", wallet.address()).unwrap(); } let _ = write!( s, r#" Private Keys ================== "# ); for (idx, wallet) in self.genesis_accounts.iter().enumerate() { let hex = hex::encode(wallet.credential().to_bytes()); let _ = write!(s, "\n({idx}) 0x{hex}"); } if let Some(generator) = &self.account_generator { let _ = write!( s, r#" Wallet ================== Mnemonic: {} Derivation path: {} "#, generator.phrase, generator.get_derivation_path() ); } if let Some(fork) = fork { let _ = write!( s, r#" Fork ================== Endpoint: {} Block number: {} Block hash: {:?} Chain ID: {} "#, fork.eth_rpc_url(), fork.block_number(), fork.block_hash(), fork.chain_id() ); if let Some(tx_hash) = fork.transaction_hash() { let _ = writeln!(s, "Transaction hash: {tx_hash}"); } } else { let _ = write!( s, r#" Chain ID ================== {} "#, self.get_chain_id().green() ); } if (SpecId::from(self.get_hardfork()) as u8) < (SpecId::LONDON as u8) { let _ = write!( s, r#" Gas Price ================== {} "#, self.get_gas_price().green() ); } else { let _ = write!( s, r#" Base Fee ================== {} "#, self.get_base_fee().green() ); } let _ = write!( s, r#" Gas Limit ================== {} "#, { if self.disable_block_gas_limit { "Disabled".to_string() } else { self.gas_limit.map(|l| l.to_string()).unwrap_or_else(|| { if self.fork_choice.is_some() { "Forked".to_string() } else { DEFAULT_GAS_LIMIT.to_string() } }) } } .green() ); let _ = write!( s, r#" Genesis Timestamp ================== {} "#, self.get_genesis_timestamp().green() ); let _ = write!( s, r#" Genesis Number ================== {} "#, self.get_genesis_number().green() ); s } fn as_json(&self, fork: Option<&ClientFork>) -> Value { let mut wallet_description = HashMap::new(); let mut available_accounts = Vec::with_capacity(self.genesis_accounts.len()); let mut private_keys = Vec::with_capacity(self.genesis_accounts.len()); for wallet in &self.genesis_accounts { available_accounts.push(format!("{:?}", wallet.address())); private_keys.push(format!("0x{}", hex::encode(wallet.credential().to_bytes()))); } if let Some(generator) = &self.account_generator { let phrase = generator.get_phrase().to_string(); let derivation_path = generator.get_derivation_path().to_string(); wallet_description.insert("derivation_path".to_string(), derivation_path); wallet_description.insert("mnemonic".to_string(), phrase); }; let gas_limit = match self.gas_limit { // if we have a disabled flag we should max out the limit Some(_) | None if self.disable_block_gas_limit => Some(u64::MAX.to_string()), Some(limit) => Some(limit.to_string()), _ => None, }; if let Some(fork) = fork { json!({ "available_accounts": available_accounts, "private_keys": private_keys, "endpoint": fork.eth_rpc_url(), "block_number": fork.block_number(), "block_hash": fork.block_hash(), "chain_id": fork.chain_id(), "wallet": wallet_description, "base_fee": format!("{}", self.get_base_fee()), "gas_price": format!("{}", self.get_gas_price()), "gas_limit": gas_limit, }) } else { json!({ "available_accounts": available_accounts, "private_keys": private_keys, "wallet": wallet_description, "base_fee": format!("{}", self.get_base_fee()), "gas_price": format!("{}", self.get_gas_price()), "gas_limit": gas_limit, "genesis_timestamp": format!("{}", self.get_genesis_timestamp()), }) } } } impl NodeConfig { /// Returns a new config intended to be used in tests, which does not print and binds to a /// random, free port by setting it to `0` #[doc(hidden)] pub fn test() -> Self { Self { enable_tracing: true, port: 0, silent: true, ..Default::default() } } /// Returns a new config which does not initialize any accounts on node startup. pub fn empty_state() -> Self { Self { genesis_accounts: vec![], signer_accounts: vec![], disable_default_create2_deployer: true, ..Default::default() } } } impl Default for NodeConfig { fn default() -> Self { // generate some random wallets let genesis_accounts = AccountGenerator::new(10) .phrase(DEFAULT_MNEMONIC) .generate() .expect("Invalid mnemonic."); Self { chain_id: None, gas_limit: None, disable_block_gas_limit: false, enable_tx_gas_limit: false, gas_price: None, hardfork: None, signer_accounts: genesis_accounts.clone(), genesis_timestamp: None, genesis_block_number: None, genesis_accounts, // 100ETH default balance genesis_balance: Unit::ETHER.wei().saturating_mul(U256::from(100u64)), block_time: None, no_mining: false, mixed_mining: false, port: NODE_PORT, max_transactions: 1_000, eth_rpc_url: None, fork_choice: None, account_generator: None, base_fee: None, disable_min_priority_fee: false, blob_excess_gas_and_price: None, enable_tracing: true, enable_steps_tracing: false, print_logs: true, print_traces: false, enable_auto_impersonate: false, no_storage_caching: false, server_config: Default::default(), host: vec![IpAddr::V4(Ipv4Addr::LOCALHOST)], transaction_order: Default::default(), config_out: None, genesis: None, fork_request_timeout: REQUEST_TIMEOUT, fork_headers: vec![], fork_request_retries: 5, fork_retry_backoff: Duration::from_millis(1_000), fork_chain_id: None, // alchemy max cpus compute_units_per_second: ALCHEMY_FREE_TIER_CUPS, ipc_path: None, code_size_limit: None, prune_history: Default::default(), max_persisted_states: None, init_state: None, transaction_block_keeper: None, disable_default_create2_deployer: false, disable_pool_balance_checks: false, slots_in_an_epoch: 32, memory_limit: None, precompile_factory: None, networks: Default::default(), silent: false, cache_path: None, } } } impl NodeConfig { /// Returns the memory limit of the node #[must_use] pub fn with_memory_limit(mut self, mems_value: Option) -> Self { self.memory_limit = mems_value; self } /// Returns the base fee to use pub fn get_base_fee(&self) -> u64 { self.base_fee .or_else(|| self.genesis.as_ref().and_then(|g| g.base_fee_per_gas.map(|g| g as u64))) .unwrap_or(INITIAL_BASE_FEE) } /// Returns the base fee to use pub fn get_gas_price(&self) -> u128 { self.gas_price.unwrap_or(INITIAL_GAS_PRICE) } pub fn get_blob_excess_gas_and_price(&self) -> BlobExcessGasAndPrice { if let Some(value) = self.blob_excess_gas_and_price { value } else { let excess_blob_gas = self.genesis.as_ref().and_then(|g| g.excess_blob_gas).unwrap_or(0); BlobExcessGasAndPrice::new( excess_blob_gas, get_blob_base_fee_update_fraction( self.get_chain_id(), self.get_genesis_timestamp(), ), ) } } /// Returns the [`BlobParams`] that should be used. pub fn get_blob_params(&self) -> BlobParams { get_blob_params(self.get_chain_id(), self.get_genesis_timestamp()) } /// Returns the hardfork to use pub fn get_hardfork(&self) -> FoundryHardfork { if let Some(hardfork) = self.hardfork { return hardfork; } if self.networks.is_optimism() { return OpHardfork::default().into(); } EthereumHardfork::default().into() } /// Sets a custom code size limit #[must_use] pub fn with_code_size_limit(mut self, code_size_limit: Option) -> Self { self.code_size_limit = code_size_limit; self } /// Disables code size limit #[must_use] pub fn disable_code_size_limit(mut self, disable_code_size_limit: bool) -> Self { if disable_code_size_limit { self.code_size_limit = Some(usize::MAX); } self } /// Sets the init state if any #[must_use] pub fn with_init_state(mut self, init_state: Option) -> Self { self.init_state = init_state; self } /// Loads the init state from a file if it exists #[must_use] #[cfg(feature = "cmd")] pub fn with_init_state_path(mut self, path: impl AsRef) -> Self { self.init_state = crate::cmd::StateFile::parse_path(path).ok().and_then(|file| file.state); self } /// Sets the chain ID #[must_use] pub fn with_chain_id>(mut self, chain_id: Option) -> Self { self.set_chain_id(chain_id); self } /// Returns the chain ID to use pub fn get_chain_id(&self) -> u64 { self.chain_id .or_else(|| self.genesis.as_ref().map(|g| g.config.chain_id)) .unwrap_or(CHAIN_ID) } /// Sets the chain id and updates all wallets pub fn set_chain_id(&mut self, chain_id: Option>) { self.chain_id = chain_id.map(Into::into); let chain_id = self.get_chain_id(); self.networks.with_chain_id(chain_id); self.genesis_accounts.iter_mut().for_each(|wallet| { *wallet = wallet.clone().with_chain_id(Some(chain_id)); }); self.signer_accounts.iter_mut().for_each(|wallet| { *wallet = wallet.clone().with_chain_id(Some(chain_id)); }) } /// Sets the gas limit #[must_use] pub fn with_gas_limit(mut self, gas_limit: Option) -> Self { self.gas_limit = gas_limit; self } /// Disable block gas limit check /// /// If set to `true` block gas limit will not be enforced #[must_use] pub fn disable_block_gas_limit(mut self, disable_block_gas_limit: bool) -> Self { self.disable_block_gas_limit = disable_block_gas_limit; self } /// Enable tx gas limit check /// /// If set to `true`, enables the tx gas limit as imposed by Osaka (EIP-7825) #[must_use] pub fn enable_tx_gas_limit(mut self, enable_tx_gas_limit: bool) -> Self { self.enable_tx_gas_limit = enable_tx_gas_limit; self } /// Sets the gas price #[must_use] pub fn with_gas_price(mut self, gas_price: Option) -> Self { self.gas_price = gas_price; self } /// Sets prune history status. #[must_use] pub fn set_pruned_history(mut self, prune_history: Option>) -> Self { self.prune_history = PruneStateHistoryConfig::from_args(prune_history); self } /// Sets max number of states to cache on disk. #[must_use] pub fn with_max_persisted_states>( mut self, max_persisted_states: Option, ) -> Self { self.max_persisted_states = max_persisted_states.map(Into::into); self } /// Sets the max number of transactions in a block #[must_use] pub fn with_max_transactions(mut self, max_transactions: Option) -> Self { if let Some(max_transactions) = max_transactions { self.max_transactions = max_transactions; } self } /// Sets max number of blocks with transactions to keep in memory #[must_use] pub fn with_transaction_block_keeper>( mut self, transaction_block_keeper: Option, ) -> Self { self.transaction_block_keeper = transaction_block_keeper.map(Into::into); self } /// Sets the base fee #[must_use] pub fn with_base_fee(mut self, base_fee: Option) -> Self { self.base_fee = base_fee; self } /// Disable the enforcement of a minimum suggested priority fee #[must_use] pub fn disable_min_priority_fee(mut self, disable_min_priority_fee: bool) -> Self { self.disable_min_priority_fee = disable_min_priority_fee; self } /// Sets the init genesis (genesis.json) #[must_use] pub fn with_genesis(mut self, genesis: Option) -> Self { self.genesis = genesis; self } /// Returns the genesis timestamp to use pub fn get_genesis_timestamp(&self) -> u64 { self.genesis_timestamp .or_else(|| self.genesis.as_ref().map(|g| g.timestamp)) .unwrap_or_else(|| duration_since_unix_epoch().as_secs()) } /// Sets the genesis timestamp #[must_use] pub fn with_genesis_timestamp>(mut self, timestamp: Option) -> Self { if let Some(timestamp) = timestamp { self.genesis_timestamp = Some(timestamp.into()); } self } /// Sets the genesis number #[must_use] pub fn with_genesis_block_number>(mut self, number: Option) -> Self { if let Some(number) = number { self.genesis_block_number = Some(number.into()); } self } /// Returns the genesis number pub fn get_genesis_number(&self) -> u64 { self.genesis_block_number .or_else(|| self.genesis.as_ref().and_then(|g| g.number)) .unwrap_or(0) } /// Sets the hardfork #[must_use] pub fn with_hardfork(mut self, hardfork: Option) -> Self { self.hardfork = hardfork; self } /// Sets the genesis accounts #[must_use] pub fn with_genesis_accounts(mut self, accounts: Vec) -> Self { self.genesis_accounts = accounts; self } /// Sets the signer accounts #[must_use] pub fn with_signer_accounts(mut self, accounts: Vec) -> Self { self.signer_accounts = accounts; self } /// Sets both the genesis accounts and the signer accounts /// so that `genesis_accounts == accounts` pub fn with_account_generator(mut self, generator: AccountGenerator) -> eyre::Result { let accounts = generator.generate()?; self.account_generator = Some(generator); Ok(self.with_signer_accounts(accounts.clone()).with_genesis_accounts(accounts)) } /// Sets the balance of the genesis accounts in the genesis block #[must_use] pub fn with_genesis_balance>(mut self, balance: U) -> Self { self.genesis_balance = balance.into(); self } /// Sets the block time to automine blocks #[must_use] pub fn with_blocktime>(mut self, block_time: Option) -> Self { self.block_time = block_time.map(Into::into); self } #[must_use] pub fn with_mixed_mining>( mut self, mixed_mining: bool, block_time: Option, ) -> Self { self.block_time = block_time.map(Into::into); self.mixed_mining = mixed_mining; self } /// If set to `true` auto mining will be disabled #[must_use] pub fn with_no_mining(mut self, no_mining: bool) -> Self { self.no_mining = no_mining; self } /// Sets the slots in an epoch #[must_use] pub fn with_slots_in_an_epoch(mut self, slots_in_an_epoch: u64) -> Self { self.slots_in_an_epoch = slots_in_an_epoch; self } /// Sets the port to use #[must_use] pub fn with_port(mut self, port: u16) -> Self { self.port = port; self } /// Sets the ipc path to use /// /// Note: this is a double Option for /// - `None` -> no ipc /// - `Some(None)` -> use default path /// - `Some(Some(path))` -> use custom path #[must_use] pub fn with_ipc(mut self, ipc_path: Option>) -> Self { self.ipc_path = ipc_path; self } /// Sets the file path to write the Anvil node's config info to. #[must_use] pub fn set_config_out(mut self, config_out: Option) -> Self { self.config_out = config_out; self } #[must_use] pub fn with_no_storage_caching(mut self, no_storage_caching: bool) -> Self { self.no_storage_caching = no_storage_caching; self } /// Sets the `eth_rpc_url` to use when forking #[must_use] pub fn with_eth_rpc_url>(mut self, eth_rpc_url: Option) -> Self { self.eth_rpc_url = eth_rpc_url.map(Into::into); self } /// Sets the `fork_choice` to use to fork off from based on a block number #[must_use] pub fn with_fork_block_number>(self, fork_block_number: Option) -> Self { self.with_fork_choice(fork_block_number.map(Into::into)) } /// Sets the `fork_choice` to use to fork off from based on a transaction hash #[must_use] pub fn with_fork_transaction_hash>( self, fork_transaction_hash: Option, ) -> Self { self.with_fork_choice(fork_transaction_hash.map(Into::into)) } /// Sets the `fork_choice` to use to fork off from #[must_use] pub fn with_fork_choice>(mut self, fork_choice: Option) -> Self { self.fork_choice = fork_choice.map(Into::into); self } /// Sets the `fork_chain_id` to use to fork off local cache from #[must_use] pub fn with_fork_chain_id(mut self, fork_chain_id: Option) -> Self { self.fork_chain_id = fork_chain_id; self } /// Sets the `fork_headers` to use with `eth_rpc_url` #[must_use] pub fn with_fork_headers(mut self, headers: Vec) -> Self { self.fork_headers = headers; self } /// Sets the `fork_request_timeout` to use for requests #[must_use] pub fn fork_request_timeout(mut self, fork_request_timeout: Option) -> Self { if let Some(fork_request_timeout) = fork_request_timeout { self.fork_request_timeout = fork_request_timeout; } self } /// Sets the `fork_request_retries` to use for spurious networks #[must_use] pub fn fork_request_retries(mut self, fork_request_retries: Option) -> Self { if let Some(fork_request_retries) = fork_request_retries { self.fork_request_retries = fork_request_retries; } self } /// Sets the initial `fork_retry_backoff` for rate limits #[must_use] pub fn fork_retry_backoff(mut self, fork_retry_backoff: Option) -> Self { if let Some(fork_retry_backoff) = fork_retry_backoff { self.fork_retry_backoff = fork_retry_backoff; } self } /// Sets the number of assumed available compute units per second /// /// See also, #[must_use] pub fn fork_compute_units_per_second(mut self, compute_units_per_second: Option) -> Self { if let Some(compute_units_per_second) = compute_units_per_second { self.compute_units_per_second = compute_units_per_second; } self } /// Sets whether to enable tracing #[must_use] pub fn with_tracing(mut self, enable_tracing: bool) -> Self { self.enable_tracing = enable_tracing; self } /// Sets whether to enable steps tracing #[must_use] pub fn with_steps_tracing(mut self, enable_steps_tracing: bool) -> Self { self.enable_steps_tracing = enable_steps_tracing; self } /// Sets whether to print `console.log` invocations to stdout. #[must_use] pub fn with_print_logs(mut self, print_logs: bool) -> Self { self.print_logs = print_logs; self } /// Sets whether to print traces to stdout. #[must_use] pub fn with_print_traces(mut self, print_traces: bool) -> Self { self.print_traces = print_traces; self } /// Sets whether to enable autoImpersonate #[must_use] pub fn with_auto_impersonate(mut self, enable_auto_impersonate: bool) -> Self { self.enable_auto_impersonate = enable_auto_impersonate; self } #[must_use] pub fn with_server_config(mut self, config: ServerConfig) -> Self { self.server_config = config; self } /// Sets the host the server will listen on #[must_use] pub fn with_host(mut self, host: Vec) -> Self { self.host = if host.is_empty() { vec![IpAddr::V4(Ipv4Addr::LOCALHOST)] } else { host }; self } #[must_use] pub fn with_transaction_order(mut self, transaction_order: TransactionOrder) -> Self { self.transaction_order = transaction_order; self } /// Returns the ipc path for the ipc endpoint if any pub fn get_ipc_path(&self) -> Option { match &self.ipc_path { Some(path) => path.clone().or_else(|| Some(DEFAULT_IPC_ENDPOINT.to_string())), None => None, } } /// Prints the config info pub fn print(&self, fork: Option<&ClientFork>) -> Result<()> { if let Some(path) = &self.config_out { let value = self.as_json(fork); foundry_common::fs::write_json_file(path, &value).wrap_err("failed writing JSON")?; } if !self.silent { sh_println!("{}", self.as_string(fork))?; } Ok(()) } /// Returns the path where the cache file should be stored /// /// See also [ Config::foundry_block_cache_file()] pub fn block_cache_path(&self, block: u64) -> Option { if self.no_storage_caching || self.eth_rpc_url.is_none() { return None; } let chain_id = self.get_chain_id(); Config::foundry_block_cache_file(chain_id, block) } /// Sets whether to disable the default create2 deployer #[must_use] pub fn with_disable_default_create2_deployer(mut self, yes: bool) -> Self { self.disable_default_create2_deployer = yes; self } /// Sets whether to disable pool balance checks #[must_use] pub fn with_disable_pool_balance_checks(mut self, yes: bool) -> Self { self.disable_pool_balance_checks = yes; self } /// Injects precompiles to `anvil`'s EVM. #[must_use] pub fn with_precompile_factory(mut self, factory: impl PrecompileFactory + 'static) -> Self { self.precompile_factory = Some(Arc::new(factory)); self } /// Enable features for provided networks. #[must_use] pub fn with_networks(mut self, networks: NetworkConfigs) -> Self { self.networks = networks; self } /// Makes the node silent to not emit anything on stdout #[must_use] pub fn silent(self) -> Self { self.set_silent(true) } #[must_use] pub fn set_silent(mut self, silent: bool) -> Self { self.silent = silent; self } /// Sets the path where persisted states are cached (used with `max_persisted_states`). /// /// Note: This does not control the fork RPC cache location (`storage.json`), which uses /// `~/.foundry/cache/rpc///` via [`Config::foundry_block_cache_file`]. #[must_use] pub fn with_cache_path(mut self, cache_path: Option) -> Self { self.cache_path = cache_path; self } /// Configures everything related to env, backend and database and returns the /// [Backend](mem::Backend) /// /// *Note*: only memory based backend for now pub(crate) async fn setup(&mut self) -> Result> where N: alloy_network::Network< TxEnvelope = foundry_primitives::FoundryTxEnvelope, ReceiptEnvelope = foundry_primitives::FoundryReceiptEnvelope, >, { // configure the revm environment 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. // 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; if !self.enable_tx_gas_limit { cfg.tx_gas_limit_cap = Some(u64::MAX); } if let Some(value) = self.memory_limit { cfg.memory_limit = value; } let spec_id = cfg.spec; let mut env = Env::new( EvmEnv::new( cfg, BlockEnv { gas_limit: self.gas_limit(), basefee: self.get_base_fee(), ..Default::default() }, ), OpTransaction { base: TxEnv { chain_id: Some(self.get_chain_id()), ..Default::default() }, ..Default::default() }, self.networks, ); let base_fee_params: BaseFeeParams = self.networks.base_fee_params(self.get_genesis_timestamp()); let fees = FeeManager::new( spec_id, self.get_base_fee(), !self.disable_min_priority_fee, self.get_gas_price(), self.get_blob_excess_gas_and_price(), self.get_blob_params(), base_fee_params, ); let (db, fork): (Arc>>, Option) = if let Some(eth_rpc_url) = self.eth_rpc_url.clone() { self.setup_fork_db(eth_rpc_url, &mut env, &fees).await? } else { (Arc::new(TokioRwLock::new(Box::::default())), None) }; // if provided use all settings of `genesis.json` if let Some(ref genesis) = self.genesis { // --chain-id flag gets precedence over the genesis.json chain id // if self.chain_id.is_none() { env.evm_env.cfg_env.chain_id = genesis.config.chain_id; } env.evm_env.block_env.timestamp = U256::from(genesis.timestamp); if let Some(base_fee) = genesis.base_fee_per_gas { env.evm_env.block_env.basefee = base_fee.try_into()?; } if let Some(number) = genesis.number { env.evm_env.block_env.number = U256::from(number); } env.evm_env.block_env.beneficiary = genesis.coinbase; } let genesis = GenesisConfig { number: self.get_genesis_number(), timestamp: self.get_genesis_timestamp(), balance: self.genesis_balance, accounts: self.genesis_accounts.iter().map(|acc| acc.address()).collect(), genesis_init: self.genesis.clone(), }; let mut decoder_builder = CallTraceDecoderBuilder::new(); if self.print_traces { // if traces should get printed we configure the decoder with the signatures cache if let Ok(identifier) = SignaturesIdentifier::new(false) { debug!(target: "node", "using signature identifier"); decoder_builder = decoder_builder.with_signature_identifier(identifier); } } // only memory based backend for now let backend = mem::Backend::with_genesis( db, Arc::new(RwLock::new(env)), genesis, fees, Arc::new(RwLock::new(fork)), self.enable_steps_tracing, self.print_logs, self.print_traces, Arc::new(decoder_builder.build()), self.prune_history, self.max_persisted_states, self.transaction_block_keeper, self.block_time, self.cache_path.clone(), Arc::new(TokioRwLock::new(self.clone())), ) .await?; // Writes the default create2 deployer to the backend, // if the option is not disabled and we are not forking. if !self.disable_default_create2_deployer && self.eth_rpc_url.is_none() { backend .set_create2_deployer(DEFAULT_CREATE2_DEPLOYER) .await .wrap_err("failed to create default create2 deployer")?; } Ok(backend) } /// Configures everything related to forking based on the passed `eth_rpc_url`: /// - returning a tuple of a [ForkedDatabase] wrapped in an [Arc] [RwLock](TokioRwLock) and /// [ClientFork] wrapped in an [Option] which can be used in a [Backend](mem::Backend) to /// fork from. /// - modifying some parameters of the passed `env` /// - mutating some members of `self` pub async fn setup_fork_db( &mut self, eth_rpc_url: String, env: &mut Env, fees: &FeeManager, ) -> Result<(Arc>>, Option)> { let (db, config) = self.setup_fork_db_config(eth_rpc_url, env, fees).await?; let db: Arc>> = Arc::new(TokioRwLock::new(Box::new(db))); let fork = ClientFork::new(config, Arc::clone(&db)); Ok((db, Some(fork))) } /// Configures everything related to forking based on the passed `eth_rpc_url`: /// - returning a tuple of a [ForkedDatabase] and [ClientForkConfig] which can be used to build /// a [ClientFork] to fork from. /// - modifying some parameters of the passed `env` /// - mutating some members of `self` pub async fn setup_fork_db_config( &mut self, eth_rpc_url: String, env: &mut Env, fees: &FeeManager, ) -> Result<(ForkedDatabase, ClientForkConfig)> { debug!(target: "node", ?eth_rpc_url, "setting up fork db"); let provider = Arc::new( ProviderBuilder::new(ð_rpc_url) .timeout(self.fork_request_timeout) .initial_backoff(self.fork_retry_backoff.as_millis() as u64) .compute_units_per_second(self.compute_units_per_second) .max_retry(self.fork_request_retries) .headers(self.fork_headers.clone()) .build() .wrap_err("failed to establish provider to fork url")?, ); let (fork_block_number, fork_chain_id, force_transactions) = if let Some(fork_choice) = &self.fork_choice { let (fork_block_number, force_transactions) = derive_block_and_transactions(fork_choice, &provider).await.wrap_err( "failed to derive fork block number and force transactions from fork choice", )?; let chain_id = if let Some(chain_id) = self.fork_chain_id { Some(chain_id) } else if self.hardfork.is_none() { // Auto-adjust hardfork if not specified, but only if we're forking mainnet. 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 = ethereum_hardfork_from_block_tag(fork_block_number); env.evm_env.cfg_env.spec = spec_id_from_ethereum_hardfork(hardfork); self.hardfork = Some(FoundryHardfork::Ethereum(hardfork)); } Some(U256::from(chain_id)) } else { None }; (fork_block_number, chain_id, force_transactions) } else { // pick the last block number but also ensure it's not pending anymore let bn = find_latest_fork_block(&provider) .await .wrap_err("failed to get fork block number")?; (bn, None, None) }; let block = provider .get_block(BlockNumberOrTag::Number(fork_block_number).into()) .await .wrap_err("failed to get fork block")?; let block = if let Some(block) = block { block } else { if let Ok(latest_block) = provider.get_block_number().await { let mut message = format!( "Failed to get block for block number: {fork_block_number}\n\ latest block number: {latest_block}" ); // If the `eth_getBlockByNumber` call succeeds, but returns null instead of // the block, and the block number is less than equal the latest block, then // the user is forking from a non-archive node with an older block number. if fork_block_number <= latest_block { message.push_str(&format!("\n{NON_ARCHIVE_NODE_WARNING}")); } eyre::bail!("{message}"); } eyre::bail!("failed to get block for block number: {fork_block_number}") }; let gas_limit = self.fork_gas_limit(&block); self.gas_limit = Some(gas_limit); env.evm_env.block_env = BlockEnv { gas_limit, // Keep previous `coinbase` and `basefee` value beneficiary: env.evm_env.block_env.beneficiary, basefee: env.evm_env.block_env.basefee, ..block_env_from_header(&block.header) }; // Determine chain_id early so we can use it consistently let chain_id = if let Some(chain_id) = self.chain_id { chain_id } else { let chain_id = if let Some(fork_chain_id) = fork_chain_id { fork_chain_id.to() } else { provider.get_chain_id().await.wrap_err("failed to fetch network chain ID")? }; // need to update the dev signers and env with the chain id self.set_chain_id(Some(chain_id)); env.evm_env.cfg_env.chain_id = chain_id; env.tx.base.chain_id = chain_id.into(); chain_id }; // if not set explicitly we use the base fee of the latest block if self.base_fee.is_none() && let Some(base_fee) = block.header.base_fee_per_gas() { self.base_fee = Some(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(), gas_limit, block.header.base_fee_per_gas().unwrap_or_default(), ); // update next base fee fees.set_base_fee(next_block_base_fee); } if let (Some(blob_excess_gas), Some(blob_gas_used)) = (block.header.excess_blob_gas(), block.header.blob_gas_used()) { // Derive blob params using the fork block timestamp regardless of explicit base fee. let blob_params = get_blob_params(chain_id, block.header.timestamp()); env.evm_env.block_env.blob_excess_gas_and_price = Some(BlobExcessGasAndPrice::new( blob_excess_gas, blob_params.update_fraction as u64, )); fees.set_blob_params(blob_params); 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, blob_params.update_fraction as u64, )); } // use remote gas price if self.gas_price.is_none() && let Ok(gas_price) = provider.get_gas_price().await { self.gas_price = Some(gas_price); fees.set_gas_price(gas_price); } let block_hash = block.header.hash; 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::( &mut env.evm_env, &block, self.networks, ); 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 { BlockchainDb::new(meta, self.block_cache_path(fork_block_number)) }; // This will spawn the background thread that will use the provider to fetch // blockchain data from the other client let backend = SharedBackend::spawn_backend( Arc::clone(&provider), block_chain_db.clone(), Some(fork_block_number.into()), ) .await; let config = ClientForkConfig { eth_rpc_url, block_number: fork_block_number, block_hash, transaction_hash: self.fork_choice.and_then(|fc| fc.transaction_hash()), provider, chain_id, override_chain_id, timestamp: block.header.timestamp(), base_fee: block.header.base_fee_per_gas().map(|g| g as u128), timeout: self.fork_request_timeout, retries: self.fork_request_retries, backoff: self.fork_retry_backoff, 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.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 db.insert_block_hash(U256::from(config.block_number), config.block_hash); Ok((db, config)) } /// we only use the gas limit value of the block if it is non-zero and the block gas /// limit is enabled, since there are networks where this is not used and is always /// `0x0` which would inevitably result in `OutOfGas` errors as soon as the evm is about to record gas, See also pub(crate) fn fork_gas_limit>(&self, block: &B) -> 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(); } } 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) -> u64 { if self.disable_block_gas_limit { return u64::MAX; } self.gas_limit.unwrap_or(DEFAULT_GAS_LIMIT) } } /// If the fork choice is a block number, simply return it with an empty list of transactions. /// If the fork choice is a transaction hash, determine the block that the transaction was mined in, /// and return the block number before the fork block along with all transactions in the fork block /// that are before (and including) the fork transaction. async fn derive_block_and_transactions( fork_choice: &ForkChoice, provider: &Arc, ) -> eyre::Result<(BlockNumber, Option>>)> { match fork_choice { ForkChoice::Block(block_number) => { let block_number = *block_number; if block_number >= 0 { return Ok((block_number as u64, None)); } // subtract from latest block number let latest = provider.get_block_number().await?; Ok((block_number.saturating_add(latest as i128) as u64, None)) } ForkChoice::Transaction(transaction_hash) => { // Determine the block that this transaction was mined in let transaction = provider .get_transaction_by_hash(transaction_hash.0.into()) .await? .ok_or_else(|| eyre::eyre!("failed to get fork transaction by hash"))?; let transaction_block_number = transaction.block_number().ok_or_else(|| { eyre::eyre!("fork transaction is not mined yet (no block number)") })?; // Get the block pertaining to the fork transaction let transaction_block = provider .get_block_by_number(transaction_block_number.into()) .full() .await? .ok_or_else(|| eyre::eyre!("failed to get fork block by number"))?; // Filter out transactions that are after the fork transaction let filtered_transactions = transaction_block .transactions .as_transactions() .ok_or_else(|| eyre::eyre!("failed to get transactions from full fork block"))? .iter() .take_while_inclusive(|&transaction| transaction.tx_hash() != transaction_hash.0) .collect::>(); // Convert the transactions to PoolTransactions let force_transactions = filtered_transactions .iter() .map(|&transaction| PoolTransaction::try_from(transaction.clone())) .collect::, _>>() .map_err(|e| eyre::eyre!("Err converting to pool transactions {e}"))?; Ok((transaction_block_number.saturating_sub(1), Some(force_transactions))) } } } /// Fork delimiter used to specify which block or transaction to fork from. #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum ForkChoice { /// Block number to fork from. /// /// If negative, the given value is subtracted from the `latest` block number. Block(i128), /// Transaction hash to fork from. Transaction(TxHash), } impl ForkChoice { /// Returns the block number to fork from pub fn block_number(&self) -> Option { match self { Self::Block(block_number) => Some(*block_number), Self::Transaction(_) => None, } } /// Returns the transaction hash to fork from pub fn transaction_hash(&self) -> Option { match self { Self::Block(_) => None, Self::Transaction(transaction_hash) => Some(*transaction_hash), } } } /// Convert a transaction hash into a ForkChoice impl From for ForkChoice { fn from(tx_hash: TxHash) -> Self { Self::Transaction(tx_hash) } } /// Convert a decimal block number into a ForkChoice impl From for ForkChoice { fn from(block: u64) -> Self { Self::Block(block as i128) } } #[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] pub struct PruneStateHistoryConfig { pub enabled: bool, pub max_memory_history: Option, } impl PruneStateHistoryConfig { /// Returns `true` if writing state history is supported pub fn is_state_history_supported(&self) -> bool { !self.enabled || self.max_memory_history.is_some() } /// Returns true if this setting was enabled. pub fn is_config_enabled(&self) -> bool { self.enabled } pub fn from_args(val: Option>) -> Self { val.map(|max_memory_history| Self { enabled: true, max_memory_history }).unwrap_or_default() } } /// Can create dev accounts #[derive(Clone, Debug)] pub struct AccountGenerator { chain_id: u64, amount: usize, phrase: String, derivation_path: Option, } impl AccountGenerator { pub fn new(amount: usize) -> Self { Self { chain_id: CHAIN_ID, amount, phrase: Mnemonic::::new(&mut thread_rng()).to_phrase(), derivation_path: None, } } #[must_use] pub fn phrase(mut self, phrase: impl Into) -> Self { self.phrase = phrase.into(); self } fn get_phrase(&self) -> &str { &self.phrase } #[must_use] pub fn chain_id(mut self, chain_id: impl Into) -> Self { self.chain_id = chain_id.into(); self } #[must_use] pub fn derivation_path(mut self, derivation_path: impl Into) -> Self { let mut derivation_path = derivation_path.into(); if !derivation_path.ends_with('/') { derivation_path.push('/'); } self.derivation_path = Some(derivation_path); self } fn get_derivation_path(&self) -> &str { self.derivation_path.as_deref().unwrap_or("m/44'/60'/0'/0/") } } impl AccountGenerator { pub fn generate(&self) -> eyre::Result> { let builder = MnemonicBuilder::::default().phrase(self.phrase.as_str()); // use the derivation path let derivation_path = self.get_derivation_path(); let mut wallets = Vec::with_capacity(self.amount); for idx in 0..self.amount { let builder = builder.clone().derivation_path(format!("{derivation_path}{idx}")).unwrap(); let wallet = builder.build()?.with_chain_id(Some(self.chain_id)); wallets.push(wallet) } Ok(wallets) } } /// Returns the path to anvil dir `~/.foundry/anvil` pub fn anvil_dir() -> Option { Config::foundry_dir().map(|p| p.join("anvil")) } /// Returns the root path to anvil's temporary storage `~/.foundry/anvil/` pub fn anvil_tmp_dir() -> Option { anvil_dir().map(|p| p.join("tmp")) } /// Finds the latest appropriate block to fork /// /// This fetches the "latest" block and checks whether the `Block` is fully populated (`hash` field /// is present). This prevents edge cases where anvil forks the "latest" block but `eth_getBlockByNumber` still returns a pending block, async fn find_latest_fork_block>( provider: P, ) -> Result { let mut num = provider.get_block_number().await?; // walk back from the head of the chain, but at most 2 blocks, which should be more than enough // leeway for _ in 0..2 { if let Some(block) = provider.get_block(num.into()).await? && !block.header.hash.is_zero() { break; } // block not actually finalized, so we try the block before num = num.saturating_sub(1) } Ok(num) } #[cfg(test)] mod tests { use super::*; #[test] fn test_prune_history() { let config = PruneStateHistoryConfig::default(); assert!(config.is_state_history_supported()); let config = PruneStateHistoryConfig::from_args(Some(None)); assert!(!config.is_state_history_supported()); let config = PruneStateHistoryConfig::from_args(Some(Some(10))); assert!(config.is_state_history_supported()); } } ================================================ FILE: crates/anvil/src/error.rs ================================================ /// Result alias pub type NodeResult = Result; /// An error that can occur when launching a anvil instance #[derive(Debug, thiserror::Error)] pub enum NodeError { #[error(transparent)] Hyper(#[from] hyper::Error), #[error(transparent)] Io(#[from] std::io::Error), } ================================================ FILE: crates/anvil/src/eth/api.rs ================================================ use super::{ backend::mem::{BlockRequest, DatabaseRef, State}, sign::build_impersonated, }; use crate::{ ClientFork, LoggingManager, Miner, MiningMode, StorageInfo, eth::{ backend::{ self, db::SerializableState, mem::{MIN_CREATE_GAS, MIN_TRANSACTION_GAS}, notifications::NewBlockNotifications, validate::TransactionValidator, }, error::{ BlockchainError, FeeHistoryError, InvalidTransactionError, Result, ToRpcResponseResult, }, fees::{FeeDetails, FeeHistoryCache, MIN_SUGGESTED_PRIORITY_FEE}, macros::node_info, miner::FixedBlockTimeMiner, pool::{ Pool, transactions::{ PoolTransaction, TransactionOrder, TransactionPriority, TxMarker, to_marker, }, }, sign::{self, Signer}, }, filter::{EthFilter, Filters, LogsFilter}, mem::transaction_build, }; use alloy_consensus::{ Blob, BlockHeader, Transaction, TrieAccount, TxEip4844Variant, transaction::Recovered, }; use alloy_dyn_abi::TypedData; use alloy_eips::{ eip2718::Encodable2718, eip7910::{EthConfig, EthForkConfig}, }; use alloy_evm::overrides::{OverrideBlockHashes, apply_state_overrides}; use alloy_network::{ AnyRpcBlock, AnyRpcTransaction, BlockResponse, Network, ReceiptResponse, TransactionBuilder, TransactionBuilder4844, TransactionResponse, eip2718::Decodable2718, }; use alloy_primitives::{ Address, B64, B256, Bytes, TxHash, TxKind, U64, U256, map::{HashMap, HashSet}, }; use alloy_rpc_types::{ AccessList, AccessListResult, BlockId, BlockNumberOrTag as BlockNumber, BlockTransactions, EIP1186AccountProofResponse, FeeHistory, Filter, FilteredParams, Index, Log, Work, anvil::{ ForkedNetwork, Forking, Metadata, MineOptions, NodeEnvironment, NodeForkConfig, NodeInfo, }, request::TransactionRequest, simulate::{SimulatePayload, SimulatedBlock}, state::{AccountOverride, EvmOverrides, StateOverridesBuilder}, trace::{ filter::TraceFilter, geth::{GethDebugTracingCallOptions, GethDebugTracingOptions, GethTrace}, parity::{LocalizedTransactionTrace, TraceResultsWithTransactionHash, TraceType}, }, txpool::{TxpoolContent, TxpoolInspect, TxpoolInspectSummary, TxpoolStatus}, }; use alloy_rpc_types_eth::FillTransaction; use alloy_serde::WithOtherFields; use alloy_sol_types::{SolCall, SolValue, sol}; use alloy_transport::TransportErrorKind; use anvil_core::{ eth::{ EthRequest, block::BlockInfo, transaction::{MaybeImpersonatedTransaction, PendingTransaction}, }, types::{ReorgOptions, TransactionData}, }; use anvil_rpc::{error::RpcError, response::ResponseResult}; use foundry_common::provider::ProviderBuilder; use foundry_evm::decode::RevertDecoder; use foundry_primitives::{ FoundryNetwork, FoundryTransactionRequest, FoundryTxEnvelope, FoundryTxReceipt, FoundryTxType, FoundryTypedTx, }; use futures::{ StreamExt, TryFutureExt, channel::{mpsc::Receiver, oneshot}, }; use parking_lot::RwLock; use revm::{ context::BlockEnv, context_interface::{block::BlobExcessGasAndPrice, result::Output}, database::CacheDB, interpreter::{InstructionResult, return_ok, return_revert}, primitives::eip7702::PER_EMPTY_ACCOUNT_COST, }; use std::{sync::Arc, time::Duration}; use tokio::{ sync::mpsc::{UnboundedReceiver, unbounded_channel}, try_join, }; /// The client version: `anvil/v{major}.{minor}.{patch}` pub const CLIENT_VERSION: &str = concat!("anvil/v", env!("CARGO_PKG_VERSION")); /// The entry point for executing eth api RPC call - The Eth RPC interface. /// /// This type is cheap to clone and can be used concurrently pub struct EthApi { /// The transaction pool pool: Arc>, /// Holds all blockchain related data /// In-Memory only for now pub backend: Arc>, /// Whether this node is mining is_mining: bool, /// available signers signers: Arc>>>, /// data required for `eth_feeHistory` fee_history_cache: FeeHistoryCache, /// max number of items kept in fee cache fee_history_limit: u64, /// access to the actual miner /// /// This access is required in order to adjust miner settings based on requests received from /// custom RPC endpoints miner: Miner, /// allows to enabled/disable logging logger: LoggingManager, /// Tracks all active filters filters: Filters, /// How transactions are ordered in the pool transaction_order: Arc>, /// Whether we're listening for RPC calls net_listening: bool, /// The instance ID. Changes on every reset. instance_id: Arc>, } impl Clone for EthApi { fn clone(&self) -> Self { Self { pool: self.pool.clone(), backend: self.backend.clone(), is_mining: self.is_mining, signers: self.signers.clone(), fee_history_cache: self.fee_history_cache.clone(), fee_history_limit: self.fee_history_limit, miner: self.miner.clone(), logger: self.logger.clone(), filters: self.filters.clone(), transaction_order: self.transaction_order.clone(), net_listening: self.net_listening, instance_id: self.instance_id.clone(), } } } // == impl EthApi generic methods == impl EthApi { /// Creates a new instance #[expect(clippy::too_many_arguments)] pub fn new( pool: Arc>, backend: Arc>, signers: Arc>>>, fee_history_cache: FeeHistoryCache, fee_history_limit: u64, miner: Miner, logger: LoggingManager, filters: Filters, transactions_order: TransactionOrder, ) -> Self { Self { pool, backend, is_mining: true, signers, fee_history_cache, fee_history_limit, miner, logger, filters, net_listening: true, transaction_order: Arc::new(RwLock::new(transactions_order)), instance_id: Arc::new(RwLock::new(B256::random())), } } /// Returns the current gas price pub fn gas_price(&self) -> u128 { if self.backend.is_eip1559() { if self.backend.is_min_priority_fee_enforced() { (self.backend.base_fee() as u128).saturating_add(self.lowest_suggestion_tip()) } else { self.backend.base_fee() as u128 } } else { self.backend.fees().raw_gas_price() } } /// Returns the suggested fee cap. /// /// Returns at least [MIN_SUGGESTED_PRIORITY_FEE] fn lowest_suggestion_tip(&self) -> u128 { let block_number = self.backend.best_number(); let latest_cached_block = self.fee_history_cache.lock().get(&block_number).cloned(); match latest_cached_block { Some(block) => block.rewards.iter().copied().min(), None => self.fee_history_cache.lock().values().flat_map(|b| b.rewards.clone()).min(), } .map(|fee| fee.max(MIN_SUGGESTED_PRIORITY_FEE)) .unwrap_or(MIN_SUGGESTED_PRIORITY_FEE) } /// Returns true if auto mining is enabled, and false. /// /// Handler for ETH RPC call: `anvil_getAutomine` pub fn anvil_get_auto_mine(&self) -> Result { node_info!("anvil_getAutomine"); Ok(self.miner.is_auto_mine()) } /// Returns the value of mining interval, if set. /// /// Handler for ETH RPC call: `anvil_getIntervalMining`. pub fn anvil_get_interval_mining(&self) -> Result> { node_info!("anvil_getIntervalMining"); Ok(self.miner.get_interval()) } /// Enables or disables, based on the single boolean argument, the automatic mining of new /// blocks with each new transaction submitted to the network. /// /// Handler for ETH RPC call: `evm_setAutomine` pub async fn anvil_set_auto_mine(&self, enable_automine: bool) -> Result<()> { node_info!("evm_setAutomine"); if self.miner.is_auto_mine() { if enable_automine { return Ok(()); } self.miner.set_mining_mode(MiningMode::None); } else if enable_automine { let listener = self.pool.add_ready_listener(); let mode = MiningMode::instant(1_000, listener); self.miner.set_mining_mode(mode); } Ok(()) } /// Sets the mining behavior to interval with the given interval (seconds) /// /// Handler for ETH RPC call: `evm_setIntervalMining` pub fn anvil_set_interval_mining(&self, secs: u64) -> Result<()> { node_info!("evm_setIntervalMining"); let mining_mode = if secs == 0 { MiningMode::None } else { let block_time = Duration::from_secs(secs); // This ensures that memory limits are stricter in interval-mine mode self.backend.update_interval_mine_block_time(block_time); MiningMode::FixedBlockTime(FixedBlockTimeMiner::new(block_time)) }; self.miner.set_mining_mode(mining_mode); Ok(()) } /// Removes transactions from the pool /// /// Handler for RPC call: `anvil_dropTransaction` pub async fn anvil_drop_transaction(&self, tx_hash: B256) -> Result> { node_info!("anvil_dropTransaction"); Ok(self.pool.drop_transaction(tx_hash).map(|tx| tx.hash())) } /// Removes all transactions from the pool /// /// Handler for RPC call: `anvil_dropAllTransactions` pub async fn anvil_drop_all_transactions(&self) -> Result<()> { node_info!("anvil_dropAllTransactions"); self.pool.clear(); Ok(()) } pub async fn anvil_set_chain_id(&self, chain_id: u64) -> Result<()> { node_info!("anvil_setChainId"); self.backend.set_chain_id(chain_id); Ok(()) } /// Modifies the balance of an account. /// /// Handler for RPC call: `anvil_setBalance` pub async fn anvil_set_balance(&self, address: Address, balance: U256) -> Result<()> { node_info!("anvil_setBalance"); self.backend.set_balance(address, balance).await?; Ok(()) } /// Sets the code of a contract. /// /// Handler for RPC call: `anvil_setCode` pub async fn anvil_set_code(&self, address: Address, code: Bytes) -> Result<()> { node_info!("anvil_setCode"); self.backend.set_code(address, code).await?; Ok(()) } /// Sets the nonce of an address. /// /// Handler for RPC call: `anvil_setNonce` pub async fn anvil_set_nonce(&self, address: Address, nonce: U256) -> Result<()> { node_info!("anvil_setNonce"); self.backend.set_nonce(address, nonce).await?; Ok(()) } /// Writes a single slot of the account's storage. /// /// Handler for RPC call: `anvil_setStorageAt` pub async fn anvil_set_storage_at( &self, address: Address, slot: U256, val: B256, ) -> Result { node_info!("anvil_setStorageAt"); self.backend.set_storage_at(address, slot, val).await?; Ok(true) } /// Enable or disable logging. /// /// Handler for RPC call: `anvil_setLoggingEnabled` pub async fn anvil_set_logging(&self, enable: bool) -> Result<()> { node_info!("anvil_setLoggingEnabled"); self.logger.set_enabled(enable); Ok(()) } /// Set the minimum gas price for the node. /// /// Handler for RPC call: `anvil_setMinGasPrice` pub async fn anvil_set_min_gas_price(&self, gas: U256) -> Result<()> { node_info!("anvil_setMinGasPrice"); if self.backend.is_eip1559() { return Err(RpcError::invalid_params( "anvil_setMinGasPrice is not supported when EIP-1559 is active", ) .into()); } self.backend.set_gas_price(gas.to()); Ok(()) } /// Sets the base fee of the next block. /// /// Handler for RPC call: `anvil_setNextBlockBaseFeePerGas` pub async fn anvil_set_next_block_base_fee_per_gas(&self, basefee: U256) -> Result<()> { node_info!("anvil_setNextBlockBaseFeePerGas"); if !self.backend.is_eip1559() { return Err(RpcError::invalid_params( "anvil_setNextBlockBaseFeePerGas is only supported when EIP-1559 is active", ) .into()); } self.backend.set_base_fee(basefee.to()); Ok(()) } /// Sets the coinbase address. /// /// Handler for RPC call: `anvil_setCoinbase` pub async fn anvil_set_coinbase(&self, address: Address) -> Result<()> { node_info!("anvil_setCoinbase"); self.backend.set_coinbase(address); Ok(()) } /// Retrieves the Anvil node configuration params. /// /// Handler for RPC call: `anvil_nodeInfo` pub async fn anvil_node_info(&self) -> Result { node_info!("anvil_nodeInfo"); 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.evm_env.cfg_env.spec.into(); Ok(NodeInfo { current_block_number: self.backend.best_number(), current_block_timestamp: env.evm_env.block_env.timestamp.saturating_to(), current_block_hash: self.backend.best_hash(), hard_fork: hard_fork.to_string(), transaction_order: match *tx_order { TransactionOrder::Fifo => "fifo".to_string(), TransactionOrder::Fees => "fees".to_string(), }, environment: NodeEnvironment { base_fee: self.backend.base_fee() as u128, chain_id: self.backend.chain_id().to::(), gas_limit: self.backend.gas_limit(), gas_price: self.gas_price(), }, fork_config: fork_config .map(|fork| { let config = fork.config.read(); NodeForkConfig { fork_url: Some(config.eth_rpc_url.clone()), fork_block_number: Some(config.block_number), fork_retry_backoff: Some(config.backoff.as_millis()), } }) .unwrap_or_default(), }) } /// Retrieves metadata about the Anvil instance. /// /// Handler for RPC call: `anvil_metadata` pub async fn anvil_metadata(&self) -> Result { node_info!("anvil_metadata"); let fork_config = self.backend.get_fork(); Ok(Metadata { client_version: CLIENT_VERSION.to_string(), chain_id: self.backend.chain_id().to::(), latest_block_hash: self.backend.best_hash(), latest_block_number: self.backend.best_number(), instance_id: *self.instance_id.read(), forked_network: fork_config.map(|cfg| ForkedNetwork { chain_id: cfg.chain_id(), fork_block_number: cfg.block_number(), fork_block_hash: cfg.block_hash(), }), snapshots: self.backend.list_state_snapshots(), }) } pub async fn anvil_remove_pool_transactions(&self, address: Address) -> Result<()> { node_info!("anvil_removePoolTransactions"); self.pool.remove_transactions_by_address(address); Ok(()) } /// Snapshot the state of the blockchain at the current block. /// /// Handler for RPC call: `evm_snapshot` pub async fn evm_snapshot(&self) -> Result { node_info!("evm_snapshot"); Ok(self.backend.create_state_snapshot().await) } /// Jump forward in time by the given amount of time, in seconds. /// /// Handler for RPC call: `evm_increaseTime` pub async fn evm_increase_time(&self, seconds: U256) -> Result { node_info!("evm_increaseTime"); Ok(self.backend.time().increase_time(seconds.try_into().unwrap_or(u64::MAX)) as i64) } /// Similar to `evm_increaseTime` but takes the exact timestamp that you want in the next block /// /// Handler for RPC call: `evm_setNextBlockTimestamp` pub fn evm_set_next_block_timestamp(&self, seconds: u64) -> Result<()> { node_info!("evm_setNextBlockTimestamp"); self.backend.time().set_next_block_timestamp(seconds) } /// Sets the specific timestamp and returns the number of seconds between the given timestamp /// and the current time. /// /// Handler for RPC call: `evm_setTime` pub fn evm_set_time(&self, timestamp: u64) -> Result { node_info!("evm_setTime"); let now = self.backend.time().current_call_timestamp(); self.backend.time().reset(timestamp); // number of seconds between the given timestamp and the current time. let offset = timestamp.saturating_sub(now); Ok(Duration::from_millis(offset).as_secs()) } /// Set the next block gas limit /// /// Handler for RPC call: `evm_setBlockGasLimit` pub fn evm_set_block_gas_limit(&self, gas_limit: U256) -> Result { node_info!("evm_setBlockGasLimit"); self.backend.set_gas_limit(gas_limit.to()); Ok(true) } /// Sets an interval for the block timestamp /// /// Handler for RPC call: `anvil_setBlockTimestampInterval` pub fn evm_set_block_timestamp_interval(&self, seconds: u64) -> Result<()> { node_info!("anvil_setBlockTimestampInterval"); self.backend.time().set_block_timestamp_interval(seconds); Ok(()) } /// Sets an interval for the block timestamp /// /// Handler for RPC call: `anvil_removeBlockTimestampInterval` pub fn evm_remove_block_timestamp_interval(&self) -> Result { node_info!("anvil_removeBlockTimestampInterval"); Ok(self.backend.time().remove_block_timestamp_interval()) } /// Sets the backend rpc url /// /// Handler for ETH RPC call: `anvil_setRpcUrl` pub fn anvil_set_rpc_url(&self, url: String) -> Result<()> { node_info!("anvil_setRpcUrl"); if let Some(fork) = self.backend.get_fork() { let mut config = fork.config.write(); // let interval = config.provider.get_interval(); let new_provider = Arc::new( ProviderBuilder::new(&url).max_retry(10).initial_backoff(1000).build().map_err( |_| { TransportErrorKind::custom_str( format!("Failed to parse invalid url {url}").as_str(), ) }, // TODO: Add interval )?, // .interval(interval), ); config.provider = new_provider; trace!(target: "backend", "Updated fork rpc from \"{}\" to \"{}\"", config.eth_rpc_url, url); config.eth_rpc_url = url; } Ok(()) } /// Returns the number of transactions currently pending for inclusion in the next block(s), as /// well as the ones that are being scheduled for future execution only. /// Ref: [Here](https://geth.ethereum.org/docs/rpc/ns-txpool#txpool_status) /// /// Handler for ETH RPC call: `txpool_status` pub async fn txpool_status(&self) -> Result { node_info!("txpool_status"); Ok(self.pool.txpool_status()) } /// Executes the future on a new blocking task. async fn on_blocking_task(&self, c: C) -> Result where C: FnOnce(Self) -> F, F: Future> + Send + 'static, R: Send + 'static, { let (tx, rx) = oneshot::channel(); let this = self.clone(); let f = c(this); tokio::task::spawn_blocking(move || { tokio::runtime::Handle::current().block_on(async move { let res = f.await; let _ = tx.send(res); }) }); rx.await.map_err(|_| BlockchainError::Internal("blocking task panicked".to_string()))? } /// Updates the `TransactionOrder` pub fn set_transaction_order(&self, order: TransactionOrder) { *self.transaction_order.write() = order; } /// Returns the chain ID used for transaction pub fn chain_id(&self) -> u64 { self.backend.chain_id().to::() } /// Returns the configured fork, if any. pub fn get_fork(&self) -> Option { self.backend.get_fork() } /// Returns the current instance's ID. pub fn instance_id(&self) -> B256 { *self.instance_id.read() } /// Resets the instance ID. pub fn reset_instance_id(&self) { *self.instance_id.write() = B256::random(); } /// Returns the first signer that can sign for the given address #[expect(clippy::borrowed_box)] pub fn get_signer(&self, address: Address) -> Option<&Box>> { self.signers.iter().find(|signer| signer.is_signer_for(address)) } /// Returns a new listeners for ready transactions pub fn new_ready_transactions(&self) -> Receiver { self.pool.add_ready_listener() } /// Returns true if forked pub fn is_fork(&self) -> bool { self.backend.is_fork() } /// Returns the current state root pub async fn state_root(&self) -> Option { self.backend.get_db().read().await.maybe_state_root() } /// Returns true if the `addr` is currently impersonated pub fn is_impersonated(&self, addr: Address) -> bool { self.backend.cheats().is_impersonated(addr) } /// Returns a new accessor for certain storage elements pub fn storage_info(&self) -> StorageInfo { StorageInfo::new(Arc::clone(&self.backend)) } } // == impl EthApi anvil endpoints == impl EthApi { /// Reset the fork to a fresh forked state, and optionally update the fork config. /// /// If `forking` is `None` then this will disable forking entirely. /// /// Handler for RPC call: `anvil_reset` pub async fn anvil_reset(&self, forking: Option) -> Result<()> { self.reset_instance_id(); node_info!("anvil_reset"); if let Some(forking) = forking { // if we're resetting the fork we need to reset the instance id self.backend.reset_fork(forking).await?; } else { // Reset to a fresh in-memory state self.backend.reset_to_in_mem().await?; } // Clear pending transactions since they reference the old chain state. self.pool.clear(); Ok(()) } /// Create a buffer that represents all state on the chain, which can be loaded to separate /// process by calling `anvil_loadState` /// /// Handler for RPC call: `anvil_dumpState` pub async fn anvil_dump_state( &self, preserve_historical_states: Option, ) -> Result { node_info!("anvil_dumpState"); self.backend.dump_state(preserve_historical_states.unwrap_or(false)).await } /// Returns the current state pub async fn serialized_state( &self, preserve_historical_states: bool, ) -> Result { self.backend.serialized_state(preserve_historical_states).await } /// Append chain state buffer to current chain. Will overwrite any conflicting addresses or /// storage. /// /// Handler for RPC call: `anvil_loadState` pub async fn anvil_load_state(&self, buf: Bytes) -> Result { node_info!("anvil_loadState"); self.backend.load_state_bytes(buf).await } /// Revert the state of the blockchain to a previous snapshot. /// Takes a single parameter, which is the snapshot id to revert to. /// /// Handler for RPC call: `evm_revert` pub async fn evm_revert(&self, id: U256) -> Result { node_info!("evm_revert"); self.backend.revert_state_snapshot(id).await } async fn block_request( &self, block_number: Option, ) -> Result> { let block_request = match block_number { Some(BlockId::Number(BlockNumber::Pending)) => { let pending_txs = self.pool.ready_transactions().collect(); BlockRequest::Pending(pending_txs) } _ => { let number = self.backend.ensure_block_number(block_number).await?; BlockRequest::Number(number) } }; Ok(block_request) } /// 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.saturating_add(balance)).await?; Ok(()) } /// Rollback the chain to a specific depth. /// /// e.g depth = 3 /// A -> B -> C -> D -> E /// A -> B /// /// Depth specifies the height to rollback the chain back to. Depth must not exceed the current /// chain height, i.e. can't rollback past the genesis block. /// /// Handler for RPC call: `anvil_rollback` pub async fn anvil_rollback(&self, depth: Option) -> Result<()> { node_info!("anvil_rollback"); let depth = depth.unwrap_or(1); // Check reorg depth doesn't exceed current chain height let current_height = self.backend.best_number(); let common_height = current_height.checked_sub(depth).ok_or(BlockchainError::RpcError( RpcError::invalid_params(format!( "Rollback depth must not exceed current chain height: current height {current_height}, depth {depth}" )), ))?; // Get the common ancestor block let common_block = self.backend.get_block(common_height).ok_or(BlockchainError::BlockNotFound)?; self.backend.rollback(common_block).await?; Ok(()) } /// Estimates the gas usage of the `request` with the state. /// /// This will execute the transaction request and find the best gas limit via binary search. fn do_estimate_gas_with_state( &self, mut request: WithOtherFields, state: &dyn DatabaseRef, block_env: BlockEnv, ) -> Result { // If the request is a simple native token transfer we can optimize // We assume it's a transfer if we have no input data. 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() || 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(); if maybe_transfer && let Some(to) = to && let Ok(target_code) = self.backend.get_code_with_state(&state, *to) && target_code.as_ref().is_empty() { return Ok(MIN_TRANSACTION_GAS); } let fees = FeeDetails::new( request.gas_price, request.max_fee_per_gas, request.max_priority_fee_per_gas, request.max_fee_per_blob_gas, )? .or_zero_fees(); // 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.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 if gas_price > 0 && let Some(from) = request.from { let mut available_funds = self.backend.get_balance_with_state(state, from)?; if let Some(value) = request.value { if value > available_funds { return Err(InvalidTransactionError::InsufficientFunds.into()); } // safe: value < available_funds available_funds -= value; } // amount of gas the sender can afford with the `gas_price` let allowance = available_funds.checked_div(U256::from(gas_price)).unwrap_or_default(); highest_gas_limit = std::cmp::min(highest_gas_limit, allowance.saturating_to()); } let mut call_to_estimate = request.clone(); call_to_estimate.gas = Some(highest_gas_limit as u64); // execute the call without writing to db let ethres = self.backend.call_with_state(&state, call_to_estimate, fees.clone(), block_env.clone()); let gas_used = match ethres.try_into()? { GasEstimationCallResult::Success(gas) => Ok(gas), GasEstimationCallResult::OutOfGas => { Err(InvalidTransactionError::BasicOutOfGas(highest_gas_limit).into()) } GasEstimationCallResult::Revert(output) => { Err(InvalidTransactionError::Revert(output).into()) } GasEstimationCallResult::EvmError(err) => { warn!(target: "node", "estimation failed due to {:?}", err); Err(BlockchainError::EvmError(err)) } }?; // at this point we know the call succeeded but want to find the _best_ (lowest) gas the // transaction succeeds with. we find this by doing a binary search over the // possible range NOTE: this is the gas the transaction used, which is less than the // transaction requires to succeed // Get the starting lowest gas needed depending on the transaction kind. let mut lowest_gas_limit = determine_base_gas_by_kind(&request); // pick a point that's close to the estimated gas let mut mid_gas_limit = std::cmp::min(gas_used * 3, (highest_gas_limit + lowest_gas_limit) / 2); // Binary search for the ideal gas limit while (highest_gas_limit - lowest_gas_limit) > 1 { request.gas = Some(mid_gas_limit as u64); let ethres = self.backend.call_with_state( &state, request.clone(), fees.clone(), block_env.clone(), ); match ethres.try_into()? { GasEstimationCallResult::Success(_) => { // If the transaction succeeded, we can set a ceiling for the highest gas limit // at the current midpoint, as spending any more gas would // make no sense (as the TX would still succeed). highest_gas_limit = mid_gas_limit; } GasEstimationCallResult::OutOfGas | GasEstimationCallResult::Revert(_) | GasEstimationCallResult::EvmError(_) => { // If the transaction failed, we can set a floor for the lowest gas limit at the // current midpoint, as spending any less gas would make no // sense (as the TX would still revert due to lack of gas). // // We don't care about the reason here, as we known that transaction is correct // as it succeeded earlier lowest_gas_limit = mid_gas_limit; } }; // new midpoint mid_gas_limit = (highest_gas_limit + lowest_gas_limit) / 2; } trace!(target : "node", "Estimated Gas for call {:?}", highest_gas_limit); Ok(highest_gas_limit) } /// Returns a new block event stream that yields Notifications when a new block was added pub fn new_block_notifications(&self) -> NewBlockNotifications { self.backend.new_block_notifications() } /// Executes the [EthRequest] and returns an RPC [ResponseResult]. pub async fn execute(&self, request: EthRequest) -> ResponseResult { trace!(target: "rpc::api", "executing eth request"); let response = match request.clone() { EthRequest::EthProtocolVersion(()) => self.protocol_version().to_rpc_result(), EthRequest::Web3ClientVersion(()) => self.client_version().to_rpc_result(), EthRequest::Web3Sha3(content) => self.sha3(content).to_rpc_result(), 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() } EthRequest::EthGetTransactionByHash(hash) => { self.transaction_by_hash(hash).await.to_rpc_result() } EthRequest::EthSendTransaction(request) => { self.send_transaction(*request).await.to_rpc_result() } EthRequest::EthSendTransactionSync(request) => { self.send_transaction_sync(*request).await.to_rpc_result() } EthRequest::EthChainId(_) => self.eth_chain_id().to_rpc_result(), EthRequest::EthNetworkId(_) => self.network_id().to_rpc_result(), EthRequest::NetListening(_) => self.net_listening().to_rpc_result(), EthRequest::EthHashrate(()) => self.hashrate().to_rpc_result(), EthRequest::EthGasPrice(_) => self.eth_gas_price().to_rpc_result(), EthRequest::EthMaxPriorityFeePerGas(_) => { self.gas_max_priority_fee_per_gas().to_rpc_result() } EthRequest::EthBlobBaseFee(_) => self.blob_base_fee().to_rpc_result(), EthRequest::EthAccounts(_) => self.accounts().to_rpc_result(), EthRequest::EthBlockNumber(_) => self.block_number().to_rpc_result(), EthRequest::EthCoinbase(()) => self.author().to_rpc_result(), EthRequest::EthGetStorageAt(addr, slot, block) => { self.storage_at(addr, slot, block).await.to_rpc_result() } EthRequest::EthGetBlockByHash(hash, full) => { if full { self.block_by_hash_full(hash).await.to_rpc_result() } else { self.block_by_hash(hash).await.to_rpc_result() } } EthRequest::EthGetBlockByNumber(num, full) => { if full { self.block_by_number_full(num).await.to_rpc_result() } else { self.block_by_number(num).await.to_rpc_result() } } EthRequest::EthGetTransactionCount(addr, block) => { self.transaction_count(addr, block).await.to_rpc_result() } EthRequest::EthGetTransactionCountByHash(hash) => { self.block_transaction_count_by_hash(hash).await.to_rpc_result() } EthRequest::EthGetTransactionCountByNumber(num) => { self.block_transaction_count_by_number(num).await.to_rpc_result() } EthRequest::EthGetUnclesCountByHash(hash) => { self.block_uncles_count_by_hash(hash).await.to_rpc_result() } EthRequest::EthGetUnclesCountByNumber(num) => { self.block_uncles_count_by_number(num).await.to_rpc_result() } EthRequest::EthGetCodeAt(addr, block) => { self.get_code(addr, block).await.to_rpc_result() } EthRequest::EthGetProof(addr, keys, block) => { self.get_proof(addr, keys, block).await.to_rpc_result() } EthRequest::EthSign(addr, content) => self.sign(addr, content).await.to_rpc_result(), EthRequest::PersonalSign(content, addr) => { self.sign(addr, content).await.to_rpc_result() } EthRequest::EthSignTransaction(request) => { self.sign_transaction(*request).await.to_rpc_result() } EthRequest::EthSignTypedData(addr, data) => { self.sign_typed_data(addr, data).await.to_rpc_result() } EthRequest::EthSignTypedDataV3(addr, data) => { self.sign_typed_data_v3(addr, data).await.to_rpc_result() } EthRequest::EthSignTypedDataV4(addr, data) => { self.sign_typed_data_v4(addr, &data).await.to_rpc_result() } EthRequest::EthSendRawTransaction(tx) => { self.send_raw_transaction(tx).await.to_rpc_result() } EthRequest::EthSendRawTransactionSync(tx) => { self.send_raw_transaction_sync(tx).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, state_override, block_overrides) => self .estimate_gas(call, block, EvmOverrides::new(state_override, block_overrides)) .await .to_rpc_result(), EthRequest::EthFillTransaction(request) => { self.fill_transaction(request).await.to_rpc_result() } EthRequest::EthGetRawTransactionByHash(hash) => { self.raw_transaction(hash).await.to_rpc_result() } EthRequest::GetBlobByHash(hash) => { self.anvil_get_blob_by_versioned_hash(hash).to_rpc_result() } EthRequest::GetBlobByTransactionHash(hash) => { self.anvil_get_blob_by_tx_hash(hash).to_rpc_result() } EthRequest::GetGenesisTime(()) => self.anvil_get_genesis_time().to_rpc_result(), EthRequest::EthGetRawTransactionByBlockHashAndIndex(hash, index) => { self.raw_transaction_by_block_hash_and_index(hash, index).await.to_rpc_result() } EthRequest::EthGetRawTransactionByBlockNumberAndIndex(num, index) => { self.raw_transaction_by_block_number_and_index(num, index).await.to_rpc_result() } EthRequest::EthGetTransactionByBlockHashAndIndex(hash, index) => { self.transaction_by_block_hash_and_index(hash, index).await.to_rpc_result() } EthRequest::EthGetTransactionByBlockNumberAndIndex(num, index) => { self.transaction_by_block_number_and_index(num, index).await.to_rpc_result() } EthRequest::EthGetTransactionReceipt(tx) => { self.transaction_receipt(tx).await.to_rpc_result() } EthRequest::EthGetBlockReceipts(number) => { self.block_receipts(number).await.to_rpc_result() } EthRequest::EthGetUncleByBlockHashAndIndex(hash, index) => { self.uncle_by_block_hash_and_index(hash, index).await.to_rpc_result() } EthRequest::EthGetUncleByBlockNumberAndIndex(num, index) => { self.uncle_by_block_number_and_index(num, index).await.to_rpc_result() } EthRequest::EthGetLogs(filter) => self.logs(filter).await.to_rpc_result(), EthRequest::EthGetWork(_) => self.work().to_rpc_result(), EthRequest::EthSyncing(_) => self.syncing().to_rpc_result(), EthRequest::EthConfig(_) => self.config().to_rpc_result(), EthRequest::EthSubmitWork(nonce, pow, digest) => { self.submit_work(nonce, pow, digest).to_rpc_result() } EthRequest::EthSubmitHashRate(rate, id) => { self.submit_hashrate(rate, id).to_rpc_result() } EthRequest::EthFeeHistory(count, newest, reward_percentiles) => { self.fee_history(count, newest, reward_percentiles).await.to_rpc_result() } // non eth-standard rpc calls EthRequest::DebugGetRawTransaction(hash) => { self.raw_transaction(hash).await.to_rpc_result() } // non eth-standard rpc calls EthRequest::DebugTraceTransaction(tx, opts) => { self.debug_trace_transaction(tx, opts).await.to_rpc_result() } // non eth-standard rpc calls EthRequest::DebugTraceCall(tx, block, opts) => { self.debug_trace_call(tx, block, opts).await.to_rpc_result() } EthRequest::DebugCodeByHash(hash, block) => { self.debug_code_by_hash(hash, block).await.to_rpc_result() } EthRequest::DebugDbGet(key) => self.debug_db_get(key).await.to_rpc_result(), EthRequest::TraceTransaction(tx) => self.trace_transaction(tx).await.to_rpc_result(), EthRequest::TraceBlock(block) => self.trace_block(block).await.to_rpc_result(), EthRequest::TraceFilter(filter) => self.trace_filter(filter).await.to_rpc_result(), EthRequest::TraceReplayBlockTransactions(block, trace_types) => { self.trace_replay_block_transactions(block, trace_types).await.to_rpc_result() } EthRequest::ImpersonateAccount(addr) => { self.anvil_impersonate_account(addr).await.to_rpc_result() } EthRequest::StopImpersonatingAccount(addr) => { self.anvil_stop_impersonating_account(addr).await.to_rpc_result() } EthRequest::AutoImpersonateAccount(enable) => { self.anvil_auto_impersonate_account(enable).await.to_rpc_result() } EthRequest::ImpersonateSignature(signature, address) => { self.anvil_impersonate_signature(signature, address).await.to_rpc_result() } EthRequest::GetAutoMine(()) => self.anvil_get_auto_mine().to_rpc_result(), EthRequest::Mine(blocks, interval) => { self.anvil_mine(blocks, interval).await.to_rpc_result() } EthRequest::SetAutomine(enabled) => { self.anvil_set_auto_mine(enabled).await.to_rpc_result() } EthRequest::SetIntervalMining(interval) => { self.anvil_set_interval_mining(interval).to_rpc_result() } EthRequest::GetIntervalMining(()) => self.anvil_get_interval_mining().to_rpc_result(), EthRequest::DropTransaction(tx) => { self.anvil_drop_transaction(tx).await.to_rpc_result() } EthRequest::DropAllTransactions() => { self.anvil_drop_all_transactions().await.to_rpc_result() } EthRequest::Reset(fork) => { self.anvil_reset(fork.and_then(|p| p.params)).await.to_rpc_result() } 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() } EthRequest::SetNonce(addr, nonce) => { self.anvil_set_nonce(addr, nonce).await.to_rpc_result() } EthRequest::SetStorageAt(addr, slot, val) => { self.anvil_set_storage_at(addr, slot, val).await.to_rpc_result() } EthRequest::SetCoinbase(addr) => self.anvil_set_coinbase(addr).await.to_rpc_result(), EthRequest::SetChainId(id) => self.anvil_set_chain_id(id).await.to_rpc_result(), EthRequest::SetLogging(log) => self.anvil_set_logging(log).await.to_rpc_result(), EthRequest::SetMinGasPrice(gas) => { self.anvil_set_min_gas_price(gas).await.to_rpc_result() } EthRequest::SetNextBlockBaseFeePerGas(gas) => { self.anvil_set_next_block_base_fee_per_gas(gas).await.to_rpc_result() } EthRequest::DumpState(preserve_historical_states) => self .anvil_dump_state(preserve_historical_states.and_then(|s| s.params)) .await .to_rpc_result(), EthRequest::LoadState(buf) => self.anvil_load_state(buf).await.to_rpc_result(), EthRequest::NodeInfo(_) => self.anvil_node_info().await.to_rpc_result(), EthRequest::AnvilMetadata(_) => self.anvil_metadata().await.to_rpc_result(), EthRequest::EvmSnapshot(_) => self.evm_snapshot().await.to_rpc_result(), EthRequest::EvmRevert(id) => self.evm_revert(id).await.to_rpc_result(), EthRequest::EvmIncreaseTime(time) => self.evm_increase_time(time).await.to_rpc_result(), EthRequest::EvmSetNextBlockTimeStamp(time) => { if time >= U256::from(u64::MAX) { return ResponseResult::Error(RpcError::invalid_params( "The timestamp is too big", )); } let time = time.to::(); self.evm_set_next_block_timestamp(time).to_rpc_result() } EthRequest::EvmSetTime(timestamp) => { if timestamp >= U256::from(u64::MAX) { return ResponseResult::Error(RpcError::invalid_params( "The timestamp is too big", )); } let time = timestamp.to::(); self.evm_set_time(time).to_rpc_result() } EthRequest::EvmSetBlockGasLimit(gas_limit) => { self.evm_set_block_gas_limit(gas_limit).to_rpc_result() } EthRequest::EvmSetBlockTimeStampInterval(time) => { self.evm_set_block_timestamp_interval(time).to_rpc_result() } EthRequest::EvmRemoveBlockTimeStampInterval(()) => { self.evm_remove_block_timestamp_interval().to_rpc_result() } EthRequest::EvmMine(mine) => { self.evm_mine(mine.and_then(|p| p.params)).await.to_rpc_result() } EthRequest::EvmMineDetailed(mine) => { self.evm_mine_detailed(mine.and_then(|p| p.params)).await.to_rpc_result() } EthRequest::SetRpcUrl(url) => self.anvil_set_rpc_url(url).to_rpc_result(), EthRequest::EthSendUnsignedTransaction(tx) => { self.eth_send_unsigned_transaction(*tx).await.to_rpc_result() } EthRequest::EthNewFilter(filter) => self.new_filter(filter).await.to_rpc_result(), EthRequest::EthGetFilterChanges(id) => self.get_filter_changes(&id).await, EthRequest::EthNewBlockFilter(_) => self.new_block_filter().await.to_rpc_result(), EthRequest::EthNewPendingTransactionFilter(_) => { self.new_pending_transaction_filter().await.to_rpc_result() } EthRequest::EthGetFilterLogs(id) => self.get_filter_logs(&id).await.to_rpc_result(), EthRequest::EthUninstallFilter(id) => self.uninstall_filter(&id).await.to_rpc_result(), EthRequest::TxPoolStatus(_) => self.txpool_status().await.to_rpc_result(), EthRequest::TxPoolInspect(_) => self.txpool_inspect().await.to_rpc_result(), EthRequest::TxPoolContent(_) => self.txpool_content().await.to_rpc_result(), EthRequest::ErigonGetHeaderByNumber(num) => { self.erigon_get_header_by_number(num).await.to_rpc_result() } EthRequest::OtsGetApiLevel(_) => self.ots_get_api_level().await.to_rpc_result(), EthRequest::OtsGetInternalOperations(hash) => { self.ots_get_internal_operations(hash).await.to_rpc_result() } EthRequest::OtsHasCode(addr, num) => self.ots_has_code(addr, num).await.to_rpc_result(), EthRequest::OtsTraceTransaction(hash) => { self.ots_trace_transaction(hash).await.to_rpc_result() } EthRequest::OtsGetTransactionError(hash) => { self.ots_get_transaction_error(hash).await.to_rpc_result() } EthRequest::OtsGetBlockDetails(num) => { self.ots_get_block_details(num).await.to_rpc_result() } EthRequest::OtsGetBlockDetailsByHash(hash) => { self.ots_get_block_details_by_hash(hash).await.to_rpc_result() } EthRequest::OtsGetBlockTransactions(num, page, page_size) => { self.ots_get_block_transactions(num, page, page_size).await.to_rpc_result() } EthRequest::OtsSearchTransactionsBefore(address, num, page_size) => { self.ots_search_transactions_before(address, num, page_size).await.to_rpc_result() } EthRequest::OtsSearchTransactionsAfter(address, num, page_size) => { self.ots_search_transactions_after(address, num, page_size).await.to_rpc_result() } EthRequest::OtsGetTransactionBySenderAndNonce(address, nonce) => { self.ots_get_transaction_by_sender_and_nonce(address, nonce).await.to_rpc_result() } EthRequest::EthGetTransactionBySenderAndNonce(sender, nonce) => { self.transaction_by_sender_and_nonce(sender, nonce).await.to_rpc_result() } EthRequest::OtsGetContractCreator(address) => { self.ots_get_contract_creator(address).await.to_rpc_result() } EthRequest::RemovePoolTransactions(address) => { self.anvil_remove_pool_transactions(address).await.to_rpc_result() } EthRequest::Reorg(reorg_options) => { self.anvil_reorg(reorg_options).await.to_rpc_result() } EthRequest::Rollback(depth) => self.anvil_rollback(depth).await.to_rpc_result(), }; if let ResponseResult::Error(err) = &response { node_info!("\nRPC request failed:"); node_info!(" Request: {:?}", request); node_info!(" Error: {}\n", err); } response } fn sign_request(&self, from: &Address, typed_tx: FoundryTypedTx) -> Result { match typed_tx { FoundryTypedTx::Deposit(_) => return Ok(build_impersonated(typed_tx)), _ => { for signer in self.signers.iter() { if signer.accounts().contains(from) { return signer.sign_transaction_from(from, typed_tx); } } } } Err(BlockchainError::NoSignerAvailable) } async fn inner_raw_transaction(&self, hash: B256) -> Result> { match self.pool.get_transaction(hash) { Some(tx) => Ok(Some(tx.transaction.encoded_2718().into())), None => match self.backend.transaction_by_hash(hash).await? { Some(tx) => Ok(Some(tx.as_ref().encoded_2718().into())), None => Ok(None), }, } } /// Returns the current client version. /// /// Handler for ETH RPC call: `web3_clientVersion` pub fn client_version(&self) -> Result { node_info!("web3_clientVersion"); Ok(CLIENT_VERSION.to_string()) } /// Returns Keccak-256 (not the standardized SHA3-256) of the given data. /// /// Handler for ETH RPC call: `web3_sha3` pub fn sha3(&self, bytes: Bytes) -> Result { node_info!("web3_sha3"); let hash = alloy_primitives::keccak256(bytes.as_ref()); Ok(alloy_primitives::hex::encode_prefixed(&hash[..])) } /// Returns protocol version encoded as a string (quotes are necessary). /// /// Handler for ETH RPC call: `eth_protocolVersion` pub fn protocol_version(&self) -> Result { node_info!("eth_protocolVersion"); Ok(1) } /// Returns the number of hashes per second that the node is mining with. /// /// Handler for ETH RPC call: `eth_hashrate` pub fn hashrate(&self) -> Result { node_info!("eth_hashrate"); Ok(U256::ZERO) } /// Returns the client coinbase address. /// /// Handler for ETH RPC call: `eth_coinbase` pub fn author(&self) -> Result
{ node_info!("eth_coinbase"); Ok(self.backend.coinbase()) } /// Returns true if client is actively mining new blocks. /// /// Handler for ETH RPC call: `eth_mining` pub fn is_mining(&self) -> Result { node_info!("eth_mining"); Ok(self.is_mining) } /// Returns the chain ID used for transaction signing at the /// current best block. None is returned if not /// available. /// /// Handler for ETH RPC call: `eth_chainId` pub fn eth_chain_id(&self) -> Result> { node_info!("eth_chainId"); Ok(Some(self.backend.chain_id().to::())) } /// Returns the same as `chain_id` /// /// Handler for ETH RPC call: `eth_networkId` pub fn network_id(&self) -> Result> { node_info!("eth_networkId"); let chain_id = self.backend.chain_id().to::(); Ok(Some(format!("{chain_id}"))) } /// Returns true if client is actively listening for network connections. /// /// Handler for ETH RPC call: `net_listening` pub fn net_listening(&self) -> Result { node_info!("net_listening"); Ok(self.net_listening) } /// Returns the current gas price fn eth_gas_price(&self) -> Result { node_info!("eth_gasPrice"); Ok(U256::from(self.gas_price())) } /// Returns the excess blob gas and current blob gas price pub fn excess_blob_gas_and_price(&self) -> Result> { Ok(self.backend.excess_blob_gas_and_price()) } /// Returns a fee per gas that is an estimate of how much you can pay as a priority fee, or /// 'tip', to get a transaction included in the current block. /// /// Handler for ETH RPC call: `eth_maxPriorityFeePerGas` pub fn gas_max_priority_fee_per_gas(&self) -> Result { self.max_priority_fee_per_gas() } /// Returns the base fee per blob required to send a EIP-4844 tx. /// /// Handler for ETH RPC call: `eth_blobBaseFee` pub fn blob_base_fee(&self) -> Result { Ok(U256::from(self.backend.fees().base_fee_per_blob_gas())) } /// Returns the block gas limit pub fn gas_limit(&self) -> U256 { U256::from(self.backend.gas_limit()) } /// Returns the accounts list /// /// Handler for ETH RPC call: `eth_accounts` pub fn accounts(&self) -> Result> { node_info!("eth_accounts"); let mut unique = HashSet::new(); let mut accounts: Vec
= Vec::new(); for signer in self.signers.iter() { accounts.extend(signer.accounts().into_iter().filter(|acc| unique.insert(*acc))); } accounts.extend( self.backend .cheats() .impersonated_accounts() .into_iter() .filter(|acc| unique.insert(*acc)), ); Ok(accounts.into_iter().collect()) } /// Returns the number of most recent block. /// /// Handler for ETH RPC call: `eth_blockNumber` pub fn block_number(&self) -> Result { node_info!("eth_blockNumber"); Ok(U256::from(self.backend.best_number())) } /// Returns balance of the given account. /// /// Handler for ETH RPC call: `eth_getBalance` pub async fn balance(&self, address: Address, block_number: Option) -> Result { node_info!("eth_getBalance"); 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 && let Some(fork) = self.get_fork() && fork.predates_fork(number) { return Ok(fork.get_balance(address, number).await?); } self.backend.get_balance(address, Some(block_request)).await } /// Returns the ethereum account. /// /// Handler for ETH RPC call: `eth_getAccount` pub async fn get_account( &self, address: Address, block_number: Option, ) -> Result { node_info!("eth_getAccount"); 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 && let Some(fork) = self.get_fork() && fork.predates_fork(number) { return Ok(fork.get_account(address, number).await?); } self.backend.get_account_at_block(address, Some(block_request)).await } /// Returns the account information including balance, nonce, code and storage /// /// Note: This isn't support by all providers pub async fn get_account_info( &self, address: Address, block_number: Option, ) -> Result { node_info!("eth_getAccountInfo"); if let Some(fork) = self.get_fork() { 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 { trace!(target: "node", "get_account_info: fork block {}, requested block {number}", fork.block_number()); return if fork.predates_fork(number) { // if this predates the fork we need to fetch balance, nonce, code individually // because the provider might not support this endpoint let balance = fork.get_balance(address, number).map_err(BlockchainError::from); let code = fork.get_code(address, number).map_err(BlockchainError::from); let nonce = self.get_transaction_count(address, Some(number.into())); let (balance, code, nonce) = try_join!(balance, code, nonce)?; Ok(alloy_rpc_types::eth::AccountInfo { balance, nonce, code }) } else { // Anvil node is at the same block or higher than the fork block, // return account info from backend to reflect current state. let account_info = self.backend.get_account(address).await?; let code = self.backend.get_code(address, Some(block_request)).await?; Ok(alloy_rpc_types::eth::AccountInfo { balance: account_info.balance, nonce: account_info.nonce, code, }) }; } } let account = self.get_account(address, block_number); let code = self.get_code(address, block_number); let (account, code) = try_join!(account, code)?; 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` pub async fn storage_at( &self, address: Address, index: U256, block_number: Option, ) -> Result { node_info!("eth_getStorageAt"); 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 && let Some(fork) = self.get_fork() && fork.predates_fork(number) { return Ok(B256::from( fork.storage_at(address, index, Some(BlockNumber::Number(number))).await?, )); } self.backend.storage_at(address, index, Some(block_request)).await } /// Returns block with given hash. /// /// Handler for ETH RPC call: `eth_getBlockByHash` pub async fn block_by_hash(&self, hash: B256) -> Result> { node_info!("eth_getBlockByHash"); self.backend.block_by_hash(hash).await } /// Returns a _full_ block with given hash. /// /// Handler for ETH RPC call: `eth_getBlockByHash` pub async fn block_by_hash_full(&self, hash: B256) -> Result> { node_info!("eth_getBlockByHash"); self.backend.block_by_hash_full(hash).await } /// Returns block with given number. /// /// Handler for ETH RPC call: `eth_getBlockByNumber` pub async fn block_by_number(&self, number: BlockNumber) -> Result> { node_info!("eth_getBlockByNumber"); if number == BlockNumber::Pending { return Ok(Some(self.pending_block().await)); } self.backend.block_by_number(number).await } /// Returns a _full_ block with given number /// /// Handler for ETH RPC call: `eth_getBlockByNumber` pub async fn block_by_number_full(&self, number: BlockNumber) -> Result> { node_info!("eth_getBlockByNumber"); if number == BlockNumber::Pending { return Ok(self.pending_block_full().await); } self.backend.block_by_number_full(number).await } /// Returns the number of transactions sent from given address at given time (block number). /// /// Also checks the pending transactions if `block_number` is /// `BlockId::Number(BlockNumber::Pending)` /// /// Handler for ETH RPC call: `eth_getTransactionCount` pub async fn transaction_count( &self, address: Address, block_number: Option, ) -> Result { node_info!("eth_getTransactionCount"); self.get_transaction_count(address, block_number).await.map(U256::from) } /// Returns the number of transactions in a block with given hash. /// /// Handler for ETH RPC call: `eth_getBlockTransactionCountByHash` pub async fn block_transaction_count_by_hash(&self, hash: B256) -> Result> { node_info!("eth_getBlockTransactionCountByHash"); let block = self.backend.block_by_hash(hash).await?; let txs = block.map(|b| match b.transactions() { BlockTransactions::Full(txs) => U256::from(txs.len()), BlockTransactions::Hashes(txs) => U256::from(txs.len()), BlockTransactions::Uncle => U256::from(0), }); Ok(txs) } /// Returns the number of transactions in a block with given block number. /// /// Handler for ETH RPC call: `eth_getBlockTransactionCountByNumber` pub async fn block_transaction_count_by_number( &self, block_number: BlockNumber, ) -> Result> { node_info!("eth_getBlockTransactionCountByNumber"); let block_request = self.block_request(Some(block_number.into())).await?; if let BlockRequest::Pending(txs) = block_request { let block = self.backend.pending_block(txs).await; return Ok(Some(U256::from(block.block.body.transactions.len()))); } let block = self.backend.block_by_number(block_number).await?; let txs = block.map(|b| match b.transactions() { BlockTransactions::Full(txs) => U256::from(txs.len()), BlockTransactions::Hashes(txs) => U256::from(txs.len()), BlockTransactions::Uncle => U256::from(0), }); Ok(txs) } /// Returns the number of uncles in a block with given hash. /// /// Handler for ETH RPC call: `eth_getUncleCountByBlockHash` pub async fn block_uncles_count_by_hash(&self, hash: B256) -> Result { node_info!("eth_getUncleCountByBlockHash"); let block = self.backend.block_by_hash(hash).await?.ok_or(BlockchainError::BlockNotFound)?; Ok(U256::from(block.uncles.len())) } /// Returns the number of uncles in a block with given block number. /// /// Handler for ETH RPC call: `eth_getUncleCountByBlockNumber` pub async fn block_uncles_count_by_number(&self, block_number: BlockNumber) -> Result { node_info!("eth_getUncleCountByBlockNumber"); let block = self .backend .block_by_number(block_number) .await? .ok_or(BlockchainError::BlockNotFound)?; Ok(U256::from(block.uncles.len())) } /// Returns the code at given address at given time (block number). /// /// Handler for ETH RPC call: `eth_getCode` pub async fn get_code(&self, address: Address, block_number: Option) -> Result { node_info!("eth_getCode"); 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 && let Some(fork) = self.get_fork() && fork.predates_fork(number) { return Ok(fork.get_code(address, number).await?); } self.backend.get_code(address, Some(block_request)).await } /// Returns the account and storage values of the specified account including the Merkle-proof. /// This call can be used to verify that the data you are pulling from is not tampered with. /// /// Handler for ETH RPC call: `eth_getProof` pub async fn get_proof( &self, address: Address, keys: Vec, block_number: Option, ) -> Result { node_info!("eth_getProof"); let block_request = self.block_request(block_number).await?; // If we're in forking mode, or still on the forked block (no blocks mined yet) then we can // delegate the call. if let BlockRequest::Number(number) = block_request && let Some(fork) = self.get_fork() && fork.predates_fork_inclusive(number) { return Ok(fork.get_proof(address, keys, Some(number.into())).await?); } let proof = self.backend.prove_account_at(address, keys, Some(block_request)).await?; Ok(proof) } /// Signs data via [EIP-712](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-712.md). /// /// Handler for ETH RPC call: `eth_signTypedData` pub async fn sign_typed_data( &self, _address: Address, _data: serde_json::Value, ) -> Result { node_info!("eth_signTypedData"); Err(BlockchainError::RpcUnimplemented) } /// Signs data via [EIP-712](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-712.md). /// /// Handler for ETH RPC call: `eth_signTypedData_v3` pub async fn sign_typed_data_v3( &self, _address: Address, _data: serde_json::Value, ) -> Result { node_info!("eth_signTypedData_v3"); Err(BlockchainError::RpcUnimplemented) } /// Signs data via [EIP-712](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-712.md), and includes full support of arrays and recursive data structures. /// /// Handler for ETH RPC call: `eth_signTypedData_v4` pub async fn sign_typed_data_v4(&self, address: Address, data: &TypedData) -> Result { node_info!("eth_signTypedData_v4"); let signer = self.get_signer(address).ok_or(BlockchainError::NoSignerAvailable)?; let signature = signer.sign_typed_data(address, data).await?; let signature = alloy_primitives::hex::encode(signature.as_bytes()); Ok(format!("0x{signature}")) } /// The sign method calculates an Ethereum specific signature /// /// Handler for ETH RPC call: `eth_sign` pub async fn sign(&self, address: Address, content: impl AsRef<[u8]>) -> Result { node_info!("eth_sign"); let signer = self.get_signer(address).ok_or(BlockchainError::NoSignerAvailable)?; let signature = alloy_primitives::hex::encode(signer.sign(address, content.as_ref()).await?.as_bytes()); Ok(format!("0x{signature}")) } /// Signs a transaction /// /// Handler for ETH RPC call: `eth_signTransaction` pub async fn sign_transaction( &self, request: WithOtherFields, ) -> Result { node_info!("eth_signTransaction"); let from = request.from.map(Ok).unwrap_or_else(|| { self.accounts()?.first().copied().ok_or(BlockchainError::NoSignerAvailable) })?; let (nonce, _) = self.request_nonce(&request, from).await?; let request = self.build_tx_request(request, nonce).await?; let signed_transaction = self.sign_request(&from, request)?.encoded_2718(); Ok(alloy_primitives::hex::encode_prefixed(signed_transaction)) } /// Sends a transaction /// /// Handler for ETH RPC call: `eth_sendTransaction` pub async fn send_transaction( &self, request: WithOtherFields, ) -> Result { node_info!("eth_sendTransaction"); let from = request.from.map(Ok).unwrap_or_else(|| { self.accounts()?.first().copied().ok_or(BlockchainError::NoSignerAvailable) })?; let (nonce, on_chain_nonce) = self.request_nonce(&request, from).await?; let typed_tx = self.build_tx_request(request, nonce).await?; // if the sender is currently impersonated we need to "bypass" signing let pending_transaction = if self.is_impersonated(from) { let transaction = sign::build_impersonated(typed_tx); self.ensure_typed_transaction_supported(&transaction)?; trace!(target : "node", ?from, "eth_sendTransaction: impersonating"); PendingTransaction::with_impersonated(transaction, from) } else { let transaction = self.sign_request(&from, typed_tx)?; self.ensure_typed_transaction_supported(&transaction)?; PendingTransaction::new(transaction)? }; // pre-validate self.backend.validate_pool_transaction(&pending_transaction).await?; let requires = required_marker(nonce, on_chain_nonce, from); let provides = vec![to_marker(nonce, from)]; debug_assert!(requires != provides); self.add_pending_transaction(pending_transaction, requires, provides) } /// Waits for a transaction to be included in a block and returns its receipt (no timeout). async fn await_transaction_inclusion(&self, hash: TxHash) -> Result { let mut stream = self.new_block_notifications(); // Check if the transaction is already included before listening for new blocks. if let Some(receipt) = self.backend.transaction_receipt(hash).await? { return Ok(receipt); } while let Some(notification) = stream.next().await { if let Some(block) = self.backend.get_block_by_hash(notification.hash) && block.body.transactions.iter().any(|tx| tx.hash() == hash) && let Some(receipt) = self.backend.transaction_receipt(hash).await? { return Ok(receipt); } } Err(BlockchainError::Message("Failed to await transaction inclusion".to_string())) } /// Waits for a transaction to be included in a block and returns its receipt, with timeout. async fn check_transaction_inclusion(&self, hash: TxHash) -> Result { const TIMEOUT_DURATION: Duration = Duration::from_secs(30); tokio::time::timeout(TIMEOUT_DURATION, self.await_transaction_inclusion(hash)) .await .unwrap_or_else(|_elapsed| { Err(BlockchainError::TransactionConfirmationTimeout { hash, duration: TIMEOUT_DURATION, }) }) } /// Sends a transaction and waits for receipt /// /// Handler for ETH RPC call: `eth_sendTransactionSync` pub async fn send_transaction_sync( &self, request: WithOtherFields, ) -> Result { node_info!("eth_sendTransactionSync"); let hash = self.send_transaction(request).await?; let receipt = self.check_transaction_inclusion(hash).await?; Ok(receipt) } /// Sends signed transaction, returning its hash. /// /// Handler for ETH RPC call: `eth_sendRawTransaction` pub async fn send_raw_transaction(&self, tx: Bytes) -> Result { node_info!("eth_sendRawTransaction"); let mut data = tx.as_ref(); if data.is_empty() { return Err(BlockchainError::EmptyRawTransactionData); } let transaction = FoundryTxEnvelope::decode_2718(&mut data) .map_err(|_| BlockchainError::FailedToDecodeSignedTransaction)?; self.ensure_typed_transaction_supported(&transaction)?; let pending_transaction = PendingTransaction::new(transaction)?; // pre-validate self.backend.validate_pool_transaction(&pending_transaction).await?; let on_chain_nonce = self.backend.current_nonce(*pending_transaction.sender()).await?; let from = *pending_transaction.sender(); let nonce = pending_transaction.transaction.nonce(); let requires = required_marker(nonce, on_chain_nonce, from); let priority = self.transaction_priority(&pending_transaction.transaction); let pool_transaction = PoolTransaction { requires, provides: vec![to_marker(nonce, *pending_transaction.sender())], pending_transaction, priority, }; let tx = self.pool.add_transaction(pool_transaction)?; trace!(target: "node", "Added transaction: [{:?}] sender={:?}", tx.hash(), from); Ok(*tx.hash()) } /// Sends signed transaction, returning its receipt. /// /// Handler for ETH RPC call: `eth_sendRawTransactionSync` pub async fn send_raw_transaction_sync(&self, tx: Bytes) -> Result { node_info!("eth_sendRawTransactionSync"); let hash = self.send_raw_transaction(tx).await?; let receipt = self.check_transaction_inclusion(hash).await?; Ok(receipt) } /// Call contract, returning the output data. /// /// Handler for ETH RPC call: `eth_call` pub async fn call( &self, request: WithOtherFields, block_number: Option, overrides: EvmOverrides, ) -> Result { node_info!("eth_call"); 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 && let Some(fork) = self.get_fork() && fork.predates_fork(number) { if overrides.has_state() || overrides.has_block() { return Err(BlockchainError::EvmOverrideError( "not available on past forked blocks".to_string(), )); } return Ok(fork.call(&request, Some(number.into())).await?); } let fees = FeeDetails::new( request.gas_price, request.max_fee_per_gas, request.max_priority_fee_per_gas, request.max_fee_per_blob_gas, )? .or_zero_fees(); // this can be blocking for a bit, especially in forking mode // self.on_blocking_task(|this| async move { let (exit, out, gas, _) = this.backend.call(request, fees, Some(block_request), overrides).await?; trace!(target : "node", "Call status {:?}, gas {}", exit, gas); ensure_return_ok(exit, &out) }) .await } pub async fn simulate_v1( &self, request: SimulatePayload, block_number: Option, ) -> Result>> { node_info!("eth_simulateV1"); 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 && let Some(fork) = self.get_fork() && fork.predates_fork(number) { return Ok(fork.simulate_v1(&request, Some(number.into())).await?); } // this can be blocking for a bit, especially in forking mode // self.on_blocking_task(|this| async move { let simulated_blocks = this.backend.simulate(request, Some(block_request)).await?; trace!(target : "node", "Simulate status {:?}", simulated_blocks); Ok(simulated_blocks) }) .await } /// This method creates an EIP2930 type accessList based on a given Transaction. The accessList /// contains all storage slots and addresses read and written by the transaction, except for the /// sender account and the precompiles. /// /// It returns list of addresses and storage keys used by the transaction, plus the gas /// consumed when the access list is added. That is, it gives you the list of addresses and /// storage keys that will be used by that transaction, plus the gas consumed if the access /// list is included. Like eth_estimateGas, this is an estimation; the list could change /// when the transaction is actually mined. Adding an accessList to your transaction does /// not necessary result in lower gas usage compared to a transaction without an access /// list. /// /// Handler for ETH RPC call: `eth_createAccessList` pub async fn create_access_list( &self, mut request: WithOtherFields, block_number: Option, ) -> Result { node_info!("eth_createAccessList"); 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 && let Some(fork) = self.get_fork() && fork.predates_fork(number) { return Ok(fork.create_access_list(&request, Some(number.into())).await?); } self.backend .with_database_at(Some(block_request), |state, block_env| { let (exit, out, _, access_list) = self.backend.build_access_list_with_state( &state, request.clone(), FeeDetails::zero(), block_env.clone(), )?; ensure_return_ok(exit, &out)?; // execute again but with access list set request.access_list = Some(access_list.clone()); let (exit, out, gas_used, _) = self.backend.call_with_state( &state, request.clone(), FeeDetails::zero(), block_env, )?; ensure_return_ok(exit, &out)?; Ok(AccessListResult { access_list: AccessList(access_list.0), gas_used: U256::from(gas_used), error: None, }) }) .await? } /// Estimate gas needed for execution of given contract. /// If no block parameter is given, it will use the pending block by default /// /// Handler for ETH RPC call: `eth_estimateGas` pub async fn estimate_gas( &self, request: WithOtherFields, block_number: Option, overrides: EvmOverrides, ) -> Result { node_info!("eth_estimateGas"); self.do_estimate_gas( request, block_number.or_else(|| Some(BlockNumber::Pending.into())), overrides, ) .await .map(U256::from) } /// Fills a transaction request with default values for missing fields. /// /// This method populates missing transaction fields like nonce, gas limit, /// chain ID, and fee parameters with appropriate defaults. /// /// Handler for ETH RPC call: `eth_fillTransaction` pub async fn fill_transaction( &self, mut request: WithOtherFields, ) -> Result> { node_info!("eth_fillTransaction"); let from = match request.as_ref().from() { Some(from) => from, None => self.accounts()?.first().copied().ok_or(BlockchainError::NoSignerAvailable)?, }; let nonce = if let Some(nonce) = request.as_ref().nonce() { nonce } else { self.request_nonce(&request, from).await?.0 }; // Prefill gas limit with estimated gas, bubble up the error if the gas estimation fails // This is a workaround to avoid the error being swallowed by the `build_tx_request` // function if request.as_ref().gas_limit().is_none() { let estimated_gas = self.estimate_gas(request.clone(), None, EvmOverrides::default()).await?; request.as_mut().set_gas_limit(estimated_gas.to()); } let typed_tx = self.build_tx_request(request, nonce).await?; let tx = build_impersonated(typed_tx); let raw = tx.encoded_2718().to_vec().into(); let mut tx = transaction_build(None, MaybeImpersonatedTransaction::new(tx), None, None, None); // Set the correct `from` address (overrides the recovered zero address from dummy // signature) tx.0.inner.inner = Recovered::new_unchecked(tx.0.inner.inner.into_inner(), from); Ok(FillTransaction { raw, tx }) } /// Handler for RPC call: `anvil_getBlobByHash` pub fn anvil_get_blob_by_versioned_hash( &self, hash: B256, ) -> Result> { node_info!("anvil_getBlobByHash"); Ok(self.backend.get_blob_by_versioned_hash(hash)?) } /// Handler for RPC call: `anvil_getBlobsByTransactionHash` pub fn anvil_get_blob_by_tx_hash(&self, hash: B256) -> Result>> { node_info!("anvil_getBlobsByTransactionHash"); Ok(self.backend.get_blob_by_tx_hash(hash)?) } /// Handler for RPC call: `anvil_getBlobsByBlockId` pub fn anvil_get_blobs_by_block_id( &self, block_id: impl Into, versioned_hashes: Vec, ) -> Result>> { node_info!("anvil_getBlobsByBlockId"); Ok(self.backend.get_blobs_by_block_id(block_id, versioned_hashes)?) } /// Returns the genesis time for the Beacon chain /// /// Handler for Beacon API call: `GET /eth/v1/beacon/genesis` pub fn anvil_get_genesis_time(&self) -> Result { node_info!("anvil_getGenesisTime"); Ok(self.backend.genesis_time()) } /// Get transaction by its hash. /// /// This will check the storage for a matching transaction, if no transaction exists in storage /// this will also scan the mempool for a matching pending transaction /// /// Handler for ETH RPC call: `eth_getTransactionByHash` pub async fn transaction_by_hash(&self, hash: B256) -> Result> { node_info!("eth_getTransactionByHash"); let mut tx = self.pool.get_transaction(hash).map(|pending| { let from = *pending.sender(); let tx = transaction_build( Some(*pending.hash()), pending.transaction, None, None, Some(self.backend.base_fee()), ); let WithOtherFields { inner: mut tx, other } = tx.0; // we set the from field here explicitly to the set sender of the pending transaction, // in case the transaction is impersonated. tx.inner = Recovered::new_unchecked(tx.inner.into_inner(), from); AnyRpcTransaction(WithOtherFields { inner: tx, other }) }); if tx.is_none() { tx = self.backend.transaction_by_hash(hash).await? } Ok(tx) } /// Returns transaction at given block hash and index. /// /// Handler for ETH RPC call: `eth_getTransactionByBlockHashAndIndex` pub async fn transaction_by_block_hash_and_index( &self, hash: B256, index: Index, ) -> Result> { node_info!("eth_getTransactionByBlockHashAndIndex"); self.backend.transaction_by_block_hash_and_index(hash, index).await } /// Returns transaction by given block number and index. /// /// Handler for ETH RPC call: `eth_getTransactionByBlockNumberAndIndex` pub async fn transaction_by_block_number_and_index( &self, block: BlockNumber, idx: Index, ) -> Result> { node_info!("eth_getTransactionByBlockNumberAndIndex"); self.backend.transaction_by_block_number_and_index(block, idx).await } /// Returns the transaction by sender and nonce. /// /// This will check the mempool for pending transactions first, then perform a binary search /// over mined blocks to find the transaction. /// /// Handler for ETH RPC call: `eth_getTransactionBySenderAndNonce` pub async fn transaction_by_sender_and_nonce( &self, sender: Address, nonce: U256, ) -> Result> { node_info!("eth_getTransactionBySenderAndNonce"); // check pending txs first for pending_tx in self.pool.ready_transactions().chain(self.pool.pending_transactions()) { if U256::from(pending_tx.pending_transaction.nonce()) == nonce && *pending_tx.pending_transaction.sender() == sender { let tx = transaction_build( Some(*pending_tx.pending_transaction.hash()), pending_tx.pending_transaction.transaction.clone(), None, None, Some(self.backend.base_fee()), ); let WithOtherFields { inner: mut tx, other } = tx.0; // we set the from field here explicitly to the set sender of the pending // transaction, in case the transaction is impersonated. let from = *pending_tx.pending_transaction.sender(); tx.inner = Recovered::new_unchecked(tx.inner.into_inner(), from); return Ok(Some(AnyRpcTransaction(WithOtherFields { inner: tx, other }))); } } let highest_nonce = self.transaction_count(sender, None).await?.saturating_to::(); let target_nonce = nonce.saturating_to::(); // if the nonce is higher or equal to the highest nonce, the transaction doesn't exist if target_nonce >= highest_nonce { return Ok(None); } // no mined blocks yet let latest_block = self.backend.best_number(); if latest_block == 0 { return Ok(None); } // binary search for the block containing the transaction let mut low = 1u64; let mut high = latest_block; while low <= high { let mid = low + (high - low) / 2; let mid_nonce = self.transaction_count(sender, Some(mid.into())).await?.saturating_to::(); if mid_nonce > target_nonce { high = mid - 1; } else { low = mid + 1; } } // search in the target block let target_block = low; if target_block <= latest_block && let Some(txs) = self.backend.mined_transactions_by_block_number(target_block.into()).await { for tx in txs { if tx.from() == sender && tx.nonce() == target_nonce { return Ok(Some(tx)); } } } Ok(None) } /// Returns transaction receipt by transaction hash. /// /// Handler for ETH RPC call: `eth_getTransactionReceipt` pub async fn transaction_receipt(&self, hash: B256) -> Result> { node_info!("eth_getTransactionReceipt"); self.backend.transaction_receipt(hash).await } /// Returns block receipts by block number. /// /// Handler for ETH RPC call: `eth_getBlockReceipts` pub async fn block_receipts(&self, number: BlockId) -> Result>> { node_info!("eth_getBlockReceipts"); self.backend.block_receipts(number).await } /// Returns an uncles at given block and index. /// /// Handler for ETH RPC call: `eth_getUncleByBlockHashAndIndex` pub async fn uncle_by_block_hash_and_index( &self, block_hash: B256, idx: Index, ) -> Result> { node_info!("eth_getUncleByBlockHashAndIndex"); let number = self.backend.ensure_block_number(Some(BlockId::Hash(block_hash.into()))).await?; if let Some(fork) = self.get_fork() && fork.predates_fork_inclusive(number) { return Ok(fork.uncle_by_block_hash_and_index(block_hash, idx.into()).await?); } // It's impossible to have uncles outside of fork mode Ok(None) } /// Returns an uncles at given block and index. /// /// Handler for ETH RPC call: `eth_getUncleByBlockNumberAndIndex` pub async fn uncle_by_block_number_and_index( &self, block_number: BlockNumber, idx: Index, ) -> Result> { node_info!("eth_getUncleByBlockNumberAndIndex"); let number = self.backend.ensure_block_number(Some(BlockId::Number(block_number))).await?; if let Some(fork) = self.get_fork() && fork.predates_fork_inclusive(number) { return Ok(fork.uncle_by_block_number_and_index(number, idx.into()).await?); } // It's impossible to have uncles outside of fork mode Ok(None) } /// Returns logs matching given filter object. /// /// Handler for ETH RPC call: `eth_getLogs` pub async fn logs(&self, filter: Filter) -> Result> { node_info!("eth_getLogs"); self.backend.logs(filter).await } /// Returns the hash of the current block, the seedHash, and the boundary condition to be met. /// /// Handler for ETH RPC call: `eth_getWork` pub fn work(&self) -> Result { node_info!("eth_getWork"); Err(BlockchainError::RpcUnimplemented) } /// Returns the sync status, always be fails. /// /// Handler for ETH RPC call: `eth_syncing` pub fn syncing(&self) -> Result { node_info!("eth_syncing"); Ok(false) } /// Returns the current configuration of the chain. /// This is useful for finding out what precompiles and system contracts are available. /// /// Note: the activation timestamp is always 0 as the configuration is set at genesis. /// Note: the `fork_id` is always `0x00000000` as this node does not participate in any forking /// on the network. /// Note: the `next` and `last` fields are always `null` as this node does not participate in /// any forking on the network. /// /// Handler for ETH RPC call: `eth_config` pub fn config(&self) -> Result { node_info!("eth_config"); Ok(EthConfig { current: EthForkConfig { activation_time: 0, blob_schedule: self.backend.blob_params(), chain_id: self.backend.env().read().evm_env.cfg_env.chain_id, fork_id: Bytes::from_static(&[0; 4]), precompiles: self.backend.precompiles(), system_contracts: self.backend.system_contracts(), }, next: None, last: None, }) } /// Used for submitting a proof-of-work solution. /// /// Handler for ETH RPC call: `eth_submitWork` pub fn submit_work(&self, _: B64, _: B256, _: B256) -> Result { node_info!("eth_submitWork"); Err(BlockchainError::RpcUnimplemented) } /// Used for submitting mining hashrate. /// /// Handler for ETH RPC call: `eth_submitHashrate` pub fn submit_hashrate(&self, _: U256, _: B256) -> Result { node_info!("eth_submitHashrate"); Err(BlockchainError::RpcUnimplemented) } /// Introduced in EIP-1559 for getting information on the appropriate priority fee to use. /// /// Handler for ETH RPC call: `eth_feeHistory` pub async fn fee_history( &self, block_count: U256, newest_block: BlockNumber, reward_percentiles: Vec, ) -> Result { node_info!("eth_feeHistory"); // max number of blocks in the requested range let current = self.backend.best_number(); let slots_in_an_epoch = 32u64; let number = match newest_block { BlockNumber::Latest | BlockNumber::Pending => current, BlockNumber::Earliest => 0, BlockNumber::Number(n) => n, BlockNumber::Safe => current.saturating_sub(slots_in_an_epoch), BlockNumber::Finalized => current.saturating_sub(slots_in_an_epoch * 2), }; // check if the number predates the fork, if in fork mode if let Some(fork) = self.get_fork() { // if we're still at the forked block we don't have any history and can't compute it // efficiently, instead we fetch it from the fork if fork.predates_fork_inclusive(number) { return fork .fee_history(block_count.to(), BlockNumber::Number(number), &reward_percentiles) .await .map_err(BlockchainError::AlloyForkProvider); } } const MAX_BLOCK_COUNT: u64 = 1024u64; let block_count = block_count.to::().min(MAX_BLOCK_COUNT); // highest and lowest block num in the requested range let highest = number; let lowest = highest.saturating_sub(block_count.saturating_sub(1)); // only support ranges that are in cache range if lowest < self.backend.best_number().saturating_sub(self.fee_history_limit) { return Err(FeeHistoryError::InvalidBlockRange.into()); } let mut response = FeeHistory { oldest_block: lowest, base_fee_per_gas: Vec::new(), gas_used_ratio: Vec::new(), reward: Some(Default::default()), base_fee_per_blob_gas: Default::default(), blob_gas_used_ratio: Default::default(), }; let mut rewards = Vec::new(); { let fee_history = self.fee_history_cache.lock(); // iter over the requested block range for n in lowest..=highest { // if let Some(block) = fee_history.get(&n) { response.base_fee_per_gas.push(block.base_fee); response.base_fee_per_blob_gas.push(block.base_fee_per_blob_gas.unwrap_or(0)); response.blob_gas_used_ratio.push(block.blob_gas_used_ratio); response.gas_used_ratio.push(block.gas_used_ratio); // requested percentiles if !reward_percentiles.is_empty() { let mut block_rewards = Vec::new(); let resolution_per_percentile: f64 = 2.0; for p in &reward_percentiles { let p = p.clamp(0.0, 100.0); let index = ((p.round() / 2f64) * 2f64) * resolution_per_percentile; let reward = block.rewards.get(index as usize).map_or(0, |r| *r); block_rewards.push(reward); } rewards.push(block_rewards); } } } } response.reward = Some(rewards); // add the next block's base fee to the response // The spec states that `base_fee_per_gas` "[..] includes the next block after the // newest of the returned range, because this value can be derived from the // newest block" response.base_fee_per_gas.push(self.backend.fees().base_fee() as u128); // Same goes for the `base_fee_per_blob_gas`: // > [..] includes the next block after the newest of the returned range, because this // > value can be derived from the newest block. response.base_fee_per_blob_gas.push(self.backend.fees().base_fee_per_blob_gas()); Ok(response) } /// Introduced in EIP-1159, a Geth-specific and simplified priority fee oracle. /// Leverages the already existing fee history cache. /// /// Returns a suggestion for a gas tip cap for dynamic fee transactions. /// /// Handler for ETH RPC call: `eth_maxPriorityFeePerGas` pub fn max_priority_fee_per_gas(&self) -> Result { node_info!("eth_maxPriorityFeePerGas"); Ok(U256::from(self.lowest_suggestion_tip())) } /// Creates a filter object, based on filter options, to notify when the state changes (logs). /// /// Handler for ETH RPC call: `eth_newFilter` pub async fn new_filter(&self, filter: Filter) -> Result { node_info!("eth_newFilter"); // all logs that are already available that match the filter if the filter's block range is // in the past let historic = if filter.block_option.get_from_block().is_some() { self.backend.logs(filter.clone()).await? } else { vec![] }; let filter = EthFilter::Logs(Box::new(LogsFilter { blocks: self.new_block_notifications(), storage: self.storage_info(), filter: FilteredParams::new(Some(filter)), historic: Some(historic), })); Ok(self.filters.add_filter(filter).await) } /// Creates a filter in the node, to notify when a new block arrives. /// /// Handler for ETH RPC call: `eth_newBlockFilter` pub async fn new_block_filter(&self) -> Result { node_info!("eth_newBlockFilter"); let filter = EthFilter::Blocks(self.new_block_notifications()); Ok(self.filters.add_filter(filter).await) } /// Creates a filter in the node, to notify when new pending transactions arrive. /// /// Handler for ETH RPC call: `eth_newPendingTransactionFilter` pub async fn new_pending_transaction_filter(&self) -> Result { node_info!("eth_newPendingTransactionFilter"); let filter = EthFilter::PendingTransactions(self.new_ready_transactions()); Ok(self.filters.add_filter(filter).await) } /// Polling method for a filter, which returns an array of logs which occurred since last poll. /// /// Handler for ETH RPC call: `eth_getFilterChanges` pub async fn get_filter_changes(&self, id: &str) -> ResponseResult { node_info!("eth_getFilterChanges"); self.filters.get_filter_changes(id).await } /// Returns an array of all logs matching filter with given id. /// /// Handler for ETH RPC call: `eth_getFilterLogs` pub async fn get_filter_logs(&self, id: &str) -> Result> { node_info!("eth_getFilterLogs"); if let Some(filter) = self.filters.get_log_filter(id).await { self.backend.logs(filter).await } else { Err(BlockchainError::FilterNotFound) } } /// Handler for ETH RPC call: `eth_uninstallFilter` pub async fn uninstall_filter(&self, id: &str) -> Result { node_info!("eth_uninstallFilter"); Ok(self.filters.uninstall_filter(id).await.is_some()) } /// Returns EIP-2718 encoded raw transaction /// /// Handler for RPC call: `debug_getRawTransaction` pub async fn raw_transaction(&self, hash: B256) -> Result> { node_info!("debug_getRawTransaction"); self.inner_raw_transaction(hash).await } /// Returns EIP-2718 encoded raw transaction by block hash and index /// /// Handler for RPC call: `eth_getRawTransactionByBlockHashAndIndex` pub async fn raw_transaction_by_block_hash_and_index( &self, block_hash: B256, index: Index, ) -> Result> { node_info!("eth_getRawTransactionByBlockHashAndIndex"); match self.backend.transaction_by_block_hash_and_index(block_hash, index).await? { Some(tx) => self.inner_raw_transaction(tx.tx_hash()).await, None => Ok(None), } } /// Returns EIP-2718 encoded raw transaction by block number and index /// /// Handler for RPC call: `eth_getRawTransactionByBlockNumberAndIndex` pub async fn raw_transaction_by_block_number_and_index( &self, block_number: BlockNumber, index: Index, ) -> Result> { node_info!("eth_getRawTransactionByBlockNumberAndIndex"); match self.backend.transaction_by_block_number_and_index(block_number, index).await? { Some(tx) => self.inner_raw_transaction(tx.tx_hash()).await, None => Ok(None), } } /// Returns traces for the transaction hash for geth's tracing endpoint /// /// Handler for RPC call: `debug_traceTransaction` pub async fn debug_trace_transaction( &self, tx_hash: B256, opts: GethDebugTracingOptions, ) -> Result { node_info!("debug_traceTransaction"); self.backend.debug_trace_transaction(tx_hash, opts).await } /// Returns traces for the transaction for geth's tracing endpoint /// /// Handler for RPC call: `debug_traceCall` pub async fn debug_trace_call( &self, request: WithOtherFields, block_number: Option, opts: GethDebugTracingCallOptions, ) -> Result { node_info!("debug_traceCall"); let block_request = self.block_request(block_number).await?; let fees = FeeDetails::new( request.gas_price, request.max_fee_per_gas, request.max_priority_fee_per_gas, request.max_fee_per_blob_gas, )? .or_zero_fees(); let result: std::result::Result = self.backend.call_with_tracing(request, fees, Some(block_request), opts).await; result } /// Returns code by its hash /// /// Handler for RPC call: `debug_codeByHash` pub async fn debug_code_by_hash( &self, hash: B256, block_id: Option, ) -> Result> { node_info!("debug_codeByHash"); self.backend.debug_code_by_hash(hash, block_id).await } /// Returns the value associated with a key from the database /// Only supports bytecode lookups. /// /// Handler for RPC call: `debug_dbGet` pub async fn debug_db_get(&self, key: String) -> Result> { node_info!("debug_dbGet"); self.backend.debug_db_get(key).await } /// Returns traces for the transaction hash via parity's tracing endpoint /// /// Handler for RPC call: `trace_transaction` pub async fn trace_transaction(&self, tx_hash: B256) -> Result> { node_info!("trace_transaction"); self.backend.trace_transaction(tx_hash).await } /// Returns traces for the transaction hash via parity's tracing endpoint /// /// Handler for RPC call: `trace_block` pub async fn trace_block(&self, block: BlockNumber) -> Result> { node_info!("trace_block"); self.backend.trace_block(block).await } /// Returns filtered traces over blocks /// /// Handler for RPC call: `trace_filter` pub async fn trace_filter( &self, filter: TraceFilter, ) -> Result> { node_info!("trace_filter"); self.backend.trace_filter(filter).await } /// Replays all transactions in a block returning the requested traces for each transaction /// /// Handler for RPC call: `trace_replayBlockTransactions` pub async fn trace_replay_block_transactions( &self, block: BlockNumber, trace_types: HashSet, ) -> Result> { node_info!("trace_replayBlockTransactions"); self.backend.trace_replay_block_transactions(block, trace_types).await } } // == impl EthApi anvil endpoints == impl EthApi { /// Send transactions impersonating specific account and contract addresses. /// /// Handler for ETH RPC call: `anvil_impersonateAccount` pub async fn anvil_impersonate_account(&self, address: Address) -> Result<()> { node_info!("anvil_impersonateAccount"); self.backend.impersonate(address); Ok(()) } /// Stops impersonating an account if previously set with `anvil_impersonateAccount`. /// /// Handler for ETH RPC call: `anvil_stopImpersonatingAccount` pub async fn anvil_stop_impersonating_account(&self, address: Address) -> Result<()> { node_info!("anvil_stopImpersonatingAccount"); self.backend.stop_impersonating(address); Ok(()) } /// If set to true will make every account impersonated /// /// Handler for ETH RPC call: `anvil_autoImpersonateAccount` pub async fn anvil_auto_impersonate_account(&self, enabled: bool) -> Result<()> { node_info!("anvil_autoImpersonateAccount"); self.backend.auto_impersonate_account(enabled); Ok(()) } /// Registers a new address and signature pair to impersonate. pub async fn anvil_impersonate_signature( &self, signature: Bytes, address: Address, ) -> Result<()> { node_info!("anvil_impersonateSignature"); self.backend.impersonate_signature(signature, address).await } /// Mines a series of blocks. /// /// Handler for ETH RPC call: `anvil_mine` pub async fn anvil_mine(&self, num_blocks: Option, interval: Option) -> Result<()> { node_info!("anvil_mine"); let interval = interval.map(|i| i.to::()); let blocks = num_blocks.unwrap_or(U256::from(1)); if blocks.is_zero() { return Ok(()); } 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; } Ok(()) }) .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(()) } /// Reorg the chain to a specific depth and mine new blocks back to the canonical height. /// /// e.g depth = 3 /// A -> B -> C -> D -> E /// A -> B -> C' -> D' -> E' /// /// Depth specifies the height to reorg the chain back to. Depth must not exceed the current /// chain height, i.e. can't reorg past the genesis block. /// /// Optionally supply a list of transaction and block pairs that will populate the reorged /// blocks. The maximum block number of the pairs must not exceed the specified depth. /// /// Handler for RPC call: `anvil_reorg` pub async fn anvil_reorg(&self, options: ReorgOptions) -> Result<()> { node_info!("anvil_reorg"); let depth = options.depth; let tx_block_pairs = options.tx_block_pairs; // Check reorg depth doesn't exceed current chain height let current_height = self.backend.best_number(); let common_height = current_height.checked_sub(depth).ok_or(BlockchainError::RpcError( RpcError::invalid_params(format!( "Reorg depth must not exceed current chain height: current height {current_height}, depth {depth}" )), ))?; // Get the common ancestor block let common_block = self.backend.get_block(common_height).ok_or(BlockchainError::BlockNotFound)?; // Convert the transaction requests to pool transactions if they exist, otherwise use empty // hashmap let block_pool_txs = if tx_block_pairs.is_empty() { HashMap::default() } else { let mut pairs = tx_block_pairs; // Check the maximum block supplied number will not exceed the reorged chain height if let Some((_, num)) = pairs.iter().find(|(_, num)| *num >= depth) { return Err(BlockchainError::RpcError(RpcError::invalid_params(format!( "Block number for reorg tx will exceed the reorged chain height. Block number {num} must not exceed (depth-1) {}", depth - 1 )))); } // Sort by block number to make it easier to manage new nonces pairs.sort_by_key(|a| a.1); // Manage nonces for each signer // address -> cumulative nonce let mut nonces: HashMap = HashMap::default(); let mut txs: HashMap>>> = HashMap::default(); for pair in pairs { let (tx_data, block_index) = pair; let pending = match tx_data { TransactionData::Raw(bytes) => { let mut data = bytes.as_ref(); let decoded = FoundryTxEnvelope::decode_2718(&mut data) .map_err(|_| BlockchainError::FailedToDecodeSignedTransaction)?; PendingTransaction::new(decoded)? } TransactionData::JSON(request) => { let from = request.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?, ); // Build typed transaction request let typed_tx = self.build_tx_request(request.into(), *curr_nonce).await?; // Increment nonce *curr_nonce += 1; // Handle signer and convert to pending transaction if self.is_impersonated(from) { let transaction = sign::build_impersonated(typed_tx); self.ensure_typed_transaction_supported(&transaction)?; PendingTransaction::with_impersonated(transaction, from) } else { let transaction = self.sign_request(&from, typed_tx)?; self.ensure_typed_transaction_supported(&transaction)?; PendingTransaction::new(transaction)? } } }; let pooled = PoolTransaction::new(pending); txs.entry(block_index).or_default().push(Arc::new(pooled)); } txs }; self.backend.reorg(depth, block_pool_txs, common_block).await?; Ok(()) } /// Mine blocks, instantly. /// /// Handler for RPC call: `evm_mine` /// /// This will mine the blocks regardless of the configured mining mode. /// **Note**: ganache returns `0x0` here as placeholder for additional meta-data in the future. pub async fn evm_mine(&self, opts: Option) -> Result { node_info!("evm_mine"); self.do_evm_mine(opts).await?; Ok("0x0".to_string()) } /// Mine blocks, instantly and return the mined blocks. /// /// Handler for RPC call: `evm_mine_detailed` /// /// This will mine the blocks regardless of the configured mining mode. /// /// **Note**: This behaves exactly as [Self::evm_mine] but returns different output, for /// compatibility reasons, this is a separate call since `evm_mine` is not an anvil original. /// and `ganache` may change the `0x0` placeholder. pub async fn evm_mine_detailed(&self, opts: Option) -> Result> { node_info!("evm_mine_detailed"); let mined_blocks = self.do_evm_mine(opts).await?; let mut blocks = Vec::with_capacity(mined_blocks as usize); let latest = self.backend.best_number(); for offset in (0..mined_blocks).rev() { let block_num = latest - offset; if let Some(mut block) = self.backend.block_by_number_full(BlockNumber::Number(block_num)).await? { let block_txs = match block.transactions_mut() { BlockTransactions::Full(txs) => txs, BlockTransactions::Hashes(_) | BlockTransactions::Uncle => unreachable!(), }; for tx in block_txs.iter_mut() { if let Some(receipt) = self.backend.mined_transaction_receipt(tx.tx_hash()) && let Some(output) = receipt.out { // insert revert reason if failure if !receipt.inner.as_ref().status() && let Some(reason) = RevertDecoder::new().maybe_decode(&output, None) { tx.other.insert( "revertReason".to_string(), serde_json::to_value(reason).expect("Infallible"), ); } tx.other.insert( "output".to_string(), serde_json::to_value(output).expect("Infallible"), ); } } block.transactions = BlockTransactions::Full(block_txs.to_vec()); blocks.push(block); } } Ok(blocks) } /// Execute a transaction regardless of signature status /// /// Handler for ETH RPC call: `eth_sendUnsignedTransaction` pub async fn eth_send_unsigned_transaction( &self, request: WithOtherFields, ) -> Result { node_info!("eth_sendUnsignedTransaction"); // either use the impersonated account of the request's `from` field let from = request.from.ok_or(BlockchainError::NoSignerAvailable)?; let (nonce, on_chain_nonce) = self.request_nonce(&request, from).await?; let typed_tx = self.build_tx_request(request, nonce).await?; let transaction = sign::build_impersonated(typed_tx); self.ensure_typed_transaction_supported(&transaction)?; let pending_transaction = PendingTransaction::with_impersonated(transaction, from); // pre-validate self.backend.validate_pool_transaction(&pending_transaction).await?; let requires = required_marker(nonce, on_chain_nonce, from); let provides = vec![to_marker(nonce, from)]; self.add_pending_transaction(pending_transaction, requires, provides) } /// Returns a summary of all the transactions currently pending for inclusion in the next /// block(s), as well as the ones that are being scheduled for future execution only. /// /// See [here](https://geth.ethereum.org/docs/rpc/ns-txpool#txpool_inspect) for more details /// /// Handler for ETH RPC call: `txpool_inspect` pub async fn txpool_inspect(&self) -> Result { node_info!("txpool_inspect"); let mut inspect = TxpoolInspect::default(); fn convert(tx: Arc>) -> TxpoolInspectSummary { let tx = &tx.pending_transaction.transaction; let to = tx.to(); let gas_price = tx.max_fee_per_gas(); let value = tx.value(); let gas = tx.gas_limit(); TxpoolInspectSummary { to, value, gas, gas_price } } // Note: naming differs geth vs anvil: // // _Pending transactions_ are transactions that are ready to be processed and included in // the block. _Queued transactions_ are transactions where the transaction nonce is // not in sequence. The transaction nonce is an incrementing number for each transaction // with the same From address. for pending in self.pool.ready_transactions() { let entry = inspect.pending.entry(*pending.pending_transaction.sender()).or_default(); let key = pending.pending_transaction.nonce().to_string(); entry.insert(key, convert(pending)); } for queued in self.pool.pending_transactions() { let entry = inspect.pending.entry(*queued.pending_transaction.sender()).or_default(); let key = queued.pending_transaction.nonce().to_string(); entry.insert(key, convert(queued)); } Ok(inspect) } /// Returns the details of all transactions currently pending for inclusion in the next /// block(s), as well as the ones that are being scheduled for future execution only. /// /// See [here](https://geth.ethereum.org/docs/rpc/ns-txpool#txpool_content) for more details /// /// Handler for ETH RPC call: `txpool_inspect` pub async fn txpool_content(&self) -> Result> { node_info!("txpool_content"); let mut content = TxpoolContent::::default(); fn convert(tx: Arc>) -> Result { let from = *tx.pending_transaction.sender(); let tx = transaction_build( Some(tx.hash()), tx.pending_transaction.transaction.clone(), None, None, None, ); let WithOtherFields { inner: mut tx, other } = tx.0; // we set the from field here explicitly to the set sender of the pending transaction, // in case the transaction is impersonated. tx.inner = Recovered::new_unchecked(tx.inner.into_inner(), from); let tx = AnyRpcTransaction(WithOtherFields { inner: tx, other }); Ok(tx) } for pending in self.pool.ready_transactions() { let entry = content.pending.entry(*pending.pending_transaction.sender()).or_default(); let key = pending.pending_transaction.nonce().to_string(); entry.insert(key, convert(pending)?); } for queued in self.pool.pending_transactions() { let entry = content.pending.entry(*queued.pending_transaction.sender()).or_default(); let key = queued.pending_transaction.nonce().to_string(); entry.insert(key, convert(queued)?); } Ok(content) } } impl EthApi { /// Executes the `evm_mine` and returns the number of blocks mined async fn do_evm_mine(&self, opts: Option) -> Result { let mut blocks_to_mine = 1u64; if let Some(opts) = opts { let timestamp = match opts { MineOptions::Timestamp(timestamp) => timestamp, MineOptions::Options { timestamp, blocks } => { if let Some(blocks) = blocks { blocks_to_mine = blocks; } timestamp } }; if let Some(timestamp) = timestamp { // timestamp was explicitly provided to be the next timestamp self.evm_set_next_block_timestamp(timestamp)?; } } // 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) } async fn do_estimate_gas( &self, request: WithOtherFields, block_number: 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 && let Some(fork) = self.get_fork() && fork.predates_fork(number) { if overrides.has_state() || overrides.has_block() { return Err(BlockchainError::EvmOverrideError( "not available on past forked blocks".to_string(), )); } return Ok(fork.estimate_gas(&request, Some(number.into())).await?); } // this can be blocking for a bit, especially in forking mode // self.on_blocking_task(|this| async move { this.backend .with_database_at(Some(block_request), |state, mut block| { let mut cache_db = CacheDB::new(state); if let Some(state_overrides) = overrides.state { apply_state_overrides( state_overrides.into_iter().collect(), &mut cache_db, )?; } if let Some(block_overrides) = overrides.block { cache_db.apply_block_overrides(*block_overrides, &mut block); } this.do_estimate_gas_with_state(request, &cache_db, block) }) .await? }) .await } /// Returns the priority of the transaction based on the current `TransactionOrder` fn transaction_priority(&self, tx: &FoundryTxEnvelope) -> TransactionPriority { self.transaction_order.read().priority(tx) } /// 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 && tx.send(txn).is_err() { break; } } }); rx } /// Mines exactly one block pub async fn mine_one(&self) { let transactions = self.pool.ready_transactions().collect::>(); let outcome = self.backend.mine_block(transactions).await; trace!(target: "node", blocknumber = ?outcome.block_number, "mined block"); self.pool.on_mined_block(outcome); } /// Returns the pending block with tx hashes async fn pending_block(&self) -> AnyRpcBlock { let transactions = self.pool.ready_transactions().collect::>(); let info = self.backend.pending_block(transactions).await; self.backend.convert_block(info.block) } /// Returns the full pending block with `Transaction` objects async fn pending_block_full(&self) -> Option { let transactions = self.pool.ready_transactions().collect::>(); let BlockInfo { block, transactions, receipts: _ } = self.backend.pending_block(transactions).await; let mut partial_block = self.backend.convert_block(block.clone()); let mut block_transactions = Vec::with_capacity(block.body.transactions.len()); let base_fee = self.backend.base_fee(); for info in transactions { let tx = block.body.transactions.get(info.transaction_index as usize)?.clone(); let tx = transaction_build( Some(info.transaction_hash), tx, Some(&block), Some(info), Some(base_fee), ); block_transactions.push(tx); } partial_block.transactions = BlockTransactions::from(block_transactions); Some(partial_block) } /// Prepares transaction request by filling missing fields using Anvil's API, then attempts /// to build a [`FoundryTypedTx`]. async fn build_tx_request( &self, request: WithOtherFields, nonce: u64, ) -> Result { let mut request = Into::::into(request); let from = request.from().or(self.accounts()?.first().copied()); // Fill common fields for all tx types request.chain_id().is_none().then(|| request.set_chain_id(self.chain_id())); request.nonce().is_none().then(|| request.set_nonce(nonce)); request.kind().is_none().then(|| request.set_kind(TxKind::default())); if request.gas_limit().is_none() { request.set_gas_limit( self.do_estimate_gas( request.as_ref().clone().into(), None, EvmOverrides::default(), ) .await .map(|v| v as u64) .unwrap_or(self.backend.gas_limit()), ); } // Fill missing tx type specific fields if let Err((tx_type, _)) = request.missing_keys() { if matches!(tx_type, FoundryTxType::Legacy | FoundryTxType::Eip2930) { request.gas_price().is_none().then(|| request.set_gas_price(self.gas_price())); } if tx_type == FoundryTxType::Eip2930 { request .access_list() .is_none() .then(|| request.set_access_list(Default::default())); } if matches!( tx_type, FoundryTxType::Eip1559 | FoundryTxType::Eip4844 | FoundryTxType::Eip7702 ) { request .max_fee_per_gas() .is_none() .then(|| request.set_max_fee_per_gas(self.gas_price())); request .max_priority_fee_per_gas() .is_none() .then(|| request.set_max_priority_fee_per_gas(MIN_SUGGESTED_PRIORITY_FEE)); } if tx_type == FoundryTxType::Eip4844 { request.as_ref().max_fee_per_blob_gas().is_none().then(|| { request.as_mut().set_max_fee_per_blob_gas( self.backend.fees().get_next_block_blob_base_fee_per_gas(), ) }); } } match request .build_unsigned() .map_err(|e| BlockchainError::InvalidTransactionRequest(e.to_string()))? { FoundryTypedTx::Eip4844(TxEip4844Variant::TxEip4844(_)) if !self.backend.skip_blob_validation(from) => { // If blob validation is not skipped, reject TxEip4844 variant without sidecar. Err(BlockchainError::FailedToDecodeTransaction) } res => Ok(res), } } /// Returns the nonce of the `address` depending on the `block_number` async fn get_transaction_count( &self, address: Address, block_number: Option, ) -> Result { let block_request = self.block_request(block_number).await?; if let BlockRequest::Number(number) = block_request && let Some(fork) = self.get_fork() && fork.predates_fork(number) { return Ok(fork.get_nonce(address, number).await?); } self.backend.get_nonce(address, block_request).await } /// Returns the nonce for this request /// /// This returns a tuple of `(request nonce, highest nonce)` /// If the nonce field of the `request` is `None` then the tuple will be `(highest nonce, /// highest nonce)`. /// /// This will also check the tx pool for pending transactions from the sender. async fn request_nonce( &self, request: &TransactionRequest, from: Address, ) -> Result<(u64, u64)> { let highest_nonce = self.get_transaction_count(from, Some(BlockId::Number(BlockNumber::Pending))).await?; let nonce = request.nonce.unwrap_or(highest_nonce); Ok((nonce, highest_nonce)) } /// Adds the given transaction to the pool fn add_pending_transaction( &self, pending_transaction: PendingTransaction, requires: Vec, provides: Vec, ) -> Result { let from = *pending_transaction.sender(); let priority = self.transaction_priority(&pending_transaction.transaction); let pool_transaction = PoolTransaction { requires, provides, pending_transaction, priority }; let tx = self.pool.add_transaction(pool_transaction)?; trace!(target: "node", "Added transaction: [{:?}] sender={:?}", tx.hash(), from); Ok(*tx.hash()) } /// additional validation against hardfork fn ensure_typed_transaction_supported(&self, tx: &FoundryTxEnvelope) -> Result<()> { match &tx { FoundryTxEnvelope::Eip2930(_) => self.backend.ensure_eip2930_active(), FoundryTxEnvelope::Eip1559(_) => self.backend.ensure_eip1559_active(), FoundryTxEnvelope::Eip4844(_) => self.backend.ensure_eip4844_active(), FoundryTxEnvelope::Eip7702(_) => self.backend.ensure_eip7702_active(), FoundryTxEnvelope::Deposit(_) => self.backend.ensure_op_deposits_active(), FoundryTxEnvelope::Legacy(_) => Ok(()), // TODO(onbjerg): we should impl support for Tempo transactions FoundryTxEnvelope::Tempo(_) => todo!(), } } } fn required_marker(provided_nonce: u64, on_chain_nonce: u64, from: Address) -> Vec { if provided_nonce == on_chain_nonce { return Vec::new(); } let prev_nonce = provided_nonce.saturating_sub(1); if on_chain_nonce <= prev_nonce { vec![to_marker(prev_nonce, from)] } else { Vec::new() } } fn convert_transact_out(out: &Option) -> Bytes { match out { None => Default::default(), Some(Output::Call(out)) => out.to_vec().into(), Some(Output::Create(out, _)) => out.to_vec().into(), } } /// Returns an error if the `exit` code is _not_ ok fn ensure_return_ok(exit: InstructionResult, out: &Option) -> Result { let out = convert_transact_out(out); match exit { return_ok!() => Ok(out), return_revert!() => Err(InvalidTransactionError::Revert(Some(out)).into()), reason => Err(BlockchainError::EvmError(reason)), } } /// Determines the minimum gas needed for a transaction depending on the transaction kind. fn determine_base_gas_by_kind(request: &WithOtherFields) -> u128 { match request.kind() { Some(TxKind::Call(_)) => { MIN_TRANSACTION_GAS + request.inner().authorization_list.as_ref().map_or(0, |auths_list| { auths_list.len() as u128 * PER_EMPTY_ACCOUNT_COST as u128 }) } Some(TxKind::Create) => MIN_CREATE_GAS, // Tighten the gas limit upwards if we don't know the tx kind to avoid deployments failing. None => MIN_CREATE_GAS, } } /// Keeps result of a call to revm EVM used for gas estimation enum GasEstimationCallResult { Success(u128), OutOfGas, Revert(Option), EvmError(InstructionResult), } /// 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; fn try_from(res: Result<(InstructionResult, Option, u128, State)>) -> Result { match res { // Exceptional case: init used too much gas, treated as out of gas error Err(BlockchainError::InvalidTransaction(InvalidTransactionError::GasTooHigh(_))) => { Ok(Self::OutOfGas) } Err(err) => Err(err), Ok((exit, output, gas, _)) => match exit { return_ok!() => 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 | InstructionResult::ReentrancySentryOOG => Ok(Self::OutOfGas), // Other errors: InstructionResult::OpcodeNotFound | InstructionResult::CallNotAllowedInsideStatic | InstructionResult::StateChangeDuringStaticCall | InstructionResult::InvalidFEOpcode | InstructionResult::InvalidJump | InstructionResult::NotActivated | InstructionResult::StackUnderflow | InstructionResult::StackOverflow | InstructionResult::OutOfOffset | InstructionResult::CreateCollision | InstructionResult::OverflowPayment | InstructionResult::PrecompileError | InstructionResult::NonceOverflow | InstructionResult::CreateContractSizeLimit | InstructionResult::CreateContractStartingWithEF | InstructionResult::CreateInitCodeSizeLimit | InstructionResult::FatalExternalError => Ok(Self::EvmError(exit)), }, } } } ================================================ FILE: crates/anvil/src/eth/backend/cheats.rs ================================================ //! Support for "cheat codes" / bypass functions use alloy_evm::precompiles::{Precompile, PrecompileInput}; use alloy_primitives::{ Address, Bytes, map::{AddressHashSet, foldhash::HashMap}, }; use parking_lot::RwLock; use revm::precompile::{ PrecompileError, PrecompileId, PrecompileOutput, PrecompileResult, secp256k1::ec_recover_run, utilities::right_pad, }; use std::{borrow::Cow, sync::Arc}; /// ID for the [`CheatEcrecover::precompile_id`] precompile. static PRECOMPILE_ID_CHEAT_ECRECOVER: PrecompileId = PrecompileId::Custom(Cow::Borrowed("cheat_ecrecover")); /// Manages user modifications that may affect the node's behavior /// /// Contains the state of executed, non-eth standard cheat code RPC #[derive(Clone, Debug, Default)] pub struct CheatsManager { /// shareable state state: Arc>, } impl CheatsManager { /// Sets the account to impersonate /// /// Returns `true` if the account is already impersonated pub fn impersonate(&self, addr: Address) -> bool { trace!(target: "cheats", %addr, "start impersonating"); // When somebody **explicitly** impersonates an account we need to store it so we are able // to return it from `eth_accounts`. That's why we do not simply call `is_impersonated()` // which does not check that list when auto impersonation is enabled. !self.state.write().impersonated_accounts.insert(addr) } /// Removes the account that from the impersonated set pub fn stop_impersonating(&self, addr: &Address) { trace!(target: "cheats", %addr, "stop impersonating"); self.state.write().impersonated_accounts.remove(addr); } /// Returns true if the `addr` is currently impersonated pub fn is_impersonated(&self, addr: Address) -> bool { if self.auto_impersonate_accounts() { true } else { self.state.read().impersonated_accounts.contains(&addr) } } /// Returns true is auto impersonation is enabled pub fn auto_impersonate_accounts(&self) -> bool { self.state.read().auto_impersonate_accounts } /// Sets the auto impersonation flag which if set to true will make the `is_impersonated` /// function always return true pub fn set_auto_impersonate_account(&self, enabled: bool) { trace!(target: "cheats", "Auto impersonation set to {:?}", enabled); self.state.write().auto_impersonate_accounts = enabled } /// Returns all accounts that are currently being impersonated. pub fn impersonated_accounts(&self) -> AddressHashSet { self.state.read().impersonated_accounts.clone() } /// Registers an override so that `ecrecover(signature)` returns `addr`. pub fn add_recover_override(&self, sig: Bytes, addr: Address) { self.state.write().signature_overrides.insert(sig, addr); } /// If an override exists for `sig`, returns the address; otherwise `None`. pub fn get_recover_override(&self, sig: &Bytes) -> Option
{ self.state.read().signature_overrides.get(sig).copied() } /// Returns true if any ecrecover overrides have been registered. pub fn has_recover_overrides(&self) -> bool { !self.state.read().signature_overrides.is_empty() } } /// Container type for all the state variables #[derive(Clone, Debug, Default)] pub struct CheatsState { /// All accounts that are currently impersonated pub impersonated_accounts: AddressHashSet, /// If set to true will make the `is_impersonated` function always return true pub auto_impersonate_accounts: bool, /// Overrides for ecrecover: Signature => Address pub signature_overrides: HashMap, } impl CheatEcrecover { pub fn new(cheats: Arc) -> Self { Self { cheats } } } impl Precompile for CheatEcrecover { fn call(&self, input: PrecompileInput<'_>) -> PrecompileResult { if !self.cheats.has_recover_overrides() { return ec_recover_run(input.data, input.gas); } const ECRECOVER_BASE: u64 = 3_000; if input.gas < ECRECOVER_BASE { return Err(PrecompileError::OutOfGas); } let padded = right_pad::<128>(input.data); let v = padded[63]; let mut sig_bytes = [0u8; 65]; sig_bytes[..64].copy_from_slice(&padded[64..128]); sig_bytes[64] = v; let sig_bytes_wrapped = Bytes::copy_from_slice(&sig_bytes); if let Some(addr) = self.cheats.get_recover_override(&sig_bytes_wrapped) { let mut out = [0u8; 32]; out[12..].copy_from_slice(addr.as_slice()); return Ok(PrecompileOutput::new(ECRECOVER_BASE, Bytes::copy_from_slice(&out))); } ec_recover_run(input.data, input.gas) } fn precompile_id(&self) -> &PrecompileId { &PRECOMPILE_ID_CHEAT_ECRECOVER } fn supports_caching(&self) -> bool { false } } /// A custom ecrecover precompile that supports cheat-based signature overrides. #[derive(Clone, Debug)] pub struct CheatEcrecover { cheats: Arc, } #[cfg(test)] mod tests { use super::*; #[test] fn impersonate_returns_false_then_true() { let mgr = CheatsManager::default(); let addr = Address::from([1u8; 20]); assert!(!mgr.impersonate(addr)); assert!(mgr.impersonate(addr)); } } ================================================ FILE: crates/anvil/src/eth/backend/db.rs ================================================ //! Helper types for working with [revm](foundry_evm::revm) use std::{ collections::BTreeMap, fmt::{self, Debug}, path::Path, }; use alloy_consensus::{BlockBody, Header}; use alloy_eips::eip4895::Withdrawals; use alloy_evm::block::StateDB; use alloy_primitives::{ Address, B256, Bytes, U256, keccak256, map::{AddressMap, HashMap}, }; use alloy_rpc_types::BlockId; use anvil_core::eth::{ block::Block, transaction::{MaybeImpersonatedTransaction, TransactionInfo}, }; use foundry_common::errors::FsPathError; use foundry_evm::backend::{ BlockchainDb, DatabaseError, DatabaseResult, MemDb, RevertStateSnapshotAction, StateSnapshot, }; use foundry_primitives::{FoundryNetwork, FoundryReceiptEnvelope, FoundryTxEnvelope}; use revm::{ Database, DatabaseCommit, bytecode::Bytecode, context::BlockEnv, context_interface::block::BlobExcessGasAndPrice, database::{CacheDB, DatabaseRef, DbAccount}, primitives::{KECCAK_EMPTY, eip4844::BLOB_BASE_FEE_UPDATE_FRACTION_PRAGUE}, state::AccountInfo, }; use serde::{ Deserialize, Deserializer, Serialize, de::{Error as DeError, MapAccess, Visitor}, }; use serde_json::Value; use crate::mem::storage::MinedTransaction; /// Helper trait get access to the full state data of the database pub trait MaybeFullDatabase: DatabaseRef + Debug { fn maybe_as_full_db(&self) -> Option<&AddressMap> { None } /// Clear the state and move it into a new `StateSnapshot`. fn clear_into_state_snapshot(&mut self) -> StateSnapshot; /// Read the state snapshot. /// /// This clones all the states and returns a new `StateSnapshot`. fn read_as_state_snapshot(&self) -> StateSnapshot; /// Clears the entire database fn clear(&mut self); /// Reverses `clear_into_snapshot` by initializing the db's state with the state snapshot. fn init_from_state_snapshot(&mut self, state_snapshot: StateSnapshot); } impl<'a, T: 'a + MaybeFullDatabase + ?Sized> MaybeFullDatabase for &'a T where &'a T: DatabaseRef, { fn maybe_as_full_db(&self) -> Option<&AddressMap> { T::maybe_as_full_db(self) } fn clear_into_state_snapshot(&mut self) -> StateSnapshot { unreachable!("never called for DatabaseRef") } fn read_as_state_snapshot(&self) -> StateSnapshot { unreachable!("never called for DatabaseRef") } fn clear(&mut self) {} fn init_from_state_snapshot(&mut self, _state_snapshot: StateSnapshot) {} } /// Helper trait to reset the DB if it's forked pub trait MaybeForkedDatabase { fn maybe_reset(&mut self, _url: Option, block_number: BlockId) -> Result<(), String>; fn maybe_flush_cache(&self) -> Result<(), String>; fn maybe_inner(&self) -> Result<&BlockchainDb, String>; } /// `dyn Db` satisfies all `alloy_evm::Database` requirements via its supertraits, but the /// blanket impl has an implicit `Sized` bound. Provide an explicit impl. impl alloy_evm::Database for dyn Db {} impl StateDB for dyn Db { fn set_state_clear_flag(&mut self, _has_state_clear: bool) { // Anvil does not use the revm State wrapper, so this is a no-op. } } /// A wrapper around [`CacheDB`] that implements [`StateDB`]. /// /// `StateDB` is a foreign trait with an orphan-rule constraint, so we cannot /// implement it directly for `CacheDB`. This newtype sidesteps the orphan /// rule while delegating all [`Database`], [`DatabaseCommit`] and /// [`DatabaseRef`] calls to the inner `CacheDB`. #[derive(Debug)] pub struct AnvilCacheDB(pub CacheDB); impl> AnvilCacheDB { pub fn new(inner: T) -> Self { Self(CacheDB::new(inner)) } } impl> std::ops::Deref for AnvilCacheDB { type Target = CacheDB; fn deref(&self) -> &Self::Target { &self.0 } } impl> std::ops::DerefMut for AnvilCacheDB { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.0 } } impl + fmt::Debug> Database for AnvilCacheDB { type Error = DatabaseError; fn basic(&mut self, address: Address) -> Result, Self::Error> { self.0.basic(address) } fn code_by_hash(&mut self, code_hash: B256) -> Result { self.0.code_by_hash(code_hash) } fn storage(&mut self, address: Address, index: U256) -> Result { self.0.storage(address, index) } fn block_hash(&mut self, number: u64) -> Result { self.0.block_hash(number) } } impl> DatabaseRef for AnvilCacheDB { type Error = DatabaseError; fn basic_ref(&self, address: Address) -> Result, Self::Error> { self.0.basic_ref(address) } fn code_by_hash_ref(&self, code_hash: B256) -> Result { self.0.code_by_hash_ref(code_hash) } fn storage_ref(&self, address: Address, index: U256) -> Result { self.0.storage_ref(address, index) } fn block_hash_ref(&self, number: u64) -> Result { self.0.block_hash_ref(number) } } impl + fmt::Debug> DatabaseCommit for AnvilCacheDB { fn commit(&mut self, changes: revm::state::EvmState) { self.0.commit(changes) } } impl + fmt::Debug> StateDB for AnvilCacheDB { fn set_state_clear_flag(&mut self, _has_state_clear: bool) { // Anvil does not use the revm State wrapper, so this is a no-op. } } /// This bundles all required revm traits pub trait Db: DatabaseRef + Database + DatabaseCommit + MaybeFullDatabase + MaybeForkedDatabase + fmt::Debug + Send + Sync { /// Inserts an account fn insert_account(&mut self, address: Address, account: AccountInfo); /// Sets the nonce of the given address fn set_nonce(&mut self, address: Address, nonce: u64) -> DatabaseResult<()> { let mut info = self.basic(address)?.unwrap_or_default(); info.nonce = nonce; self.insert_account(address, info); Ok(()) } /// Sets the balance of the given address fn set_balance(&mut self, address: Address, balance: U256) -> DatabaseResult<()> { let mut info = self.basic(address)?.unwrap_or_default(); info.balance = balance; self.insert_account(address, info); Ok(()) } /// Sets the code of the given address fn set_code(&mut self, address: Address, code: Bytes) -> DatabaseResult<()> { let mut info = self.basic(address)?.unwrap_or_default(); let code_hash = if code.as_ref().is_empty() { KECCAK_EMPTY } else { B256::from_slice(&keccak256(code.as_ref())[..]) }; info.code_hash = code_hash; info.code = Some(Bytecode::new_raw(alloy_primitives::Bytes(code.0))); self.insert_account(address, info); Ok(()) } /// Sets the storage value at the given slot for the address fn set_storage_at(&mut self, address: Address, slot: B256, val: B256) -> DatabaseResult<()>; /// inserts a blockhash for the given number fn insert_block_hash(&mut self, number: U256, hash: B256); /// Write all chain data to serialized bytes buffer fn dump_state( &self, at: BlockEnv, best_number: u64, blocks: Vec, transactions: Vec, historical_states: Option, ) -> DatabaseResult>; /// Deserialize and add all chain data to the backend storage fn load_state(&mut self, state: SerializableState) -> DatabaseResult { for (addr, account) in state.accounts.into_iter() { let old_account_nonce = DatabaseRef::basic_ref(self, addr) .ok() .and_then(|acc| acc.map(|acc| acc.nonce)) .unwrap_or_default(); // use max nonce in case account is imported multiple times with difference // nonces to prevent collisions let nonce = std::cmp::max(old_account_nonce, account.nonce); self.insert_account( addr, AccountInfo { balance: account.balance, code_hash: KECCAK_EMPTY, // will be set automatically code: if account.code.0.is_empty() { None } else { Some(Bytecode::new_raw(alloy_primitives::Bytes(account.code.0))) }, nonce, account_id: None, }, ); for (k, v) in account.storage.into_iter() { self.set_storage_at(addr, k, v)?; } } Ok(true) } /// Creates a new state snapshot. fn snapshot_state(&mut self) -> U256; /// Reverts a state snapshot. /// /// Returns `true` if the state snapshot was reverted. fn revert_state(&mut self, state_snapshot: U256, action: RevertStateSnapshotAction) -> bool; /// Returns the state root if possible to compute fn maybe_state_root(&self) -> Option { None } /// Returns the current, standalone state of the Db fn current_state(&self) -> StateDb; } /// Convenience impl only used to use any `Db` on the fly as the db layer for revm's CacheDB /// This is useful to create blocks without actually writing to the `Db`, but rather in the cache of /// the `CacheDB` see also /// [Backend::pending_block()](crate::eth::backend::mem::Backend::pending_block()) impl + Send + Sync + Clone + fmt::Debug> Db for CacheDB { fn insert_account(&mut self, address: Address, account: AccountInfo) { self.insert_account_info(address, account) } fn set_storage_at(&mut self, address: Address, slot: B256, val: B256) -> DatabaseResult<()> { self.insert_account_storage(address, slot.into(), val.into()) } fn insert_block_hash(&mut self, number: U256, hash: B256) { self.cache.block_hashes.insert(number, hash); } fn dump_state( &self, _at: BlockEnv, _best_number: u64, _blocks: Vec, _transaction: Vec, _historical_states: Option, ) -> DatabaseResult> { Ok(None) } fn snapshot_state(&mut self) -> U256 { U256::ZERO } fn revert_state(&mut self, _state_snapshot: U256, _action: RevertStateSnapshotAction) -> bool { false } fn current_state(&self) -> StateDb { StateDb::new(MemDb::default()) } } impl + Debug> MaybeFullDatabase for CacheDB { fn maybe_as_full_db(&self) -> Option<&AddressMap> { Some(&self.cache.accounts) } fn clear_into_state_snapshot(&mut self) -> StateSnapshot { 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.cache.contracts.remove(&info.code_hash); accounts.insert(addr, info); } 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 mut accounts = HashMap::default(); let mut account_storage = HashMap::default(); for (addr, acc) in &self.cache.accounts { account_storage.insert(*addr, acc.storage.clone()); let mut info = acc.info.clone(); info.code = self.cache.contracts.get(&info.code_hash).cloned(); accounts.insert(*addr, info); } let block_hashes = self.cache.block_hashes.clone(); StateSnapshot { accounts, storage: account_storage, block_hashes } } fn clear(&mut self) { self.clear_into_state_snapshot(); } fn init_from_state_snapshot(&mut self, state_snapshot: StateSnapshot) { let StateSnapshot { accounts, mut storage, block_hashes } = state_snapshot; for (addr, mut acc) in accounts { if let Some(code) = acc.code.take() { self.cache.contracts.insert(acc.code_hash, code); } self.cache.accounts.insert( addr, DbAccount { info: acc, storage: storage.remove(&addr).unwrap_or_default(), ..Default::default() }, ); } self.cache.block_hashes = block_hashes; } } impl> MaybeForkedDatabase for CacheDB { fn maybe_reset(&mut self, _url: Option, _block_number: BlockId) -> Result<(), String> { Err("not supported".to_string()) } fn maybe_flush_cache(&self) -> Result<(), String> { Err("not supported".to_string()) } fn maybe_inner(&self) -> Result<&BlockchainDb, String> { Err("not supported".to_string()) } } /// Represents a state at certain point #[derive(Debug)] pub struct StateDb(pub(crate) Box); impl StateDb { pub fn new(db: impl MaybeFullDatabase + Send + Sync + 'static) -> Self { Self(Box::new(db)) } pub fn serialize_state(&mut self) -> StateSnapshot { // Using read_as_snapshot makes sures we don't clear the historical state from the current // instance. self.read_as_state_snapshot() } } impl DatabaseRef for StateDb { type Error = DatabaseError; fn basic_ref(&self, address: Address) -> DatabaseResult> { self.0.basic_ref(address) } fn code_by_hash_ref(&self, code_hash: B256) -> DatabaseResult { self.0.code_by_hash_ref(code_hash) } fn storage_ref(&self, address: Address, index: U256) -> DatabaseResult { self.0.storage_ref(address, index) } fn block_hash_ref(&self, number: u64) -> DatabaseResult { self.0.block_hash_ref(number) } } impl MaybeFullDatabase for StateDb { fn maybe_as_full_db(&self) -> Option<&AddressMap> { self.0.maybe_as_full_db() } fn clear_into_state_snapshot(&mut self) -> StateSnapshot { self.0.clear_into_state_snapshot() } fn read_as_state_snapshot(&self) -> StateSnapshot { self.0.read_as_state_snapshot() } fn clear(&mut self) { self.0.clear() } fn init_from_state_snapshot(&mut self, state_snapshot: StateSnapshot) { self.0.init_from_state_snapshot(state_snapshot) } } /// Legacy block environment from before v1.3. #[derive(Debug, Deserialize)] #[serde(rename_all = "snake_case")] pub struct LegacyBlockEnv { pub number: Option, #[serde(alias = "coinbase")] pub beneficiary: Option
, pub timestamp: Option, pub gas_limit: Option, pub basefee: Option, pub difficulty: Option, pub prevrandao: Option, pub blob_excess_gas_and_price: Option, } /// Legacy blob excess gas and price structure from before v1.3. #[derive(Debug, Deserialize)] pub struct LegacyBlobExcessGasAndPrice { pub excess_blob_gas: u64, pub blob_gasprice: u64, } /// Legacy string or u64 type from before v1.3. #[derive(Debug, Deserialize)] #[serde(untagged)] pub enum StringOrU64 { Hex(String), Dec(u64), } impl StringOrU64 { pub fn to_u64(&self) -> Option { match self { Self::Dec(n) => Some(*n), Self::Hex(s) => s.strip_prefix("0x").and_then(|s| u64::from_str_radix(s, 16).ok()), } } pub fn to_u256(&self) -> Option { match self { Self::Dec(n) => Some(U256::from(*n)), Self::Hex(s) => s.strip_prefix("0x").and_then(|s| U256::from_str_radix(s, 16).ok()), } } } /// Converts a `LegacyBlockEnv` to a `BlockEnv`, handling the conversion of legacy fields. impl TryFrom for BlockEnv { type Error = &'static str; fn try_from(legacy: LegacyBlockEnv) -> Result { Ok(Self { number: legacy.number.and_then(|v| v.to_u256()).unwrap_or(U256::ZERO), beneficiary: legacy.beneficiary.unwrap_or(Address::ZERO), timestamp: legacy.timestamp.and_then(|v| v.to_u256()).unwrap_or(U256::ONE), gas_limit: legacy.gas_limit.and_then(|v| v.to_u64()).unwrap_or(u64::MAX), basefee: legacy.basefee.and_then(|v| v.to_u64()).unwrap_or(0), difficulty: legacy.difficulty.and_then(|v| v.to_u256()).unwrap_or(U256::ZERO), prevrandao: legacy.prevrandao.or(Some(B256::ZERO)), blob_excess_gas_and_price: legacy .blob_excess_gas_and_price .map(|v| BlobExcessGasAndPrice::new(v.excess_blob_gas, v.blob_gasprice)) .or_else(|| { Some(BlobExcessGasAndPrice::new(0, BLOB_BASE_FEE_UPDATE_FRACTION_PRAGUE)) }), }) } } /// Custom deserializer for `BlockEnv` that handles both v1.2 and v1.3+ formats. fn deserialize_block_env_compat<'de, D>(deserializer: D) -> Result, D::Error> where D: Deserializer<'de>, { let value: Option = Option::deserialize(deserializer)?; let Some(value) = value else { return Ok(None); }; if let Ok(env) = BlockEnv::deserialize(&value) { return Ok(Some(env)); } let legacy: LegacyBlockEnv = serde_json::from_value(value).map_err(|e| { D::Error::custom(format!("Legacy deserialization of `BlockEnv` failed: {e}")) })?; Ok(Some(BlockEnv::try_from(legacy).map_err(D::Error::custom)?)) } /// Custom deserializer for `best_block_number` that handles both v1.2 and v1.3+ formats. fn deserialize_best_block_number_compat<'de, D>(deserializer: D) -> Result, D::Error> where D: Deserializer<'de>, { let value: Option = Option::deserialize(deserializer)?; let Some(value) = value else { return Ok(None); }; let number = match value { Value::Number(n) => n.as_u64(), Value::String(s) => { if let Some(s) = s.strip_prefix("0x") { u64::from_str_radix(s, 16).ok() } else { s.parse().ok() } } _ => None, }; Ok(number) } #[derive(Clone, Debug, Default, Serialize, Deserialize)] pub struct SerializableState { /// The block number of the state /// /// Note: This is an Option for backwards compatibility: #[serde(deserialize_with = "deserialize_block_env_compat")] pub block: Option, pub accounts: BTreeMap, /// The best block number of the state, can be different from block number (Arbitrum chain). #[serde(deserialize_with = "deserialize_best_block_number_compat")] pub best_block_number: Option, #[serde(default)] pub blocks: Vec, #[serde(default)] pub transactions: Vec, /// Historical states of accounts and storage at particular block hashes. /// /// Note: This is an Option for backwards compatibility. #[serde(default)] pub historical_states: Option, } impl SerializableState { /// Loads the `Genesis` object from the given json file path pub fn load(path: impl AsRef) -> Result { let path = path.as_ref(); if path.is_dir() { foundry_common::fs::read_json_file(&path.join("state.json")) } else { foundry_common::fs::read_json_file(path) } } /// This is used as the clap `value_parser` implementation #[cfg(feature = "cmd")] pub(crate) fn parse(path: &str) -> Result { Self::load(path).map_err(|err| err.to_string()) } } #[derive(Clone, Debug, Serialize, Deserialize)] pub struct SerializableAccountRecord { pub nonce: u64, pub balance: U256, pub code: Bytes, #[serde(deserialize_with = "deserialize_btree")] pub storage: BTreeMap, } fn deserialize_btree<'de, D>(deserializer: D) -> Result, D::Error> where D: Deserializer<'de>, { struct BTreeVisitor; impl<'de> Visitor<'de> for BTreeVisitor { type Value = BTreeMap; fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { formatter.write_str("a mapping of hex encoded storage slots to hex encoded state data") } fn visit_map(self, mut mapping: M) -> Result, M::Error> where M: MapAccess<'de>, { let mut btree = BTreeMap::new(); while let Some((key, value)) = mapping.next_entry::()? { btree.insert(B256::from(key), B256::from(value)); } Ok(btree) } } deserializer.deserialize_map(BTreeVisitor) } /// Defines a backwards-compatible enum for transactions. /// This is essential for maintaining compatibility with state dumps /// created before the changes introduced in PR #8411. /// /// The enum can represent either a `TypedTransaction` or a `MaybeImpersonatedTransaction`, /// depending on the data being deserialized. This flexibility ensures that older state /// dumps can still be loaded correctly, even after the changes in #8411. #[derive(Clone, Debug, Serialize, Deserialize)] #[serde(untagged)] pub enum SerializableTransactionType { TypedTransaction(FoundryTxEnvelope), MaybeImpersonatedTransaction(MaybeImpersonatedTransaction), } #[derive(Clone, Debug, Serialize, Deserialize)] pub struct SerializableBlock { pub header: Header, pub transactions: Vec, pub ommers: Vec
, #[serde(default)] pub withdrawals: Option, } impl From for SerializableBlock { fn from(block: Block) -> Self { Self { header: block.header, transactions: block.body.transactions.into_iter().map(Into::into).collect(), ommers: block.body.ommers.into_iter().collect(), withdrawals: block.body.withdrawals, } } } impl From for Block { fn from(block: SerializableBlock) -> Self { let transactions = block.transactions.into_iter().map(Into::into).collect(); let ommers = block.ommers; let body = BlockBody { transactions, ommers, withdrawals: block.withdrawals }; Self::new(block.header, body) } } impl From> for SerializableTransactionType { fn from(transaction: MaybeImpersonatedTransaction) -> Self { Self::MaybeImpersonatedTransaction(transaction) } } impl From for MaybeImpersonatedTransaction { fn from(transaction: SerializableTransactionType) -> Self { match transaction { SerializableTransactionType::TypedTransaction(tx) => Self::new(tx), SerializableTransactionType::MaybeImpersonatedTransaction(tx) => tx, } } } #[derive(Clone, Debug, Serialize, Deserialize)] pub struct SerializableTransaction { pub info: TransactionInfo, pub receipt: FoundryReceiptEnvelope, pub block_hash: B256, pub block_number: u64, } impl From> for SerializableTransaction { fn from(transaction: MinedTransaction) -> Self { Self { info: transaction.info, receipt: transaction.receipt, block_hash: transaction.block_hash, block_number: transaction.block_number, } } } impl From for MinedTransaction { fn from(transaction: SerializableTransaction) -> Self { Self { info: transaction.info, receipt: transaction.receipt, block_hash: transaction.block_hash, block_number: transaction.block_number, } } } #[derive(Clone, Debug, Serialize, Deserialize, Default)] pub struct SerializableHistoricalStates(Vec<(B256, StateSnapshot)>); impl SerializableHistoricalStates { pub const fn new(states: Vec<(B256, StateSnapshot)>) -> Self { Self(states) } } impl IntoIterator for SerializableHistoricalStates { type Item = (B256, StateSnapshot); type IntoIter = std::vec::IntoIter; fn into_iter(self) -> Self::IntoIter { self.0.into_iter() } } #[cfg(test)] mod test { use super::*; #[test] fn test_deser_block() { let block = r#"{ "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": [ { "type": "0x2", "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": [] } "#; let _block: SerializableBlock = serde_json::from_str(block).unwrap(); } #[test] fn test_block_withdrawals_preserved() { use alloy_eips::eip4895::Withdrawal; // create a block with withdrawals (like post-Shanghai blocks) let withdrawal = Withdrawal { index: 42, validator_index: 123, address: Address::repeat_byte(1), amount: 1000, }; let header = Header::default(); let body = BlockBody { transactions: vec![], ommers: vec![], withdrawals: Some(vec![withdrawal].into()), }; let block = Block::new(header, body); // convert to SerializableBlock and back let serializable = SerializableBlock::from(block); let restored = Block::from(serializable); // withdrawals should be preserved assert!(restored.body.withdrawals.is_some()); let withdrawals = restored.body.withdrawals.unwrap(); assert_eq!(withdrawals.len(), 1); assert_eq!(withdrawals[0].index, 42); assert_eq!(withdrawals[0].validator_index, 123); } } ================================================ FILE: crates/anvil/src/eth/backend/env.rs ================================================ use alloy_evm::EvmEnv; use foundry_evm_networks::NetworkConfigs; use op_revm::OpTransaction; use revm::context::TxEnv; /// Helper container type for [`EvmEnv`] and [`OpTransaction`]. #[derive(Clone, Debug, Default)] pub struct Env { pub evm_env: EvmEnv, pub tx: OpTransaction, pub networks: NetworkConfigs, } /// Helper container type for [`EvmEnv`] and [`OpTransaction`]. impl Env { pub fn new(evm_env: EvmEnv, tx: OpTransaction, networks: NetworkConfigs) -> Self { Self { evm_env, tx, networks } } } ================================================ FILE: crates/anvil/src/eth/backend/executor.rs ================================================ use crate::{ eth::backend::{cheats::CheatsManager, env::Env}, mem::inspector::AnvilInspector, }; use alloy_consensus::{Eip658Value, Transaction, TransactionEnvelope, transaction::Either}; use alloy_eips::{ Encodable2718, eip2935, eip4788, eip7702::{RecoveredAuthority, RecoveredAuthorization}, }; use alloy_evm::{ EthEvmFactory, Evm, EvmEnv, EvmFactory, FromRecoveredTx, FromTxWithEncoded, RecoveredTx, block::{ BlockExecutionError, BlockExecutionResult, BlockExecutor, BlockValidationError, ExecutableTx, OnStateHook, StateChangeSource, StateDB, TxResult, }, eth::{ EthEvmContext, EthTxResult, receipt_builder::{ReceiptBuilder, ReceiptBuilderCtx}, }, precompiles::PrecompilesMap, }; use alloy_op_evm::OpEvmFactory; use alloy_primitives::{Address, B256, Bytes}; use anvil_core::eth::transaction::PendingTransaction; use foundry_evm::{backend::DatabaseError, core::either_evm::EitherEvm}; use foundry_evm_networks::NetworkConfigs; use foundry_primitives::{FoundryReceiptEnvelope, FoundryTxEnvelope, FoundryTxType}; use op_revm::{OpContext, OpTransaction}; use revm::{ Database, DatabaseCommit, Inspector, context::{Block as RevmBlock, TxEnv}, context_interface::result::ResultAndState, }; use std::{fmt, fmt::Debug}; /// Receipt builder for Foundry/Anvil that handles all transaction types #[derive(Debug, Default, Clone, Copy)] #[non_exhaustive] pub struct FoundryReceiptBuilder; impl ReceiptBuilder for FoundryReceiptBuilder { type Transaction = FoundryTxEnvelope; type Receipt = FoundryReceiptEnvelope; fn build_receipt( &self, ctx: ReceiptBuilderCtx<'_, FoundryTxType, E>, ) -> FoundryReceiptEnvelope { let receipt = alloy_consensus::Receipt { status: Eip658Value::Eip658(ctx.result.is_success()), cumulative_gas_used: ctx.cumulative_gas_used, logs: ctx.result.into_logs(), } .with_bloom(); match ctx.tx_type { FoundryTxType::Legacy => FoundryReceiptEnvelope::Legacy(receipt), FoundryTxType::Eip2930 => FoundryReceiptEnvelope::Eip2930(receipt), FoundryTxType::Eip1559 => FoundryReceiptEnvelope::Eip1559(receipt), FoundryTxType::Eip4844 => FoundryReceiptEnvelope::Eip4844(receipt), FoundryTxType::Eip7702 => FoundryReceiptEnvelope::Eip7702(receipt), FoundryTxType::Deposit => { unreachable!("deposit receipts are built in commit_transaction") } FoundryTxType::Tempo => FoundryReceiptEnvelope::Tempo(receipt), } } } /// Result of executing a transaction in [`AnvilBlockExecutor`]. /// /// Wraps [`EthTxResult`] with the sender address, needed for deposit nonce resolution. #[derive(Debug)] pub struct AnvilTxResult { pub inner: EthTxResult, pub sender: Address, } impl TxResult for AnvilTxResult { type HaltReason = H; fn result(&self) -> &ResultAndState { self.inner.result() } } /// Execution context for [`AnvilBlockExecutor`], providing block-level data /// needed for pre/post execution system calls. #[derive(Debug, Clone)] pub struct AnvilExecutionCtx { /// Parent block hash — needed for EIP-2935 system call. pub parent_hash: B256, /// Whether Prague hardfork is active. pub is_prague: bool, /// Whether Cancun hardfork is active. pub is_cancun: bool, } /// Block executor for Anvil that implements [`BlockExecutor`]. /// /// Wraps an EVM instance and produces [`FoundryReceiptEnvelope`] receipts. /// Validation (gas limits, blob gas, transaction validity) is handled by the /// caller before transactions are fed to this executor. pub struct AnvilBlockExecutor { /// The EVM instance used for execution. evm: E, /// Execution context. ctx: AnvilExecutionCtx, /// Receipt builder. receipt_builder: FoundryReceiptBuilder, /// Receipts of executed transactions. receipts: Vec, /// Total gas used by transactions in this block. gas_used: u64, /// Blob gas used by the block. blob_gas_used: u64, /// Optional state change hook. state_hook: Option>, } impl fmt::Debug for AnvilBlockExecutor { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("AnvilBlockExecutor") .field("evm", &self.evm) .field("ctx", &self.ctx) .field("gas_used", &self.gas_used) .field("blob_gas_used", &self.blob_gas_used) .field("receipts", &self.receipts.len()) .finish_non_exhaustive() } } impl AnvilBlockExecutor { /// Creates a new [`AnvilBlockExecutor`]. pub fn new(evm: E, ctx: AnvilExecutionCtx) -> Self { Self { evm, ctx, receipt_builder: FoundryReceiptBuilder, receipts: Vec::new(), gas_used: 0, blob_gas_used: 0, state_hook: None, } } } impl BlockExecutor for AnvilBlockExecutor where E: Evm< DB: StateDB, Tx: FromRecoveredTx + FromTxWithEncoded, >, { type Transaction = FoundryTxEnvelope; type Receipt = FoundryReceiptEnvelope; type Evm = E; type Result = AnvilTxResult; fn apply_pre_execution_changes(&mut self) -> Result<(), BlockExecutionError> { // EIP-2935: store parent block hash in history storage contract. if self.ctx.is_prague { let result = self .evm .transact_system_call( eip4788::SYSTEM_ADDRESS, eip2935::HISTORY_STORAGE_ADDRESS, Bytes::copy_from_slice(self.ctx.parent_hash.as_slice()), ) .map_err(BlockExecutionError::other)?; if let Some(hook) = &mut self.state_hook { hook.on_state( StateChangeSource::PreBlock( alloy_evm::block::StateChangePreBlockSource::BlockHashesContract, ), &result.state, ); } self.evm.db_mut().commit(result.state); } Ok(()) } fn execute_transaction_without_commit( &mut self, tx: impl ExecutableTx, ) -> Result { let (tx_env, tx) = tx.into_parts(); let block_available_gas = self.evm.block().gas_limit() - self.gas_used; if tx.tx().gas_limit() > block_available_gas { return Err(BlockValidationError::TransactionGasLimitMoreThanAvailableBlockGas { transaction_gas_limit: tx.tx().gas_limit(), block_available_gas, } .into()); } let sender = *tx.signer(); let result = self.evm.transact(tx_env).map_err(|err| { let hash = tx.tx().trie_hash(); BlockExecutionError::evm(err, hash) })?; Ok(AnvilTxResult { inner: EthTxResult { result, blob_gas_used: tx.tx().blob_gas_used().unwrap_or_default(), tx_type: tx.tx().tx_type(), }, sender, }) } fn commit_transaction(&mut self, output: Self::Result) -> Result { let AnvilTxResult { inner: EthTxResult { result: ResultAndState { result, state }, blob_gas_used, tx_type }, sender, } = output; if let Some(hook) = &mut self.state_hook { hook.on_state(StateChangeSource::Transaction(self.receipts.len()), &state); } let gas_used = result.gas_used(); self.gas_used += gas_used; if self.ctx.is_cancun { self.blob_gas_used = self.blob_gas_used.saturating_add(blob_gas_used); } let receipt = if tx_type == FoundryTxType::Deposit { let deposit_nonce = state.get(&sender).map(|acc| acc.info.nonce); let receipt = alloy_consensus::Receipt { status: Eip658Value::Eip658(result.is_success()), cumulative_gas_used: self.gas_used, logs: result.into_logs(), } .with_bloom(); FoundryReceiptEnvelope::Deposit(op_alloy_consensus::OpDepositReceiptWithBloom { receipt: op_alloy_consensus::OpDepositReceipt { inner: receipt.receipt, deposit_nonce, deposit_receipt_version: deposit_nonce.map(|_| 1), }, logs_bloom: receipt.logs_bloom, }) } else { self.receipt_builder.build_receipt(ReceiptBuilderCtx { tx_type, evm: &self.evm, result, state: &state, cumulative_gas_used: self.gas_used, }) }; self.receipts.push(receipt); self.evm.db_mut().commit(state); Ok(gas_used) } fn finish( self, ) -> Result<(Self::Evm, BlockExecutionResult), BlockExecutionError> { Ok(( self.evm, BlockExecutionResult { receipts: self.receipts, requests: Default::default(), gas_used: self.gas_used, blob_gas_used: self.blob_gas_used, }, )) } fn set_state_hook(&mut self, hook: Option>) { self.state_hook = hook; } fn evm_mut(&mut self) -> &mut Self::Evm { &mut self.evm } fn evm(&self) -> &Self::Evm { &self.evm } fn receipts(&self) -> &[FoundryReceiptEnvelope] { &self.receipts } } pub struct AnvilBlockExecutorFactory; impl AnvilBlockExecutorFactory { pub fn create_executor( evm: EitherEvm, ctx: AnvilExecutionCtx, ) -> AnvilBlockExecutor> where DB: StateDB, { AnvilBlockExecutor::new(evm, ctx) } } /// Builds the per-tx `OpTransaction` from a pending transaction, replicating the logic /// from `TransactionExecutor::env_for`. pub fn build_tx_env_for_pending( tx: &PendingTransaction, cheats: &CheatsManager, networks: NetworkConfigs, _evm_env: &EvmEnv, ) -> OpTransaction { let mut tx_env: OpTransaction = FromRecoveredTx::from_recovered_tx(tx.transaction.as_ref(), *tx.sender()); if let FoundryTxEnvelope::Eip7702(tx_7702) = tx.transaction.as_ref() && cheats.has_recover_overrides() { let cheated_auths = tx_7702 .tx() .authorization_list .iter() .zip(tx_env.base.authorization_list) .map(|(signed_auth, either_auth)| { either_auth.right_and_then(|recovered_auth| { if recovered_auth.authority().is_none() && let Ok(signature) = signed_auth.signature() && let Some(override_addr) = cheats.get_recover_override(&signature.as_bytes().into()) { Either::Right(RecoveredAuthorization::new_unchecked( recovered_auth.into_parts().0, RecoveredAuthority::Valid(override_addr), )) } else { Either::Right(recovered_auth) } }) }) .collect(); tx_env.base.authorization_list = cheated_auths; } if networks.is_optimism() { tx_env.enveloped_tx = Some(tx.transaction.encoded_2718().into()); } tx_env } /// Creates a database with given database and inspector. pub fn new_eth_evm_with_inspector( db: DB, env: &Env, inspector: I, ) -> EitherEvm where DB: Database + Debug, I: Inspector> + Inspector>, { if env.networks.is_optimism() { let evm_env = EvmEnv::new( env.evm_env .cfg_env .clone() .with_spec_and_mainnet_gas_params(op_revm::OpSpecId::ISTHMUS), env.evm_env.block_env.clone(), ); EitherEvm::Op(OpEvmFactory::default().create_evm_with_inspector(db, evm_env, inspector)) } else { EitherEvm::Eth(EthEvmFactory::default().create_evm_with_inspector( db, env.evm_env.clone(), inspector, )) } } ================================================ FILE: crates/anvil/src/eth/backend/fork.rs ================================================ //! Support for forking off another client use crate::eth::{backend::db::Db, error::BlockchainError, pool::transactions::PoolTransaction}; use alloy_consensus::{BlockHeader, TrieAccount}; use alloy_eips::eip2930::AccessListResult; use alloy_network::{ AnyNetwork, AnyRpcBlock, BlockResponse, Network, TransactionResponse, primitives::HeaderResponse, }; use alloy_primitives::{ Address, B256, Bytes, StorageValue, U256, map::{FbHashMap, HashMap, HashSet}, }; use alloy_provider::{ Provider, ext::{DebugApi, TraceApi}, }; use alloy_rpc_types::{ BlockId, BlockNumberOrTag as BlockNumber, BlockTransactions, EIP1186AccountProofResponse, FeeHistory, Filter, Log, simulate::{SimulatePayload, SimulatedBlock}, trace::{ geth::{GethDebugTracingOptions, GethTrace}, parity::{LocalizedTransactionTrace as Trace, TraceResultsWithTransactionHash, TraceType}, }, }; use alloy_transport::TransportError; use foundry_common::provider::{ProviderBuilder, RetryProvider}; use foundry_primitives::{FoundryTxEnvelope, FoundryTxReceipt}; use parking_lot::{ RawRwLock, RwLock, lock_api::{RwLockReadGuard, RwLockWriteGuard}, }; use revm::context_interface::block::BlobExcessGasAndPrice; use std::{sync::Arc, time::Duration}; use tokio::sync::RwLock as AsyncRwLock; /// Represents a fork of a remote client /// /// This type contains a subset of the [`EthApi`](crate::eth::EthApi) functions but will exclusively /// fetch the requested data from the remote client, if it wasn't already fetched. #[derive(Clone, Debug)] pub struct ClientFork { /// Contains the cached data pub storage: Arc>>, /// contains the info how the fork is configured // Wrapping this in a lock, ensures we can update this on the fly via additional custom RPC // endpoints pub config: Arc>>, /// This also holds a handle to the underlying database pub database: Arc>>, } impl ClientFork { /// Creates a new instance of the fork pub fn new(config: ClientForkConfig, database: Arc>>) -> Self { Self { storage: Default::default(), config: Arc::new(RwLock::new(config)), database } } /// Removes all data cached from previous responses pub fn clear_cached_storage(&self) { self.storage.write().clear() } /// Returns true whether the block predates the fork pub fn predates_fork(&self, block: u64) -> bool { block < self.block_number() } /// Returns true whether the block predates the fork _or_ is the same block as the fork pub fn predates_fork_inclusive(&self, block: u64) -> bool { block <= self.block_number() } pub fn timestamp(&self) -> u64 { self.config.read().timestamp } pub fn block_number(&self) -> u64 { self.config.read().block_number } /// Returns the transaction hash we forked off of, if any. pub fn transaction_hash(&self) -> Option { self.config.read().transaction_hash } pub fn total_difficulty(&self) -> U256 { self.config.read().total_difficulty } pub fn base_fee(&self) -> Option { self.config.read().base_fee } pub fn block_hash(&self) -> B256 { self.config.read().block_hash } pub fn eth_rpc_url(&self) -> String { self.config.read().eth_rpc_url.clone() } pub fn chain_id(&self) -> u64 { self.config.read().chain_id } fn provider(&self) -> Arc> { self.config.read().provider.clone() } fn storage_read(&self) -> RwLockReadGuard<'_, RawRwLock, ForkedStorage> { self.storage.read() } fn storage_write(&self) -> RwLockWriteGuard<'_, RawRwLock, ForkedStorage> { self.storage.write() } /// Returns the fee history `eth_feeHistory` pub async fn fee_history( &self, block_count: u64, newest_block: BlockNumber, reward_percentiles: &[f64], ) -> Result { self.provider().get_fee_history(block_count, newest_block, reward_percentiles).await } /// Sends `eth_getProof` pub async fn get_proof( &self, address: Address, keys: Vec, block_number: Option, ) -> Result { self.provider().get_proof(address, keys).block_id(block_number.unwrap_or_default()).await } pub async fn storage_at( &self, address: Address, index: U256, number: Option, ) -> Result { self.provider() .get_storage_at(address, index) .block_id(number.unwrap_or_default().into()) .await } pub async fn logs(&self, filter: &Filter) -> Result, TransportError> { if let Some(logs) = self.storage_read().logs.get(filter).cloned() { return Ok(logs); } let logs = self.provider().get_logs(filter).await?; let mut storage = self.storage_write(); storage.logs.insert(filter.clone(), logs.clone()); Ok(logs) } pub async fn get_code( &self, address: Address, blocknumber: u64, ) -> Result { trace!(target: "backend::fork", "get_code={:?}", address); if let Some(code) = self.storage_read().code_at.get(&(address, blocknumber)).cloned() { return Ok(code); } let block_id = BlockId::number(blocknumber); let code = self.provider().get_code_at(address).block_id(block_id).await?; let mut storage = self.storage_write(); storage.code_at.insert((address, blocknumber), code.clone()); Ok(code) } pub async fn get_balance( &self, address: Address, blocknumber: u64, ) -> Result { trace!(target: "backend::fork", "get_balance={:?}", address); self.provider().get_balance(address).block_id(blocknumber.into()).await } pub async fn get_nonce(&self, address: Address, block: u64) -> Result { trace!(target: "backend::fork", "get_nonce={:?}", address); self.provider().get_transaction_count(address).block_id(block.into()).await } pub async fn get_account( &self, address: Address, blocknumber: u64, ) -> Result { trace!(target: "backend::fork", "get_account={:?}", address); self.provider().get_account(address).block_id(blocknumber.into()).await } pub async fn trace_transaction(&self, hash: B256) -> Result, TransportError> { if let Some(traces) = self.storage_read().transaction_traces.get(&hash).cloned() { return Ok(traces); } let traces = self.provider().trace_transaction(hash).await?.into_iter().collect::>(); let mut storage = self.storage_write(); storage.transaction_traces.insert(hash, traces.clone()); Ok(traces) } pub async fn debug_trace_transaction( &self, hash: B256, opts: GethDebugTracingOptions, ) -> Result { if let Some(traces) = self.storage_read().geth_transaction_traces.get(&hash).cloned() { return Ok(traces); } let trace = self.provider().debug_trace_transaction(hash, opts).await?; let mut storage = self.storage_write(); storage.geth_transaction_traces.insert(hash, trace.clone()); Ok(trace) } pub async fn debug_code_by_hash( &self, code_hash: B256, block_id: Option, ) -> Result, TransportError> { self.provider().debug_code_by_hash(code_hash, block_id).await } pub async fn trace_block(&self, number: u64) -> Result, TransportError> { if let Some(traces) = self.storage_read().block_traces.get(&number).cloned() { return Ok(traces); } let traces = self.provider().trace_block(number.into()).await?.into_iter().collect::>(); let mut storage = self.storage_write(); storage.block_traces.insert(number, traces.clone()); Ok(traces) } pub async fn trace_replay_block_transactions( &self, number: u64, trace_types: HashSet, ) -> Result, TransportError> { // Forward to upstream provider for historical blocks let params = (number, trace_types.iter().map(|t| format!("{t:?}")).collect::>()); self.provider().raw_request("trace_replayBlockTransactions".into(), params).await } /// Reset the fork to a fresh forked state, and optionally update the fork config pub async fn reset( &self, url: Option, block_number: impl Into, ) -> Result<(), BlockchainError> { let block_number = block_number.into(); { self.database .write() .await .maybe_reset(url.clone(), block_number) .map_err(BlockchainError::Internal)?; } if let Some(url) = url { self.config.write().update_url(url)?; let override_chain_id = self.config.read().override_chain_id; let chain_id = if let Some(chain_id) = override_chain_id { chain_id } else { self.provider().get_chain_id().await? }; self.config.write().chain_id = chain_id; } let provider = self.provider(); let block = provider.get_block(block_number).await?.ok_or(BlockchainError::BlockNotFound)?; let block_hash = block.header().hash(); let timestamp = block.header().timestamp(); let base_fee = block.header().base_fee_per_gas(); let total_difficulty = block.header().difficulty(); let number = block.header().number(); self.config.write().update_block( number, block_hash, timestamp, base_fee.map(|g| g as u128), total_difficulty, ); self.clear_cached_storage(); self.database.write().await.insert_block_hash(U256::from(number), block_hash); Ok(()) } /// Sends `eth_call` pub async fn call( &self, request: &N::TransactionRequest, block: Option, ) -> Result { let block = block.unwrap_or(BlockNumber::Latest); let res = self.provider().call(request.clone()).block(block.into()).await?; Ok(res) } /// Sends `eth_simulateV1` pub async fn simulate_v1( &self, request: &SimulatePayload, block: Option, ) -> Result>, TransportError> { let mut simulate_call = self.provider().simulate(request); if let Some(n) = block { simulate_call = simulate_call.number(n.as_number().unwrap()); } let res = simulate_call.await?; Ok(res) } /// Sends `eth_estimateGas` pub async fn estimate_gas( &self, request: &N::TransactionRequest, block: Option, ) -> Result { let block = block.unwrap_or_default(); let res = self.provider().estimate_gas(request.clone()).block(block.into()).await?; Ok(res as u128) } /// Sends `eth_createAccessList` pub async fn create_access_list( &self, request: &N::TransactionRequest, block: Option, ) -> Result { self.provider().create_access_list(request).block_id(block.unwrap_or_default().into()).await } pub async fn transaction_by_block_number_and_index( &self, number: u64, index: usize, ) -> Result, TransportError> { if let Some(block) = self.block_by_number(number).await? { #[allow(clippy::collapsible_match)] match block.transactions() { BlockTransactions::Full(txs) => { if let Some(tx) = txs.get(index) { return Ok(Some(tx.clone())); } } BlockTransactions::Hashes(hashes) => { if let Some(tx_hash) = hashes.get(index) { return self.transaction_by_hash(*tx_hash).await; } } // TODO(evalir): Is it possible to reach this case? Should we support it BlockTransactions::Uncle => panic!("Uncles not supported"), } } Ok(None) } pub async fn transaction_by_block_hash_and_index( &self, hash: B256, index: usize, ) -> Result, TransportError> { if let Some(block) = self.block_by_hash(hash).await? { #[allow(clippy::collapsible_match)] match block.transactions() { BlockTransactions::Full(txs) => { if let Some(tx) = txs.get(index) { return Ok(Some(tx.clone())); } } BlockTransactions::Hashes(hashes) => { if let Some(tx_hash) = hashes.get(index) { return self.transaction_by_hash(*tx_hash).await; } } // TODO(evalir): Is it possible to reach this case? Should we support it BlockTransactions::Uncle => panic!("Uncles not supported"), } } Ok(None) } pub async fn transaction_by_hash( &self, hash: B256, ) -> Result, TransportError> { trace!(target: "backend::fork", "transaction_by_hash={:?}", hash); if let tx @ Some(_) = self.storage_read().transactions.get(&hash).cloned() { return Ok(tx); } let tx = self.provider().get_transaction_by_hash(hash).await?; if let Some(tx) = tx.clone() { let mut storage = self.storage_write(); storage.transactions.insert(hash, tx); } Ok(tx) } pub async fn block_by_hash( &self, hash: B256, ) -> Result, TransportError> { if let Some(mut block) = self.storage_read().blocks.get(&hash).cloned() { block.transactions_mut().convert_to_hashes(); return Ok(Some(block)); } Ok(self.fetch_full_block(hash).await?.map(|mut b| { b.transactions_mut().convert_to_hashes(); b })) } pub async fn block_by_hash_full( &self, hash: B256, ) -> Result, TransportError> { if let Some(block) = self.storage_read().blocks.get(&hash).cloned() { return Ok(Some(self.convert_to_full_block(block))); } self.fetch_full_block(hash).await } pub async fn block_by_number( &self, block_number: u64, ) -> Result, TransportError> { if let Some(mut block) = self .storage_read() .hashes .get(&block_number) .and_then(|hash| self.storage_read().blocks.get(hash).cloned()) { block.transactions_mut().convert_to_hashes(); return Ok(Some(block)); } let mut block = self.fetch_full_block(block_number).await?; if let Some(block) = &mut block { block.transactions_mut().convert_to_hashes(); } Ok(block) } pub async fn block_by_number_full( &self, block_number: u64, ) -> Result, TransportError> { if let Some(block) = self .storage_read() .hashes .get(&block_number) .copied() .and_then(|hash| self.storage_read().blocks.get(&hash).cloned()) { return Ok(Some(self.convert_to_full_block(block))); } self.fetch_full_block(block_number).await } async fn fetch_full_block( &self, block_id: impl Into, ) -> Result, TransportError> { if let Some(block) = self.provider().get_block(block_id.into()).full().await? { let hash = block.header().hash(); let block_number = block.header().number(); let mut storage = self.storage_write(); // also insert all transactions let block_txs = match block.transactions() { BlockTransactions::Full(txs) => txs.to_owned(), _ => vec![], }; storage.transactions.extend(block_txs.iter().map(|tx| (tx.tx_hash(), tx.clone()))); storage.hashes.insert(block_number, hash); storage.blocks.insert(hash, block.clone()); return Ok(Some(block)); } Ok(None) } /// Converts a block of hashes into a full block fn convert_to_full_block(&self, mut block: N::BlockResponse) -> N::BlockResponse { let storage = self.storage.read(); let transactions = block .transactions() .hashes() .filter_map(|hash| storage.transactions.get(&hash).cloned()) .collect(); *block.transactions_mut() = BlockTransactions::Full(transactions); block } } impl ClientFork { pub async fn transaction_receipt( &self, hash: B256, ) -> Result, BlockchainError> { if let Some(receipt) = self.storage_read().transaction_receipts.get(&hash).cloned() { return Ok(Some(receipt)); } if let Some(receipt) = self.provider().get_transaction_receipt(hash).await? { let receipt = FoundryTxReceipt::try_from(receipt) .map_err(|_| BlockchainError::FailedToDecodeReceipt)?; let mut storage = self.storage_write(); storage.transaction_receipts.insert(hash, receipt.clone()); return Ok(Some(receipt)); } Ok(None) } pub async fn block_receipts( &self, number: u64, ) -> Result>, BlockchainError> { if let receipts @ Some(_) = self.storage_read().block_receipts.get(&number).cloned() { return Ok(receipts); } // TODO Needs to be removed. // Since alloy doesn't indicate in the result whether the block exists, // this is being temporarily implemented in anvil. if self.predates_fork_inclusive(number) { let receipts = self.provider().get_block_receipts(BlockId::from(number)).await?; let receipts = receipts .map(|r| { r.into_iter() .map(|r| { FoundryTxReceipt::try_from(r) .map_err(|_| BlockchainError::FailedToDecodeReceipt) }) .collect::, _>>() }) .transpose()?; if let Some(receipts) = receipts.clone() { let mut storage = self.storage_write(); storage.block_receipts.insert(number, receipts); } return Ok(receipts); } Ok(None) } pub async fn uncle_by_block_hash_and_index( &self, hash: B256, index: usize, ) -> Result, TransportError> { if let Some(block) = self.block_by_hash(hash).await? { return self.uncles_by_block_and_index(block, index).await; } Ok(None) } pub async fn uncle_by_block_number_and_index( &self, number: u64, index: usize, ) -> Result, TransportError> { if let Some(block) = self.block_by_number(number).await? { return self.uncles_by_block_and_index(block, index).await; } Ok(None) } async fn uncles_by_block_and_index( &self, block: AnyRpcBlock, index: usize, ) -> Result, TransportError> { let block_hash = block.header().hash(); let block_number = block.header().number(); if let Some(uncles) = self.storage_read().uncles.get(&block_hash) { return Ok(uncles.get(index).cloned()); } let mut uncles = Vec::with_capacity(block.uncles.len()); for (uncle_idx, _) in block.uncles.iter().enumerate() { let uncle = match self.provider().get_uncle(block_number.into(), uncle_idx as u64).await? { Some(u) => u, None => return Ok(None), }; uncles.push(uncle); } self.storage_write().uncles.insert(block_hash, uncles.clone()); Ok(uncles.get(index).cloned()) } } /// Contains all fork metadata #[derive(Clone, Debug)] pub struct ClientForkConfig { pub eth_rpc_url: String, /// The block number of the forked block pub block_number: u64, /// The hash of the forked block pub block_hash: B256, /// The transaction hash we forked off of, if any. pub transaction_hash: Option, pub provider: Arc>, pub chain_id: u64, pub override_chain_id: Option, /// The timestamp for the forked block pub timestamp: u64, /// The basefee of the forked block pub base_fee: Option, /// Blob gas used of the forked block pub blob_gas_used: Option, /// Blob excess gas and price of the forked block pub blob_excess_gas_and_price: Option, /// request timeout pub timeout: Duration, /// request retries for spurious networks pub retries: u32, /// request retries for spurious networks pub backoff: Duration, /// available CUPS pub compute_units_per_second: u64, /// total difficulty of the chain until this block pub total_difficulty: U256, /// Transactions to force include in the forked chain pub force_transactions: Option>>, } impl ClientForkConfig { /// Updates the provider URL /// /// # Errors /// /// This will fail if no new provider could be established (erroneous URL) fn update_url(&mut self, url: String) -> Result<(), BlockchainError> { // let interval = self.provider.get_interval(); self.provider = Arc::new( ProviderBuilder::::new(url.as_str()) .timeout(self.timeout) // .timeout_retry(self.retries) .max_retry(self.retries) .initial_backoff(self.backoff.as_millis() as u64) .compute_units_per_second(self.compute_units_per_second) .build() .map_err(|e| BlockchainError::InvalidUrl(format!("{url}: {e}")))?, /* .interval(interval), */ ); trace!(target: "fork", "Updated rpc url {}", url); self.eth_rpc_url = url; Ok(()) } /// Updates the block forked off `(block number, block hash, timestamp)` pub fn update_block( &mut self, block_number: u64, block_hash: B256, timestamp: u64, base_fee: Option, total_difficulty: U256, ) { self.block_number = block_number; self.block_hash = block_hash; self.timestamp = timestamp; self.base_fee = base_fee; self.total_difficulty = total_difficulty; trace!(target: "fork", "Updated block number={} hash={:?}", block_number, block_hash); } } /// Contains cached state fetched to serve EthApi requests /// /// This is used as a cache so repeated requests to the same data are not sent to the remote client #[derive(Clone, Debug)] pub struct ForkedStorage { pub uncles: FbHashMap<32, Vec>, pub blocks: FbHashMap<32, N::BlockResponse>, pub hashes: HashMap, pub transactions: FbHashMap<32, N::TransactionResponse>, pub transaction_receipts: FbHashMap<32, FoundryTxReceipt>, pub transaction_traces: FbHashMap<32, Vec>, pub logs: HashMap>, pub geth_transaction_traces: FbHashMap<32, GethTrace>, pub block_traces: HashMap>, pub block_receipts: HashMap>, pub code_at: HashMap<(Address, u64), Bytes>, } impl Default for ForkedStorage { fn default() -> Self { Self { uncles: Default::default(), blocks: Default::default(), hashes: Default::default(), transactions: Default::default(), transaction_receipts: Default::default(), transaction_traces: Default::default(), logs: Default::default(), geth_transaction_traces: Default::default(), block_traces: Default::default(), block_receipts: Default::default(), code_at: Default::default(), } } } impl ForkedStorage { /// Clears all data pub fn clear(&mut self) { // simply replace with a completely new, empty instance *self = Self::default() } } ================================================ FILE: crates/anvil/src/eth/backend/genesis.rs ================================================ //! Genesis settings use crate::eth::backend::db::Db; use alloy_genesis::{Genesis, GenesisAccount}; use alloy_primitives::{Address, U256}; use foundry_evm::backend::DatabaseResult; use revm::{bytecode::Bytecode, primitives::KECCAK_EMPTY, state::AccountInfo}; use tokio::sync::RwLockWriteGuard; /// Genesis settings #[derive(Clone, Debug, Default)] pub struct GenesisConfig { /// The initial number for the genesis block pub number: u64, /// The initial timestamp for the genesis block pub timestamp: u64, /// Balance for genesis accounts pub balance: U256, /// All accounts that should be initialised at genesis pub accounts: Vec
, /// The `genesis.json` if provided pub genesis_init: Option, } impl GenesisConfig { /// Returns fresh `AccountInfo`s for the configured `accounts` pub fn account_infos(&self) -> impl Iterator + '_ { self.accounts.iter().copied().map(|address| { let info = AccountInfo { balance: self.balance, code_hash: KECCAK_EMPTY, // we set this to empty so `Database::code_by_hash` doesn't get called code: Some(Default::default()), nonce: 0, account_id: None, }; (address, info) }) } /// If an initial `genesis.json` was provided, this applies the account alloc to the db pub fn apply_genesis_json_alloc( &self, mut db: RwLockWriteGuard<'_, Box>, ) -> DatabaseResult<()> { if let Some(ref genesis) = self.genesis_init { for (addr, mut acc) in genesis.alloc.clone() { let storage = std::mem::take(&mut acc.storage); // insert all accounts db.insert_account(addr, self.genesis_to_account_info(&acc)); // insert all storage values for (k, v) in &storage.unwrap_or_default() { db.set_storage_at(addr, *k, *v)?; } } } Ok(()) } /// Converts a [`GenesisAccount`] to an [`AccountInfo`] fn genesis_to_account_info(&self, acc: &GenesisAccount) -> AccountInfo { let GenesisAccount { code, balance, nonce, .. } = acc.clone(); let code = code.map(Bytecode::new_raw); AccountInfo { balance, nonce: nonce.unwrap_or_default(), code_hash: code.as_ref().map(|code| code.hash_slow()).unwrap_or(KECCAK_EMPTY), code, account_id: None, } } } ================================================ FILE: crates/anvil/src/eth/backend/info.rs ================================================ //! Handler that can get current storage related data use crate::mem::Backend; use alloy_consensus::TxReceipt; use alloy_network::{AnyRpcBlock, Network}; use alloy_primitives::B256; use anvil_core::eth::block::Block; use std::{fmt, sync::Arc}; /// A type that can fetch data related to the ethereum storage. /// /// This is simply a wrapper type for the [`Backend`] but exposes a limited set of functions to /// fetch ethereum storage related data // TODO(mattsee): once we have multiple Backend types, this should be turned into a trait #[derive(Clone)] pub struct StorageInfo { backend: Arc>, } impl StorageInfo { pub(crate) fn new(backend: Arc>) -> Self { Self { backend } } /// Returns the current block pub fn current_block(&self) -> Option { self.backend.get_block(self.backend.best_number()) } /// Returns the block with the given hash pub fn block(&self, hash: B256) -> Option { self.backend.get_block_by_hash(hash) } /// Returns the block with the given hash in the format of the ethereum API pub fn eth_block(&self, hash: B256) -> Option { let block = self.block(hash)?; Some(self.backend.convert_block(block)) } } impl StorageInfo where N::ReceiptEnvelope: TxReceipt + Clone, { /// Returns the receipts of the current block pub fn current_receipts(&self) -> Option> { self.backend.mined_receipts(self.backend.best_hash()) } /// Returns the receipts of the block with the given hash pub fn receipts(&self, hash: B256) -> Option> { self.backend.mined_receipts(hash) } } impl fmt::Debug for StorageInfo { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("StorageInfo").finish_non_exhaustive() } } ================================================ FILE: crates/anvil/src/eth/backend/mem/cache.rs ================================================ use crate::config::anvil_tmp_dir; use alloy_primitives::B256; use foundry_evm::backend::StateSnapshot; use std::{ io, path::{Path, PathBuf}, }; use tempfile::TempDir; /// On disk state cache /// /// A basic tempdir which stores states on disk pub struct DiskStateCache { /// The path where to create the tempdir in pub(crate) temp_path: Option, /// Holds the temp dir object. pub(crate) temp_dir: Option, } impl DiskStateCache { /// Specify the path where to create the tempdir in pub fn with_path(self, temp_path: PathBuf) -> Self { Self { temp_path: Some(temp_path), temp_dir: None } } /// Returns the cache file for the given hash fn with_cache_file(&mut self, hash: B256, f: F) -> Option where F: FnOnce(PathBuf) -> R, { if self.temp_dir.is_none() { let tmp_dir = self .temp_path .as_ref() .map(|p| -> io::Result { std::fs::create_dir_all(p)?; build_tmp_dir(Some(p)) }) .unwrap_or_else(|| build_tmp_dir(None)); match tmp_dir { Ok(temp_dir) => { trace!(target: "backend", path=?temp_dir.path(), "created disk state cache dir"); self.temp_dir = Some(temp_dir); } Err(err) => { error!(target: "backend", %err, "failed to create disk state cache dir"); } } } if let Some(temp_dir) = &self.temp_dir { let path = temp_dir.path().join(format!("{hash:?}.json")); Some(f(path)) } else { None } } /// Stores the snapshot for the given hash synchronously. /// /// Returns `true` if the write was successful, `false` otherwise. pub fn write(&mut self, hash: B256, state: &StateSnapshot) -> bool { self.with_cache_file(hash, |file| match foundry_common::fs::write_json_file(&file, state) { Ok(_) => { trace!(target: "backend", ?hash, "wrote state json file"); true } Err(err) => { error!(target: "backend", %err, ?hash, "Failed to write state snapshot"); false } }) .unwrap_or(false) } /// Loads the snapshot file for the given hash /// /// Returns None if it doesn't exist or deserialization failed pub fn read(&mut self, hash: B256) -> Option { self.with_cache_file(hash, |file| { match foundry_common::fs::read_json_file::(&file) { Ok(state) => { trace!(target: "backend", ?hash,"loaded cached state"); Some(state) } Err(err) => { error!(target: "backend", %err, ?hash, "Failed to load state snapshot"); None } } }) .flatten() } /// Removes the cache file for the given hash, if it exists pub fn remove(&mut self, hash: B256) { self.with_cache_file(hash, |file| { foundry_common::fs::remove_file(file).map_err(|err| { error!(target: "backend", %err, %hash, "Failed to remove state snapshot"); }) }); } } impl Default for DiskStateCache { fn default() -> Self { Self { temp_path: anvil_tmp_dir(), temp_dir: None } } } /// Returns the temporary dir for the cached state /// /// This will create a prefixed temp dir with `anvil-state-06-11-2022-12-50` fn build_tmp_dir(p: Option<&Path>) -> io::Result { let mut builder = tempfile::Builder::new(); let now = chrono::offset::Utc::now(); let prefix = now.format("anvil-state-%d-%m-%Y-%H-%M").to_string(); builder.prefix(&prefix); if let Some(p) = p { builder.tempdir_in(p) } else { builder.tempdir() } } #[cfg(test)] mod tests { use super::*; use tempfile::tempdir; #[test] fn can_build_temp_dir() { let dir = tempdir().unwrap(); let p = dir.path(); let cache_dir = build_tmp_dir(Some(p)).unwrap(); assert!( cache_dir.path().file_name().unwrap().to_str().unwrap().starts_with("anvil-state-") ); let cache_dir = build_tmp_dir(None).unwrap(); assert!( cache_dir.path().file_name().unwrap().to_str().unwrap().starts_with("anvil-state-") ); } } ================================================ FILE: crates/anvil/src/eth/backend/mem/fork_db.rs ================================================ use crate::eth::backend::db::{ Db, MaybeForkedDatabase, MaybeFullDatabase, SerializableAccountRecord, SerializableBlock, SerializableHistoricalStates, SerializableState, SerializableTransaction, StateDb, }; use alloy_network::Network; use alloy_primitives::{Address, B256, U256, map::AddressMap}; use alloy_rpc_types::BlockId; use foundry_evm::{ backend::{BlockchainDb, DatabaseResult, RevertStateSnapshotAction, StateSnapshot}, fork::database::ForkDbStateSnapshot, }; use revm::{ context::BlockEnv, database::{Database, DbAccount}, state::AccountInfo, }; pub use foundry_evm::fork::database::ForkedDatabase; impl Db for ForkedDatabase { fn insert_account(&mut self, address: Address, account: AccountInfo) { self.database_mut().insert_account(address, account) } fn set_storage_at(&mut self, address: Address, slot: B256, val: B256) -> DatabaseResult<()> { // this ensures the account is loaded first let _ = Database::basic(self, address)?; self.database_mut().set_storage_at(address, slot, val) } fn insert_block_hash(&mut self, number: U256, hash: B256) { self.inner().block_hashes().write().insert(number, hash); } fn dump_state( &self, at: BlockEnv, best_number: u64, blocks: Vec, transactions: Vec, historical_states: Option, ) -> DatabaseResult> { let mut db = self.database().clone(); let accounts = self .database() .cache .accounts .clone() .into_iter() .map(|(k, v)| -> DatabaseResult<_> { let code = if let Some(code) = v.info.code { code } else { db.code_by_hash(v.info.code_hash)? }; Ok(( k, SerializableAccountRecord { nonce: v.info.nonce, balance: v.info.balance, code: code.original_bytes(), storage: v.storage.into_iter().map(|(k, v)| (k.into(), v.into())).collect(), }, )) }) .collect::>()?; Ok(Some(SerializableState { block: Some(at), accounts, best_block_number: Some(best_number), blocks, transactions, historical_states, })) } fn snapshot_state(&mut self) -> U256 { self.insert_state_snapshot() } fn revert_state(&mut self, id: U256, action: RevertStateSnapshotAction) -> bool { self.revert_state_snapshot(id, action) } fn current_state(&self) -> StateDb { StateDb::new(self.create_state_snapshot()) } } impl MaybeFullDatabase for ForkedDatabase { fn maybe_as_full_db(&self) -> Option<&AddressMap> { Some(&self.database().cache.accounts) } fn clear_into_state_snapshot(&mut self) -> StateSnapshot { let db = self.inner().db(); let accounts = std::mem::take(&mut *db.accounts.write()); let storage = std::mem::take(&mut *db.storage.write()); let block_hashes = std::mem::take(&mut *db.block_hashes.write()); StateSnapshot { accounts, storage, block_hashes } } fn read_as_state_snapshot(&self) -> StateSnapshot { let db = self.inner().db(); let accounts = db.accounts.read().clone(); let storage = db.storage.read().clone(); let block_hashes = db.block_hashes.read().clone(); StateSnapshot { accounts, storage, block_hashes } } fn clear(&mut self) { self.flush_cache(); self.clear_into_state_snapshot(); } fn init_from_state_snapshot(&mut self, state_snapshot: StateSnapshot) { let db = self.inner().db(); let StateSnapshot { accounts, storage, block_hashes } = state_snapshot; *db.accounts.write() = accounts; *db.storage.write() = storage; *db.block_hashes.write() = block_hashes; } } impl MaybeFullDatabase for ForkDbStateSnapshot { fn maybe_as_full_db(&self) -> Option<&AddressMap> { Some(&self.local.cache.accounts) } fn clear_into_state_snapshot(&mut self) -> StateSnapshot { let mut state_snapshot = std::mem::take(&mut self.state_snapshot); let local_state_snapshot = self.local.clear_into_state_snapshot(); state_snapshot.accounts.extend(local_state_snapshot.accounts); state_snapshot.storage.extend(local_state_snapshot.storage); state_snapshot.block_hashes.extend(local_state_snapshot.block_hashes); state_snapshot } fn read_as_state_snapshot(&self) -> StateSnapshot { let mut state_snapshot = self.state_snapshot.clone(); let local_state_snapshot = self.local.read_as_state_snapshot(); state_snapshot.accounts.extend(local_state_snapshot.accounts); state_snapshot.storage.extend(local_state_snapshot.storage); state_snapshot.block_hashes.extend(local_state_snapshot.block_hashes); state_snapshot } fn clear(&mut self) { std::mem::take(&mut self.state_snapshot); self.local.clear() } fn init_from_state_snapshot(&mut self, state_snapshot: StateSnapshot) { self.state_snapshot = state_snapshot; } } impl MaybeForkedDatabase for ForkedDatabase { fn maybe_reset(&mut self, url: Option, block_number: BlockId) -> Result<(), String> { self.reset(url, block_number) } fn maybe_flush_cache(&self) -> Result<(), String> { self.flush_cache(); Ok(()) } fn maybe_inner(&self) -> Result<&BlockchainDb, String> { Ok(self.inner()) } } ================================================ FILE: crates/anvil/src/eth/backend/mem/in_memory_db.rs ================================================ //! The in memory DB use crate::{ eth::backend::db::{ Db, MaybeForkedDatabase, MaybeFullDatabase, SerializableAccountRecord, SerializableBlock, SerializableHistoricalStates, SerializableState, SerializableTransaction, StateDb, }, mem::state::state_root, }; use alloy_primitives::{Address, B256, U256, map::AddressMap}; 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; use foundry_evm::backend::RevertStateSnapshotAction; impl Db for MemDb { fn insert_account(&mut self, address: Address, account: AccountInfo) { self.inner.insert_account_info(address, account) } fn set_storage_at(&mut self, address: Address, slot: B256, val: B256) -> DatabaseResult<()> { self.inner.insert_account_storage(address, slot.into(), val.into()) } fn insert_block_hash(&mut self, number: U256, hash: B256) { self.inner.cache.block_hashes.insert(number, hash); } fn dump_state( &self, at: BlockEnv, best_number: u64, blocks: Vec, transactions: Vec, historical_states: Option, ) -> DatabaseResult> { let accounts = self .inner .cache .accounts .clone() .into_iter() .map(|(k, v)| -> DatabaseResult<_> { let code = if let Some(code) = v.info.code { code } else { self.inner.code_by_hash_ref(v.info.code_hash)? }; Ok(( k, SerializableAccountRecord { nonce: v.info.nonce, balance: v.info.balance, code: code.original_bytes(), storage: v.storage.into_iter().map(|(k, v)| (k.into(), v.into())).collect(), }, )) }) .collect::>()?; Ok(Some(SerializableState { block: Some(at), accounts, best_block_number: Some(best_number), blocks, transactions, historical_states, })) } /// Creates a new snapshot fn snapshot_state(&mut self) -> U256 { let id = self.state_snapshots.insert(self.inner.clone()); trace!(target: "backend::memdb", "Created new state snapshot {}", id); id } fn revert_state(&mut self, id: U256, action: RevertStateSnapshotAction) -> bool { if let Some(state_snapshot) = self.state_snapshots.remove(id) { if action.is_keep() { self.state_snapshots.insert_at(state_snapshot.clone(), id); } self.inner = state_snapshot; trace!(target: "backend::memdb", "Reverted state snapshot {}", id); true } else { warn!(target: "backend::memdb", "No state snapshot to revert for {}", id); false } } fn maybe_state_root(&self) -> Option { Some(state_root(&self.inner.cache.accounts)) } fn current_state(&self) -> StateDb { StateDb::new(Self { inner: self.inner.clone(), ..Default::default() }) } } impl MaybeFullDatabase for MemDb { fn maybe_as_full_db(&self) -> Option<&AddressMap> { Some(&self.inner.cache.accounts) } fn clear_into_state_snapshot(&mut self) -> StateSnapshot { self.inner.clear_into_state_snapshot() } fn read_as_state_snapshot(&self) -> StateSnapshot { self.inner.read_as_state_snapshot() } fn clear(&mut self) { self.inner.clear(); } fn init_from_state_snapshot(&mut self, snapshot: StateSnapshot) { self.inner.init_from_state_snapshot(snapshot) } } impl MaybeForkedDatabase for MemDb { fn maybe_reset(&mut self, _url: Option, _block_number: BlockId) -> Result<(), String> { Err("not supported".to_string()) } fn maybe_flush_cache(&self) -> Result<(), String> { Err("not supported".to_string()) } fn maybe_inner(&self) -> Result<&BlockchainDb, String> { Err("not supported".to_string()) } } #[cfg(test)] mod tests { use super::*; use alloy_primitives::{Bytes, address}; 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 // is dumped and reloaded #[test] fn test_dump_reload_cycle() { let test_addr: Address = address!("0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266"); let mut dump_db = MemDb::default(); let contract_code = Bytecode::new_raw(Bytes::from("fake contract code")); dump_db.insert_account( test_addr, AccountInfo { balance: U256::from(123456), code_hash: KECCAK_EMPTY, code: Some(contract_code.clone()), nonce: 1234, account_id: None, }, ); dump_db .set_storage_at(test_addr, U256::from(1234567).into(), U256::from(1).into()) .unwrap(); // blocks dumping/loading tested in storage.rs let state = dump_db .dump_state(Default::default(), 0, Vec::new(), Vec::new(), Default::default()) .unwrap() .unwrap(); let mut load_db = MemDb::default(); load_db.load_state(state).unwrap(); let loaded_account = load_db.basic_ref(test_addr).unwrap().unwrap(); assert_eq!(loaded_account.balance, U256::from(123456)); assert_eq!(load_db.code_by_hash_ref(loaded_account.code_hash).unwrap(), contract_code); assert_eq!(loaded_account.nonce, 1234); assert_eq!(load_db.storage_ref(test_addr, U256::from(1234567)).unwrap(), U256::from(1)); } // verifies that multiple accounts can be loaded at a time, and storage is merged within those // accounts as well. #[test] fn test_load_state_merge() { let test_addr: Address = address!("0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266"); let test_addr2: Address = address!("0x70997970c51812dc3a010c7d01b50e0d17dc79c8"); let contract_code = Bytecode::new_raw(Bytes::from("fake contract code")); let mut db = MemDb::default(); db.insert_account( test_addr, AccountInfo { balance: U256::from(123456), code_hash: KECCAK_EMPTY, code: Some(contract_code.clone()), nonce: 1234, account_id: None, }, ); db.set_storage_at(test_addr, U256::from(1234567).into(), U256::from(1).into()).unwrap(); db.set_storage_at(test_addr, U256::from(1234568).into(), U256::from(2).into()).unwrap(); let mut new_state = SerializableState::default(); new_state.accounts.insert( test_addr2, SerializableAccountRecord { balance: Default::default(), code: Default::default(), nonce: 1, storage: Default::default(), }, ); let mut new_storage = BTreeMap::default(); new_storage.insert(U256::from(1234568).into(), U256::from(5).into()); new_state.accounts.insert( test_addr, SerializableAccountRecord { balance: U256::from(100100), code: contract_code.bytes()[..contract_code.len()].to_vec().into(), nonce: 100, storage: new_storage, }, ); db.load_state(new_state).unwrap(); let loaded_account = db.basic_ref(test_addr).unwrap().unwrap(); let loaded_account2 = db.basic_ref(test_addr2).unwrap().unwrap(); assert_eq!(loaded_account2.nonce, 1); assert_eq!(loaded_account.balance, U256::from(100100)); assert_eq!(db.code_by_hash_ref(loaded_account.code_hash).unwrap(), contract_code); assert_eq!(loaded_account.nonce, 1234); assert_eq!(db.storage_ref(test_addr, U256::from(1234567)).unwrap(), U256::from(1)); assert_eq!(db.storage_ref(test_addr, U256::from(1234568)).unwrap(), U256::from(5)); } } ================================================ FILE: crates/anvil/src/eth/backend/mem/inspector.rs ================================================ //! Anvil specific [`revm::Inspector`] implementation use crate::eth::macros::node_info; use alloy_primitives::{Address, Log, U256}; use foundry_evm::{ call_inspectors, decode::decode_console_logs, inspectors::{LogCollector, TracingInspector}, traces::{ CallTraceDecoder, SparsedTraceArena, TracingInspectorConfig, render_trace_arena_inner, }, }; use revm::{ Inspector, context::ContextTr, inspector::JournalExt, interpreter::{ CallInputs, CallOutcome, CreateInputs, CreateOutcome, Interpreter, interpreter::EthInterpreter, }, }; use revm_inspectors::transfer::TransferInspector; use std::sync::Arc; /// The [`revm::Inspector`] used when transacting in the evm #[derive(Clone, Debug, Default)] pub struct AnvilInspector { /// Collects all traces pub tracer: Option, /// Collects all `console.sol` logs pub log_collector: Option, /// Collects all internal ETH transfers as ERC20 transfer events. pub transfer: Option, } impl AnvilInspector { /// Called after the inspecting the evm /// /// This will log all `console.sol` logs pub fn print_logs(&self) { if let Some(LogCollector::Capture { logs }) = &self.log_collector { print_logs(logs); } } /// Consumes the type and prints the traces. pub fn into_print_traces(mut self, decoder: Arc) { if let Some(a) = self.tracer.take() { print_traces(a, decoder); } } /// Called after the inspecting the evm /// This will log all traces pub fn print_traces(&self, decoder: Arc) { if let Some(a) = self.tracer.clone() { print_traces(a, decoder); } } /// Configures the `Tracer` [`revm::Inspector`] pub fn with_tracing(mut self) -> Self { self.tracer = Some(TracingInspector::new(TracingInspectorConfig::all().set_steps(false))); self } /// Configures the `TracingInspector` [`revm::Inspector`] pub fn with_tracing_config(mut self, config: TracingInspectorConfig) -> Self { self.tracer = Some(TracingInspector::new(config)); self } /// Enables steps recording for `Tracer`. pub fn with_steps_tracing(mut self) -> Self { self.tracer = Some(TracingInspector::new(TracingInspectorConfig::all().with_state_diffs())); self } /// Configures the `Tracer` [`revm::Inspector`] with a log collector pub fn with_log_collector(mut self) -> Self { self.log_collector = Some(LogCollector::Capture { logs: Vec::new() }); self } /// Configures the `Tracer` [`revm::Inspector`] with a transfer event collector pub fn with_transfers(mut self) -> Self { self.transfer = Some(TransferInspector::new(false).with_logs(true)); self } /// Configures the `Tracer` [`revm::Inspector`] with a trace printer pub fn with_trace_printer(mut self) -> Self { self.tracer = Some(TracingInspector::new(TracingInspectorConfig::all().with_state_diffs())); self } } /// Prints the traces for the inspector /// /// Caution: This blocks on call trace decoding /// /// # Panics /// /// If called outside tokio runtime fn print_traces(tracer: TracingInspector, decoder: Arc) { let arena = tokio::task::block_in_place(move || { tokio::runtime::Handle::current().block_on(async move { let mut arena = tracer.into_traces(); decoder.populate_traces(arena.nodes_mut()).await; arena }) }); let traces = SparsedTraceArena { arena, ignored: Default::default() }; let trace = render_trace_arena_inner(&traces, false, true); node_info!(Traces = %format!("\n{}", trace)); } impl Inspector for AnvilInspector where CTX: ContextTr, { 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 CTX) { call_inspectors!([&mut self.tracer], |inspector| { inspector.step(interp, ecx); }); } fn step_end(&mut self, interp: &mut Interpreter, ecx: &mut CTX) { call_inspectors!([&mut self.tracer], |inspector| { inspector.step_end(interp, ecx); }); } #[allow(clippy::redundant_clone)] fn log(&mut self, ecx: &mut CTX, log: Log) { call_inspectors!([&mut self.tracer, &mut self.log_collector], |inspector| { inspector.log(ecx, log.clone()); }); } #[allow(clippy::redundant_clone)] fn log_full(&mut self, interp: &mut Interpreter, ecx: &mut CTX, log: Log) { call_inspectors!([&mut self.tracer, &mut self.log_collector], |inspector| { inspector.log_full(interp, ecx, log.clone()); }); } fn call(&mut self, ecx: &mut CTX, inputs: &mut CallInputs) -> Option { call_inspectors!( #[ret] [&mut self.tracer, &mut self.log_collector, &mut self.transfer], |inspector| inspector.call(ecx, inputs).map(Some), ); None } fn call_end(&mut self, ecx: &mut CTX, inputs: &CallInputs, outcome: &mut CallOutcome) { if let Some(tracer) = &mut self.tracer { tracer.call_end(ecx, inputs, outcome); } } fn create(&mut self, ecx: &mut CTX, inputs: &mut CreateInputs) -> Option { call_inspectors!( #[ret] [&mut self.tracer, &mut self.transfer], |inspector| inspector.create(ecx, inputs).map(Some), ); None } fn create_end(&mut self, ecx: &mut CTX, inputs: &CreateInputs, outcome: &mut CreateOutcome) { if let Some(tracer) = &mut self.tracer { tracer.create_end(ecx, inputs, outcome); } } fn selfdestruct(&mut self, contract: Address, target: Address, value: U256) { call_inspectors!([&mut self.tracer, &mut self.transfer], |inspector| { Inspector::::selfdestruct(inspector, contract, target, value) }); } } /// Prints all the logs pub fn print_logs(logs: &[Log]) { for log in decode_console_logs(logs) { tracing::info!(target: crate::logging::EVM_CONSOLE_LOG_TARGET, "{}", log); } } ================================================ FILE: crates/anvil/src/eth/backend/mem/mod.rs ================================================ //! In-memory blockchain backend. use self::state::trie_storage; use super::executor::new_eth_evm_with_inspector; use crate::{ ForkChoice, NodeConfig, PrecompileFactory, config::PruneStateHistoryConfig, eth::{ backend::{ cheats::{CheatEcrecover, CheatsManager}, db::{AnvilCacheDB, Db, MaybeFullDatabase, SerializableState, StateDb}, env::Env, executor::{AnvilBlockExecutorFactory, AnvilExecutionCtx, build_tx_env_for_pending}, fork::ClientFork, genesis::GenesisConfig, mem::{ state::{storage_root, trie_accounts}, storage::MinedTransactionReceipt, }, notifications::{NewBlockNotification, NewBlockNotifications}, time::{TimeManager, utc_from_secs}, validate::TransactionValidator, }, error::{BlockchainError, ErrDetail, InvalidTransactionError}, fees::{FeeDetails, FeeManager, MIN_SUGGESTED_PRIORITY_FEE}, macros::node_info, pool::transactions::PoolTransaction, sign::build_impersonated, }, mem::{ inspector::AnvilInspector, storage::{BlockchainStorage, InMemoryBlockStates, MinedBlockOutcome}, }, }; use alloy_chains::NamedChain; use alloy_consensus::{ Blob, BlockHeader, EnvKzgSettings, Header, Signed, Transaction as TransactionTrait, TrieAccount, TxEnvelope, TxReceipt, Typed2718, constants::EMPTY_WITHDRAWALS, proofs::{calculate_receipt_root, calculate_transaction_root}, transaction::Recovered, }; use alloy_eips::{ BlockNumHash, Encodable2718, eip2935, eip4844::kzg_to_versioned_hash, eip7685::EMPTY_REQUESTS_HASH, eip7840::BlobParams, eip7910::SystemContract, }; use alloy_evm::{ Database, Evm, FromRecoveredTx, block::BlockExecutor, eth::EthEvmContext, overrides::{OverrideBlockHashes, apply_state_overrides}, precompiles::{DynPrecompile, Precompile, PrecompilesMap}, }; use alloy_network::{ AnyHeader, AnyRpcBlock, AnyRpcHeader, AnyRpcTransaction, AnyTxEnvelope, AnyTxType, Network, ReceiptResponse, TransactionBuilder, UnknownTxEnvelope, UnknownTypedTransaction, }; use alloy_primitives::{ Address, B256, Bloom, BloomInput, Bytes, TxHash, TxKind, U64, U256, hex, keccak256, logs_bloom, map::{AddressMap, HashMap, HashSet}, }; use alloy_rpc_types::{ AccessList, Block as AlloyBlock, BlockId, BlockNumberOrTag as BlockNumber, BlockTransactions, EIP1186AccountProofResponse as AccountProof, EIP1186StorageProof as StorageProof, Filter, Header as AlloyHeader, Index, Log, Transaction, TransactionReceipt, anvil::Forking, request::TransactionRequest, serde_helpers::JsonStorageKey, simulate::{SimBlock, SimCallResult, SimulatePayload, SimulatedBlock}, state::EvmOverrides, trace::{ filter::TraceFilter, geth::{ FourByteFrame, GethDebugBuiltInTracerType, GethDebugTracerType, GethDebugTracingCallOptions, GethDebugTracingOptions, GethTrace, NoopFrame, }, parity::{LocalizedTransactionTrace, TraceResultsWithTransactionHash, TraceType}, }, }; use alloy_serde::{OtherFields, WithOtherFields}; use alloy_trie::{HashBuilder, Nibbles, proof::ProofRetainer}; use anvil_core::eth::{ block::{Block, BlockInfo, TypedBlockInfo, create_block}, transaction::{MaybeImpersonatedTransaction, PendingTransaction, TransactionInfo}, }; use anvil_rpc::error::RpcError; use chrono::Datelike; use eyre::{Context, Result}; use flate2::{Compression, read::GzDecoder, write::GzEncoder}; use foundry_evm::{ backend::{DatabaseError, DatabaseResult, RevertStateSnapshotAction}, constants::DEFAULT_CREATE2_DEPLOYER_RUNTIME_CODE, core::{either_evm::EitherEvm, precompiles::EC_RECOVER}, decode::RevertDecoder, inspectors::AccessListInspector, traces::{ CallTraceDecoder, FourByteInspector, GethTraceBuilder, TracingInspector, TracingInspectorConfig, }, utils::{ block_env_from_header, get_blob_base_fee_update_fraction, get_blob_base_fee_update_fraction_by_spec_id, }, }; use foundry_primitives::{ FoundryNetwork, FoundryReceiptEnvelope, FoundryTransactionRequest, FoundryTxEnvelope, FoundryTxReceipt, get_deposit_tx_parts, }; use futures::channel::mpsc::{UnboundedSender, unbounded}; use op_alloy_consensus::DEPOSIT_TX_TYPE_ID; use op_revm::{OpContext, OpHaltReason, OpTransaction}; use parking_lot::{Mutex, RwLock, RwLockUpgradableReadGuard}; use revm::{ Database as RevmDatabase, DatabaseCommit, Inspector, context::{Block as RevmBlock, BlockEnv, Cfg, TxEnv}, context_interface::{ block::BlobExcessGasAndPrice, result::{ExecutionResult, Output, ResultAndState}, }, database::{CacheDB, DbAccount, WrapDatabaseRef}, interpreter::InstructionResult, precompile::{PrecompileSpecId, Precompiles}, primitives::{KECCAK_EMPTY, hardfork::SpecId}, state::AccountInfo, }; use std::{ collections::BTreeMap, fmt::{self, Debug}, io::{Read, Write}, ops::{Mul, Not}, path::PathBuf, sync::Arc, time::Duration, }; use storage::{Blockchain, DEFAULT_HISTORY_LIMIT, MinedTransaction}; use tokio::sync::RwLock as AsyncRwLock; pub mod cache; pub mod fork_db; pub mod in_memory_db; pub mod inspector; pub mod state; pub mod storage; /// Helper trait that combines revm::DatabaseRef with Debug. /// This is needed because alloy-evm requires Debug on Database implementations. /// With trait upcasting now stable, we can now upcast from this trait to revm::DatabaseRef. pub trait DatabaseRef: revm::DatabaseRef + Debug {} impl DatabaseRef for T where T: revm::DatabaseRef + Debug {} impl DatabaseRef for dyn crate::eth::backend::db::Db {} // Gas per transaction not creating a contract. pub const MIN_TRANSACTION_GAS: u128 = 21000; // Gas per transaction creating a contract. pub const MIN_CREATE_GAS: u128 = 53000; pub type State = foundry_evm::utils::StateChangeset; /// A block request, which includes the Pool Transactions if it's Pending pub enum BlockRequest { Pending(Vec>>), Number(u64), } impl fmt::Debug for BlockRequest { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::Pending(txs) => f.debug_tuple("Pending").field(&txs.len()).finish(), Self::Number(n) => f.debug_tuple("Number").field(n).finish(), } } } impl BlockRequest { pub fn block_number(&self) -> BlockNumber { match *self { Self::Pending(_) => BlockNumber::Pending, Self::Number(n) => BlockNumber::Number(n), } } } /// Gives access to the [revm::Database] pub struct Backend { /// Access to [`revm::Database`] abstraction. /// /// 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 [`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>, /// This is set if this is currently forked off another client. fork: Arc>>, /// Provides time related info, like timestamp. time: TimeManager, /// Contains state of custom overrides. cheats: CheatsManager, /// Contains fee data. fees: FeeManager, /// Initialised genesis. genesis: GenesisConfig, /// Listeners for new blocks that get notified when a new block was imported. new_block_listeners: Arc>>>, /// Keeps track of active state snapshots at a specific block. active_state_snapshots: Arc>>, enable_steps_tracing: bool, print_logs: bool, print_traces: bool, /// Recorder used for decoding traces, used together with print_traces call_trace_decoder: Arc, /// How to keep history state prune_state_history_config: PruneStateHistoryConfig, /// max number of blocks with transactions in memory transaction_block_keeper: Option, node_config: Arc>, /// Slots in an epoch slots_in_an_epoch: u64, /// Precompiles to inject to the EVM. precompile_factory: Option>, /// Prevent race conditions during mining mining: Arc>, /// Disable pool balance checks disable_pool_balance_checks: bool, } impl Clone for Backend { fn clone(&self) -> Self { Self { db: self.db.clone(), blockchain: self.blockchain.clone(), states: self.states.clone(), env: self.env.clone(), fork: self.fork.clone(), time: self.time.clone(), cheats: self.cheats.clone(), fees: self.fees.clone(), genesis: self.genesis.clone(), new_block_listeners: self.new_block_listeners.clone(), active_state_snapshots: self.active_state_snapshots.clone(), enable_steps_tracing: self.enable_steps_tracing, print_logs: self.print_logs, print_traces: self.print_traces, call_trace_decoder: self.call_trace_decoder.clone(), prune_state_history_config: self.prune_state_history_config, transaction_block_keeper: self.transaction_block_keeper, node_config: self.node_config.clone(), slots_in_an_epoch: self.slots_in_an_epoch, precompile_factory: self.precompile_factory.clone(), mining: self.mining.clone(), disable_pool_balance_checks: self.disable_pool_balance_checks, } } } impl fmt::Debug for Backend { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("Backend").finish_non_exhaustive() } } // Methods that are generic over any Network. impl Backend { /// Sets the account to impersonate /// /// Returns `true` if the account is already impersonated pub fn impersonate(&self, addr: Address) -> bool { if self.cheats.impersonated_accounts().contains(&addr) { return true; } // Ensure EIP-3607 is disabled let mut env = self.env.write(); env.evm_env.cfg_env.disable_eip3607 = true; self.cheats.impersonate(addr) } /// Removes the account that from the impersonated set /// /// If the impersonated `addr` is a contract then we also reset the code here pub fn stop_impersonating(&self, addr: Address) { self.cheats.stop_impersonating(&addr); } /// If set to true will make every account impersonated pub fn auto_impersonate_account(&self, enabled: bool) { self.cheats.set_auto_impersonate_account(enabled); } /// Returns the configured fork, if any pub fn get_fork(&self) -> Option { self.fork.read().clone() } /// Returns the database pub fn get_db(&self) -> &Arc>> { &self.db } /// Returns the `AccountInfo` from the database pub async fn get_account(&self, address: Address) -> DatabaseResult { Ok(self.db.read().await.basic_ref(address)?.unwrap_or_default()) } /// Whether we're forked off some remote client pub fn is_fork(&self) -> bool { self.fork.read().is_some() } /// Writes the CREATE2 deployer code directly to the database at the address provided. pub async fn set_create2_deployer(&self, address: Address) -> DatabaseResult<()> { self.set_code(address, Bytes::from_static(DEFAULT_CREATE2_DEPLOYER_RUNTIME_CODE)).await?; Ok(()) } /// Updates memory limits that should be more strict when auto-mine is enabled pub(crate) fn update_interval_mine_block_time(&self, block_time: Duration) { self.states.write().update_interval_mine_block_time(block_time) } /// Returns the `TimeManager` responsible for timestamps pub fn time(&self) -> &TimeManager { &self.time } /// Returns the `CheatsManager` responsible for executing cheatcodes pub fn cheats(&self) -> &CheatsManager { &self.cheats } /// Whether to skip blob validation pub fn skip_blob_validation(&self, impersonator: Option
) -> bool { self.cheats().auto_impersonate_accounts() || impersonator .is_some_and(|addr| self.cheats().impersonated_accounts().contains(&addr)) } /// Returns the `FeeManager` that manages fee/pricings pub fn fees(&self) -> &FeeManager { &self.fees } /// The env data of the blockchain pub fn env(&self) -> &Arc> { &self.env } /// Returns the current best hash of the chain pub fn best_hash(&self) -> B256 { self.blockchain.storage.read().best_hash } /// Returns the current best number of the chain pub fn best_number(&self) -> u64 { self.blockchain.storage.read().best_number } /// Sets the block number pub fn set_block_number(&self, number: u64) { let mut env = self.env.write(); env.evm_env.block_env.number = U256::from(number); } /// Returns the client coinbase address. pub fn coinbase(&self) -> Address { self.env.read().evm_env.block_env.beneficiary } /// Returns the client coinbase address. pub fn chain_id(&self) -> U256 { U256::from(self.env.read().evm_env.cfg_env.chain_id) } pub fn set_chain_id(&self, chain_id: u64) { self.env.write().evm_env.cfg_env.chain_id = chain_id; } /// Returns the genesis data for the Beacon API. pub fn genesis_time(&self) -> u64 { self.genesis.timestamp } /// Returns balance of the given account. pub async fn current_balance(&self, address: Address) -> DatabaseResult { Ok(self.get_account(address).await?.balance) } /// Returns balance of the given account. pub async fn current_nonce(&self, address: Address) -> DatabaseResult { Ok(self.get_account(address).await?.nonce) } /// Sets the coinbase address pub fn set_coinbase(&self, address: Address) { self.env.write().evm_env.block_env.beneficiary = address; } /// Sets the nonce of the given address pub async fn set_nonce(&self, address: Address, nonce: U256) -> DatabaseResult<()> { self.db.write().await.set_nonce(address, nonce.try_into().unwrap_or(u64::MAX)) } /// Sets the balance of the given address pub async fn set_balance(&self, address: Address, balance: U256) -> DatabaseResult<()> { self.db.write().await.set_balance(address, balance) } /// Sets the code of the given address pub async fn set_code(&self, address: Address, code: Bytes) -> DatabaseResult<()> { self.db.write().await.set_code(address, code) } /// Sets the value for the given slot of the given address pub async fn set_storage_at( &self, address: Address, slot: U256, val: B256, ) -> DatabaseResult<()> { self.db.write().await.set_storage_at(address, slot.into(), val) } /// Returns the configured specid pub fn spec_id(&self) -> SpecId { self.env.read().evm_env.cfg_env.spec } /// Returns true for post London pub fn is_eip1559(&self) -> bool { (self.spec_id() as u8) >= (SpecId::LONDON as u8) } /// Returns true for post Merge pub fn is_eip3675(&self) -> bool { (self.spec_id() as u8) >= (SpecId::MERGE as u8) } /// Returns true for post Berlin pub fn is_eip2930(&self) -> bool { (self.spec_id() as u8) >= (SpecId::BERLIN as u8) } /// Returns true for post Cancun pub fn is_eip4844(&self) -> bool { (self.spec_id() as u8) >= (SpecId::CANCUN as u8) } /// Returns true for post Prague pub fn is_eip7702(&self) -> bool { (self.spec_id() as u8) >= (SpecId::PRAGUE as u8) } /// Returns true if op-stack deposits are active pub fn is_optimism(&self) -> bool { self.env.read().networks.is_optimism() } /// Returns the precompiles for the current spec. pub fn precompiles(&self) -> BTreeMap { let spec_id = self.env.read().evm_env.cfg_env.spec; let precompiles = Precompiles::new(PrecompileSpecId::from_spec_id(spec_id)); let mut precompiles_map = BTreeMap::::default(); for (address, precompile) in precompiles.inner() { precompiles_map.insert(precompile.id().name().to_string(), *address); } // Extend with configured network precompiles. precompiles_map.extend(self.env.read().networks.precompiles()); if let Some(factory) = &self.precompile_factory { for (address, precompile) in factory.precompiles() { precompiles_map.insert(precompile.precompile_id().to_string(), address); } } precompiles_map } /// Returns the system contracts for the current spec. pub fn system_contracts(&self) -> BTreeMap { let mut system_contracts = BTreeMap::::default(); let spec_id = self.env.read().evm_env.cfg_env.spec; if spec_id >= SpecId::CANCUN { system_contracts.extend(SystemContract::cancun()); } if spec_id >= SpecId::PRAGUE { system_contracts.extend(SystemContract::prague(None)); } system_contracts } /// 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) pub fn ensure_eip1559_active(&self) -> Result<(), BlockchainError> { if self.is_eip1559() { return Ok(()); } Err(BlockchainError::EIP1559TransactionUnsupportedAtHardfork) } /// Returns an error if EIP1559 is not active (pre muirGlacier) pub fn ensure_eip2930_active(&self) -> Result<(), BlockchainError> { if self.is_eip2930() { return Ok(()); } Err(BlockchainError::EIP2930TransactionUnsupportedAtHardfork) } pub fn ensure_eip4844_active(&self) -> Result<(), BlockchainError> { if self.is_eip4844() { return Ok(()); } Err(BlockchainError::EIP4844TransactionUnsupportedAtHardfork) } pub fn ensure_eip7702_active(&self) -> Result<(), BlockchainError> { if self.is_eip7702() { return Ok(()); } Err(BlockchainError::EIP7702TransactionUnsupportedAtHardfork) } /// Returns an error if op-stack deposits are not active pub fn ensure_op_deposits_active(&self) -> Result<(), BlockchainError> { if self.is_optimism() { return Ok(()); } Err(BlockchainError::DepositTransactionUnsupported) } /// Returns the block gas limit pub fn gas_limit(&self) -> u64 { 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().evm_env.block_env.gas_limit = gas_limit; } /// Returns the current base fee pub fn base_fee(&self) -> u64 { self.fees.base_fee() } /// Returns whether the minimum suggested priority fee is enforced pub fn is_min_priority_fee_enforced(&self) -> bool { self.fees.is_min_priority_fee_enforced() } pub fn excess_blob_gas_and_price(&self) -> Option { self.fees.excess_blob_gas_and_price() } /// Sets the current basefee pub fn set_base_fee(&self, basefee: u64) { self.fees.set_base_fee(basefee) } /// Sets the gas price pub fn set_gas_price(&self, price: u128) { self.fees.set_gas_price(price) } pub fn elasticity(&self) -> f64 { self.fees.elasticity() } /// Returns the total difficulty of the chain until this block /// /// Note: this will always be `0` in memory mode /// In forking mode this will always be the total difficulty of the forked block pub fn total_difficulty(&self) -> U256 { self.blockchain.storage.read().total_difficulty } /// Creates a new `evm_snapshot` at the current height. /// /// Returns the id of the snapshot created. pub async fn create_state_snapshot(&self) -> U256 { let num = self.best_number(); let hash = self.best_hash(); let id = self.db.write().await.snapshot_state(); trace!(target: "backend", "creating snapshot {} at {}", id, num); self.active_state_snapshots.lock().insert(id, (num, hash)); id } pub fn list_state_snapshots(&self) -> BTreeMap { self.active_state_snapshots.lock().clone().into_iter().collect() } /// Returns the environment for the next block fn next_env(&self) -> Env { let mut env = self.env.read().clone(); // increase block number for this block env.evm_env.block_env.number = env.evm_env.block_env.number.saturating_add(U256::from(1)); env.evm_env.block_env.basefee = self.base_fee(); env.evm_env.block_env.blob_excess_gas_and_price = self.excess_blob_gas_and_price(); env.evm_env.block_env.timestamp = U256::from(self.time.current_call_timestamp()); env } /// Builds [`Inspector`] with the configured options. fn build_inspector(&self) -> AnvilInspector { let mut inspector = AnvilInspector::default(); if self.print_logs { inspector = inspector.with_log_collector(); } if self.print_traces { inspector = inspector.with_trace_printer(); } inspector } /// Returns a new block event stream that yields Notifications when a new block was added pub fn new_block_notifications(&self) -> NewBlockNotifications { let (tx, rx) = unbounded(); self.new_block_listeners.lock().push(tx); trace!(target: "backed", "added new block listener"); rx } /// Notifies all `new_block_listeners` about the new block fn notify_on_new_block(&self, header: Header, hash: B256) { // cleanup closed notification streams first, if the channel is closed we can remove the // sender half for the set self.new_block_listeners.lock().retain(|tx| !tx.is_closed()); let notification = NewBlockNotification { hash, header: Arc::new(header) }; self.new_block_listeners .lock() .retain(|tx| tx.unbounded_send(notification.clone()).is_ok()); } /// Returns the block number for the given block id pub fn convert_block_number(&self, block: Option) -> u64 { let current = self.best_number(); match block.unwrap_or(BlockNumber::Latest) { BlockNumber::Latest | BlockNumber::Pending => current, BlockNumber::Earliest => 0, BlockNumber::Number(num) => num, BlockNumber::Safe => current.saturating_sub(self.slots_in_an_epoch), BlockNumber::Finalized => current.saturating_sub(self.slots_in_an_epoch * 2), } } /// Returns the block and its hash for the given id fn get_block_with_hash(&self, id: impl Into) -> Option<(Block, B256)> { let hash = match id.into() { BlockId::Hash(hash) => hash.block_hash, BlockId::Number(number) => { let storage = self.blockchain.storage.read(); 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(&num)?, BlockNumber::Safe => { if storage.best_number > (slots_in_an_epoch) { *storage.hashes.get(&(storage.best_number - (slots_in_an_epoch)))? } else { storage.genesis_hash // treat the genesis block as safe "by definition" } } BlockNumber::Finalized => { if storage.best_number > (slots_in_an_epoch * 2) { *storage.hashes.get(&(storage.best_number - (slots_in_an_epoch * 2)))? } else { storage.genesis_hash } } } } }; let block = self.get_block_by_hash(hash)?; Some((block, hash)) } pub fn get_block(&self, id: impl Into) -> Option { self.get_block_with_hash(id).map(|(block, _)| block) } pub fn get_block_by_hash(&self, hash: B256) -> Option { self.blockchain.get_block_by_hash(&hash) } /// Returns the traces for the given transaction pub(crate) fn mined_parity_trace_transaction( &self, hash: B256, ) -> Option> { self.blockchain.storage.read().transactions.get(&hash).map(|tx| tx.parity_traces()) } /// Returns the traces for the given block pub(crate) fn mined_parity_trace_block( &self, block: u64, ) -> Option> { let block = self.get_block(block)?; let mut traces = vec![]; let storage = self.blockchain.storage.read(); for tx in block.body.transactions { traces.extend(storage.transactions.get(&tx.hash())?.parity_traces()); } Some(traces) } /// Returns the mined transaction for the given hash pub(crate) fn mined_transaction(&self, hash: B256) -> Option> { self.blockchain.storage.read().transactions.get(&hash).cloned() } /// Overrides the given signature to impersonate the specified address during ecrecover. pub async fn impersonate_signature( &self, signature: Bytes, address: Address, ) -> Result<(), BlockchainError> { self.cheats.add_recover_override(signature, address); Ok(()) } /// Returns code by its hash pub async fn debug_code_by_hash( &self, code_hash: B256, block_id: Option, ) -> Result, BlockchainError> { if let Ok(code) = self.db.read().await.code_by_hash_ref(code_hash) { return Ok(Some(code.original_bytes())); } if let Some(fork) = self.get_fork() { return Ok(fork.debug_code_by_hash(code_hash, block_id).await?); } Ok(None) } /// Returns the value associated with a key from the database /// Currently only supports bytecode lookups. /// /// Based on Reth implementation: /// /// Key should be: 0x63 (1-byte prefix) + 32 bytes (code_hash) /// Total key length must be 33 bytes. pub async fn debug_db_get(&self, key: String) -> Result, BlockchainError> { let key_bytes = if key.starts_with("0x") { hex::decode(&key) .map_err(|_| BlockchainError::Message("Invalid hex key".to_string()))? } else { key.into_bytes() }; // Validate key length: must be 33 bytes (1 byte prefix + 32 bytes code hash) if key_bytes.len() != 33 { return Err(BlockchainError::Message(format!( "Invalid key length: expected 33 bytes, got {}", key_bytes.len() ))); } // Check for bytecode prefix (0x63 = 'c' in ASCII) if key_bytes[0] != 0x63 { return Err(BlockchainError::Message( "Key prefix must be 0x63 for code hash lookups".to_string(), )); } let code_hash = B256::from_slice(&key_bytes[1..33]); // Use the existing debug_code_by_hash method to retrieve the bytecode self.debug_code_by_hash(code_hash, None).await } fn mined_block_by_hash(&self, hash: B256) -> Option { let block = self.blockchain.get_block_by_hash(&hash)?; Some(self.convert_block_with_hash(block, Some(hash))) } pub(crate) async fn mined_transactions_by_block_number( &self, number: BlockNumber, ) -> Option> { if let Some(block) = self.get_block(number) { return self.mined_transactions_in_block(&block); } None } /// Returns all transactions given a block pub(crate) fn mined_transactions_in_block( &self, block: &Block, ) -> Option> { let mut transactions = Vec::with_capacity(block.body.transactions.len()); let base_fee = block.header.base_fee_per_gas(); let storage = self.blockchain.storage.read(); for hash in block.body.transactions.iter().map(|tx| tx.hash()) { let info = storage.transactions.get(&hash)?.info.clone(); let tx = block.body.transactions.get(info.transaction_index as usize)?.clone(); let tx = transaction_build(Some(hash), tx, Some(block), Some(info), base_fee); transactions.push(tx); } Some(transactions) } pub fn mined_block_by_number(&self, number: BlockNumber) -> Option { let (block, hash) = self.get_block_with_hash(number)?; let mut block = self.convert_block_with_hash(block, Some(hash)); block.transactions.convert_to_hashes(); Some(block) } pub fn get_full_block(&self, id: impl Into) -> Option { let (block, hash) = self.get_block_with_hash(id)?; let transactions = self.mined_transactions_in_block(&block)?; let mut block = self.convert_block_with_hash(block, Some(hash)); block.inner.transactions = BlockTransactions::Full(transactions); Some(block) } /// Takes a block as it's stored internally and returns the eth api conform block format. pub fn convert_block(&self, block: Block) -> AnyRpcBlock { self.convert_block_with_hash(block, None) } /// Takes a block as it's stored internally and returns the eth api conform block format. /// If `known_hash` is provided, it will be used instead of computing `hash_slow()`. pub fn convert_block_with_hash(&self, block: Block, known_hash: Option) -> AnyRpcBlock { let size = U256::from(alloy_rlp::encode(&block).len() as u32); let header = block.header.clone(); let transactions = block.body.transactions; let hash = known_hash.unwrap_or_else(|| header.hash_slow()); let Header { number, withdrawals_root, .. } = header; let block = AlloyBlock { header: AlloyHeader { inner: AnyHeader::from(header), hash, total_difficulty: Some(self.total_difficulty()), size: Some(size), }, transactions: alloy_rpc_types::BlockTransactions::Hashes( transactions.into_iter().map(|tx| tx.hash()).collect(), ), uncles: vec![], withdrawals: withdrawals_root.map(|_| Default::default()), }; let mut block = WithOtherFields::new(block); // If Arbitrum, apply chain specifics to converted block. if is_arbitrum(self.env.read().evm_env.cfg_env.chain_id) { // Set `l1BlockNumber` field. block.other.insert("l1BlockNumber".to_string(), number.into()); } AnyRpcBlock::from(block) } pub async fn block_by_hash(&self, hash: B256) -> Result, BlockchainError> { trace!(target: "backend", "get block by hash {:?}", hash); if let tx @ Some(_) = self.mined_block_by_hash(hash) { return Ok(tx); } if let Some(fork) = self.get_fork() { return Ok(fork.block_by_hash(hash).await?); } Ok(None) } pub async fn block_by_hash_full( &self, hash: B256, ) -> Result, BlockchainError> { trace!(target: "backend", "get block by hash {:?}", hash); if let tx @ Some(_) = self.get_full_block(hash) { return Ok(tx); } if let Some(fork) = self.get_fork() { return Ok(fork.block_by_hash_full(hash).await?); } Ok(None) } pub async fn block_by_number( &self, number: BlockNumber, ) -> Result, BlockchainError> { trace!(target: "backend", "get block by number {:?}", number); if let tx @ Some(_) = self.mined_block_by_number(number) { return Ok(tx); } if let Some(fork) = self.get_fork() { let number = self.convert_block_number(Some(number)); if fork.predates_fork_inclusive(number) { return Ok(fork.block_by_number(number).await?); } } Ok(None) } pub async fn block_by_number_full( &self, number: BlockNumber, ) -> Result, BlockchainError> { trace!(target: "backend", "get block by number {:?}", number); if let tx @ Some(_) = self.get_full_block(number) { return Ok(tx); } if let Some(fork) = self.get_fork() { let number = self.convert_block_number(Some(number)); if fork.predates_fork_inclusive(number) { return Ok(fork.block_by_number_full(number).await?); } } Ok(None) } /// Converts the `BlockNumber` into a numeric value /// /// # Errors /// /// returns an error if the requested number is larger than the current height pub async fn ensure_block_number>( &self, block_id: Option, ) -> Result { let current = self.best_number(); let requested = match block_id.map(Into::into).unwrap_or(BlockId::Number(BlockNumber::Latest)) { BlockId::Hash(hash) => { self.block_by_hash(hash.block_hash) .await? .ok_or(BlockchainError::BlockNotFound)? .header .number } BlockId::Number(num) => match num { BlockNumber::Latest | BlockNumber::Pending => current, BlockNumber::Earliest => U64::ZERO.to::(), BlockNumber::Number(num) => num, BlockNumber::Safe => current.saturating_sub(self.slots_in_an_epoch), BlockNumber::Finalized => current.saturating_sub(self.slots_in_an_epoch * 2), }, }; if requested > current { Err(BlockchainError::BlockOutOfRange(current, requested)) } else { Ok(requested) } } /// Creates an EVM instance with optionally injected precompiles. fn new_eth_evm_with_inspector_ref<'db, I, DB>( &self, db: &'db DB, env: &Env, inspector: &'db mut I, ) -> EitherEvm, &'db mut I, PrecompilesMap> where DB: DatabaseRef + ?Sized, I: Inspector>> + Inspector>>, WrapDatabaseRef<&'db DB>: Database, { let mut evm = new_eth_evm_with_inspector(WrapDatabaseRef(db), env, inspector); self.env.read().networks.inject_precompiles(evm.precompiles_mut()); if let Some(factory) = &self.precompile_factory { evm.precompiles_mut().extend_precompiles(factory.precompiles()); } let cheats = Arc::new(self.cheats.clone()); if cheats.has_recover_overrides() { let cheat_ecrecover = CheatEcrecover::new(Arc::clone(&cheats)); evm.precompiles_mut().apply_precompile(&EC_RECOVER, move |_| { Some(DynPrecompile::new_stateful( cheat_ecrecover.precompile_id().clone(), move |input| cheat_ecrecover.call(input), )) }); } evm } /// ## EVM settings /// /// This modifies certain EVM settings to mirror geth's `SkipAccountChecks` when transacting requests, see also: : /// /// - `disable_eip3607` is set to `true` /// - `disable_base_fee` is set to `true` /// - `tx_gas_limit_cap` is set to `Some(u64::MAX)` indicating no gas limit cap /// - `nonce` check is skipped fn build_call_env( &self, request: WithOtherFields, fee_details: FeeDetails, block_env: BlockEnv, ) -> Env { let tx_type = request.minimal_tx_type() as u8; let WithOtherFields:: { inner: TransactionRequest { from, to, gas, value, input, access_list, blob_versioned_hashes, authorization_list, nonce, sidecar: _, chain_id, .. // Rest of the gas fees related fields are taken from `fee_details` }, other, } = request; let FeeDetails { gas_price, max_fee_per_gas, max_priority_fee_per_gas, max_fee_per_blob_gas, } = fee_details; let gas_limit = gas.unwrap_or(block_env.gas_limit); let mut env = self.env.read().clone(); 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.evm_env.cfg_env.disable_block_gas_limit = true; env.evm_env.cfg_env.tx_gas_limit_cap = Some(u64::MAX); // The basefee should be ignored for calls against state for // - eth_call // - eth_estimateGas // - eth_createAccessList // - tracing env.evm_env.cfg_env.disable_base_fee = true; // Disable nonce check in revm env.evm_env.cfg_env.disable_nonce_check = 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) }); let caller = from.unwrap_or_default(); let to = to.as_ref().and_then(TxKind::to); let blob_hashes = blob_versioned_hashes.unwrap_or_default(); 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; } 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.evm_env.cfg_env.disable_base_fee = true; } // Deposit transaction? if let Ok(deposit) = get_deposit_tx_parts(&other) { env.tx.deposit = deposit; } env } pub fn call_with_state( &self, state: &dyn DatabaseRef, request: WithOtherFields, fee_details: FeeDetails, block_env: BlockEnv, ) -> Result<(InstructionResult, Option, u128, State), BlockchainError> { let mut inspector = self.build_inspector(); let env = self.build_call_env(request, fee_details, block_env); let mut evm = self.new_eth_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)) } ExecutionResult::Revert { gas_used, output } => { (InstructionResult::Revert, gas_used, Some(Output::Call(output))) } ExecutionResult::Halt { reason, gas_used } => { (op_haltreason_to_instruction_result(reason), gas_used, None) } }; drop(evm); inspector.print_logs(); if self.print_traces { inspector.into_print_traces(self.call_trace_decoder.clone()); } Ok((exit_reason, out, gas_used as u128, state)) } pub fn build_access_list_with_state( &self, state: &dyn DatabaseRef, request: WithOtherFields, fee_details: FeeDetails, block_env: BlockEnv, ) -> Result<(InstructionResult, Option, u64, AccessList), BlockchainError> { let mut inspector = 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_eth_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)) } ExecutionResult::Revert { gas_used, output } => { (InstructionResult::Revert, gas_used, Some(Output::Call(output))) } ExecutionResult::Halt { reason, gas_used } => { (op_haltreason_to_instruction_result(reason), gas_used, None) } }; drop(evm); let access_list = inspector.access_list(); Ok((exit_reason, out, gas_used, access_list)) } pub fn get_code_with_state( &self, state: &dyn DatabaseRef, address: Address, ) -> Result { trace!(target: "backend", "get code for {:?}", address); let account = state.basic_ref(address)?.unwrap_or_default(); if account.code_hash == KECCAK_EMPTY { // if the code hash is `KECCAK_EMPTY`, we check no further return Ok(Default::default()); } let code = if let Some(code) = account.code { code } else { state.code_by_hash_ref(account.code_hash)? }; Ok(code.bytes()[..code.len()].to_vec().into()) } pub fn get_balance_with_state( &self, state: D, address: Address, ) -> Result where D: DatabaseRef, { trace!(target: "backend", "get balance for {:?}", address); Ok(state.basic_ref(address)?.unwrap_or_default().balance) } pub async fn transaction_by_block_number_and_index( &self, number: BlockNumber, index: Index, ) -> Result, BlockchainError> { if let Some(block) = self.mined_block_by_number(number) { return Ok(self.mined_transaction_by_block_hash_and_index(block.header.hash, index)); } if let Some(fork) = self.get_fork() { let number = self.convert_block_number(Some(number)); if fork.predates_fork(number) { return Ok(fork .transaction_by_block_number_and_index(number, index.into()) .await?); } } Ok(None) } pub async fn transaction_by_block_hash_and_index( &self, hash: B256, index: Index, ) -> Result, BlockchainError> { if let tx @ Some(_) = self.mined_transaction_by_block_hash_and_index(hash, index) { return Ok(tx); } if let Some(fork) = self.get_fork() { return Ok(fork.transaction_by_block_hash_and_index(hash, index.into()).await?); } Ok(None) } pub fn mined_transaction_by_block_hash_and_index( &self, block_hash: B256, index: Index, ) -> Option { let (info, block, tx) = { let storage = self.blockchain.storage.read(); let block = storage.blocks.get(&block_hash).cloned()?; let index: usize = index.into(); let tx = block.body.transactions.get(index)?.clone(); let info = storage.transactions.get(&tx.hash())?.info.clone(); (info, block, tx) }; Some(transaction_build( Some(info.transaction_hash), tx, Some(&block), Some(info), block.header.base_fee_per_gas(), )) } pub async fn transaction_by_hash( &self, hash: B256, ) -> Result, BlockchainError> { trace!(target: "backend", "transaction_by_hash={:?}", hash); if let tx @ Some(_) = self.mined_transaction_by_hash(hash) { return Ok(tx); } if let Some(fork) = self.get_fork() { return fork .transaction_by_hash(hash) .await .map_err(BlockchainError::AlloyForkProvider); } Ok(None) } pub fn mined_transaction_by_hash(&self, hash: B256) -> Option { let (info, block) = { let storage = self.blockchain.storage.read(); let MinedTransaction { info, block_hash, .. } = storage.transactions.get(&hash)?.clone(); let block = storage.blocks.get(&block_hash).cloned()?; (info, block) }; let tx = block.body.transactions.get(info.transaction_index as usize)?.clone(); Some(transaction_build( Some(info.transaction_hash), tx, Some(&block), Some(info), block.header.base_fee_per_gas(), )) } /// Returns the traces for the given transaction pub async fn trace_transaction( &self, hash: B256, ) -> Result, BlockchainError> { if let Some(traces) = self.mined_parity_trace_transaction(hash) { return Ok(traces); } if let Some(fork) = self.get_fork() { return Ok(fork.trace_transaction(hash).await?); } Ok(vec![]) } /// Returns the traces for the given block pub async fn trace_block( &self, block: BlockNumber, ) -> Result, BlockchainError> { let number = self.convert_block_number(Some(block)); if let Some(traces) = self.mined_parity_trace_block(number) { return Ok(traces); } if let Some(fork) = self.get_fork() && fork.predates_fork(number) { return Ok(fork.trace_block(number).await?); } Ok(vec![]) } /// Replays all transactions in a block and returns the requested traces for each transaction pub async fn trace_replay_block_transactions( &self, block: BlockNumber, trace_types: HashSet, ) -> Result, BlockchainError> { let block_number = self.convert_block_number(Some(block)); // Try mined blocks first if let Some(results) = self.mined_parity_trace_replay_block_transactions(block_number, &trace_types) { return Ok(results); } // Fallback to fork if block predates fork if let Some(fork) = self.get_fork() && fork.predates_fork(block_number) { return Ok(fork.trace_replay_block_transactions(block_number, trace_types).await?); } Ok(vec![]) } /// Returns the trace results for all transactions in a mined block by replaying them fn mined_parity_trace_replay_block_transactions( &self, block_number: u64, trace_types: &HashSet, ) -> Option> { let block = self.get_block(block_number)?; // Execute this in the context of the parent state let parent_hash = block.header.parent_hash; let trace_config = TracingInspectorConfig::from_parity_config(trace_types); let read_guard = self.states.upgradable_read(); if let Some(state) = read_guard.get_state(&parent_hash) { self.replay_block_transactions_with_inspector(&block, state, trace_config, trace_types) } else { let mut write_guard = RwLockUpgradableReadGuard::upgrade(read_guard); let state = write_guard.get_on_disk_state(&parent_hash)?; self.replay_block_transactions_with_inspector(&block, state, trace_config, trace_types) } } /// Replays all transactions in a block with the tracing inspector to generate TraceResults fn replay_block_transactions_with_inspector( &self, block: &Block, parent_state: &StateDb, trace_config: TracingInspectorConfig, trace_types: &HashSet, ) -> Option> { let mut cache_db = CacheDB::new(Box::new(parent_state)); let mut results = Vec::new(); // Configure the block environment let mut env = self.env.read().clone(); env.evm_env.block_env = block_env_from_header(&block.header); // Execute each transaction in the block with tracing for tx_envelope in &block.body.transactions { let tx_hash = tx_envelope.hash(); // Create a fresh inspector for this transaction let mut inspector = TracingInspector::new(trace_config); // Prepare transaction environment let pending_tx = PendingTransaction::from_maybe_impersonated(tx_envelope.clone()).ok()?; let mut tx_env: OpTransaction = FromRecoveredTx::from_recovered_tx( pending_tx.transaction.as_ref(), *pending_tx.sender(), ); if env.networks.is_optimism() { tx_env.enveloped_tx = Some(pending_tx.transaction.encoded_2718().into()); } // Execute the transaction with the inspector let mut evm = self.new_eth_evm_with_inspector_ref(&cache_db, &env, &mut inspector); let result = evm.transact(tx_env.clone()).ok()?; // Build TraceResults from the inspector and execution result let full_trace = inspector .into_parity_builder() .into_trace_results_with_state(&result, trace_types, &cache_db) .ok()?; results.push(TraceResultsWithTransactionHash { transaction_hash: tx_hash, full_trace }); // Commit the state changes for the next transaction cache_db.commit(result.state); } Some(results) } // Returns the traces matching a given filter pub async fn trace_filter( &self, filter: TraceFilter, ) -> Result, BlockchainError> { let matcher = filter.matcher(); let start = filter.from_block.unwrap_or(0); let end = filter.to_block.unwrap_or_else(|| self.best_number()); if start > end { return Err(BlockchainError::RpcError(RpcError::invalid_params( "invalid block range, ensure that to block is greater than from block".to_string(), ))); } let dist = end - start; if dist > 300 { return Err(BlockchainError::RpcError(RpcError::invalid_params( "block range too large, currently limited to 300".to_string(), ))); } // Accumulate tasks for block range let mut trace_tasks = vec![]; for num in start..=end { trace_tasks.push(self.trace_block(num.into())); } // Execute tasks and filter traces let traces = futures::future::try_join_all(trace_tasks).await?; let filtered_traces = traces.into_iter().flatten().filter(|trace| matcher.matches(&trace.trace)); // Apply after and count let filtered_traces: Vec<_> = if let Some(after) = filter.after { filtered_traces.skip(after as usize).collect() } else { filtered_traces.collect() }; let filtered_traces: Vec<_> = if let Some(count) = filter.count { filtered_traces.into_iter().take(count as usize).collect() } else { filtered_traces }; Ok(filtered_traces) } pub fn get_blobs_by_block_id( &self, id: impl Into, versioned_hashes: Vec, ) -> Result>> { Ok(self.get_block(id).map(|block| { block .body .transactions .iter() .filter_map(|tx| tx.as_ref().sidecar()) .flat_map(|sidecar| { sidecar.sidecar.blobs().iter().zip(sidecar.sidecar.commitments().iter()) }) .filter(|(_, commitment)| { // Filter blobs by versioned_hashes if provided versioned_hashes.is_empty() || versioned_hashes.contains(&kzg_to_versioned_hash(commitment.as_slice())) }) .map(|(blob, _)| *blob) .collect() })) } pub fn get_blob_by_versioned_hash(&self, hash: B256) -> Result> { let storage = self.blockchain.storage.read(); for block in storage.blocks.values() { for tx in &block.body.transactions { let typed_tx = tx.as_ref(); if let Some(sidecar) = typed_tx.sidecar() { for versioned_hash in sidecar.sidecar.versioned_hashes() { if versioned_hash == hash && let Some(index) = sidecar.sidecar.commitments().iter().position(|commitment| { kzg_to_versioned_hash(commitment.as_slice()) == *hash }) && let Some(blob) = sidecar.sidecar.blobs().get(index) { return Ok(Some(*blob)); } } } } } Ok(None) } /// Initialises the balance of the given accounts #[expect(clippy::too_many_arguments)] pub async fn with_genesis( db: Arc>>, env: Arc>, genesis: GenesisConfig, fees: FeeManager, fork: Arc>>, enable_steps_tracing: bool, print_logs: bool, print_traces: bool, call_trace_decoder: Arc, prune_state_history_config: PruneStateHistoryConfig, max_persisted_states: Option, transaction_block_keeper: Option, automine_block_time: Option, cache_path: Option, node_config: Arc>, ) -> Result { // if this is a fork then adjust the blockchain storage let blockchain = if let Some(fork) = fork.read().as_ref() { trace!(target: "backend", "using forked blockchain at {}", fork.block_number()); Blockchain::forked(fork.block_number(), fork.block_hash(), fork.total_difficulty()) } else { let env = env.read(); Blockchain::new( &env, env.evm_env.cfg_env.spec, fees.is_eip1559().then(|| fees.base_fee()), genesis.timestamp, genesis.number, ) }; // Sync EVM block.number with genesis for non-fork mode. // Fork mode syncs in setup_fork_db_config() instead. if fork.read().is_none() { let mut write_env = env.write(); write_env.evm_env.block_env.number = U256::from(genesis.number); } let start_timestamp = if let Some(fork) = fork.read().as_ref() { fork.timestamp() } else { genesis.timestamp }; let mut states = if prune_state_history_config.is_config_enabled() { // if prune state history is enabled, configure the state cache only for memory prune_state_history_config .max_memory_history .map(|limit| InMemoryBlockStates::new(limit, 0)) .unwrap_or_default() .memory_only() } else if max_persisted_states.is_some() { max_persisted_states .map(|limit| InMemoryBlockStates::new(DEFAULT_HISTORY_LIMIT, limit)) .unwrap_or_default() } else { Default::default() }; if let Some(cache_path) = cache_path { states = states.disk_path(cache_path); } let (slots_in_an_epoch, precompile_factory, disable_pool_balance_checks) = { let cfg = node_config.read().await; (cfg.slots_in_an_epoch, cfg.precompile_factory.clone(), cfg.disable_pool_balance_checks) }; let backend = Self { db, blockchain, states: Arc::new(RwLock::new(states)), env, fork, time: TimeManager::new(start_timestamp), cheats: Default::default(), new_block_listeners: Default::default(), fees, genesis, active_state_snapshots: Arc::new(Mutex::new(Default::default())), enable_steps_tracing, print_logs, print_traces, call_trace_decoder, prune_state_history_config, transaction_block_keeper, node_config, slots_in_an_epoch, precompile_factory, mining: Arc::new(tokio::sync::Mutex::new(())), disable_pool_balance_checks, }; if let Some(interval_block_time) = automine_block_time { backend.update_interval_mine_block_time(interval_block_time); } // Note: this can only fail in forking mode, in which case we can't recover backend.apply_genesis().await.wrap_err("failed to create genesis")?; Ok(backend) } /// Applies the configured genesis settings /// /// This will fund, create the genesis accounts async fn apply_genesis(&self) -> Result<(), DatabaseError> { trace!(target: "backend", "setting genesis balances"); if self.fork.read().is_some() { // fetch all account first let mut genesis_accounts_futures = Vec::with_capacity(self.genesis.accounts.len()); for address in self.genesis.accounts.iter().copied() { let db = Arc::clone(&self.db); // The forking Database backend can handle concurrent requests, we can fetch all dev // accounts concurrently by spawning the job to a new task genesis_accounts_futures.push(tokio::task::spawn(async move { let db = db.read().await; let info = db.basic_ref(address)?.unwrap_or_default(); Ok::<_, DatabaseError>((address, info)) })); } let genesis_accounts = futures::future::join_all(genesis_accounts_futures).await; let mut db = self.db.write().await; for res in genesis_accounts { let (address, mut info) = res.unwrap()?; info.balance = self.genesis.balance; db.insert_account(address, info.clone()); } } else { let mut db = self.db.write().await; for (account, info) in self.genesis.account_infos() { db.insert_account(account, info); } // insert the new genesis hash to the database so it's available for the next block in // the evm db.insert_block_hash(U256::from(self.best_number()), self.best_hash()); // Deploy EIP-2935 blockhash history storage contract if Prague is active. if self.spec_id() >= SpecId::PRAGUE { db.set_code( eip2935::HISTORY_STORAGE_ADDRESS, eip2935::HISTORY_STORAGE_CODE.clone(), )?; } } 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(()) } /// Resets the fork to a fresh state pub async fn reset_fork(&self, forking: Forking) -> Result<(), BlockchainError> { if !self.is_fork() { if let Some(eth_rpc_url) = forking.json_rpc_url.clone() { let mut env = self.env.read().clone(); let (db, config) = { let mut node_config = self.node_config.write().await; // we want to force the correct base fee for the next block during // `setup_fork_db_config` node_config.base_fee.take(); node_config.setup_fork_db_config(eth_rpc_url, &mut env, &self.fees).await? }; *self.db.write().await = Box::new(db); let fork = ClientFork::new(config, Arc::clone(&self.db)); *self.env.write() = env; *self.fork.write() = Some(fork); } else { return Err(RpcError::invalid_params( "Forking not enabled and RPC URL not provided to start forking", ) .into()); } } if let Some(fork) = self.get_fork() { let block_number = forking.block_number.map(BlockNumber::from).unwrap_or(BlockNumber::Latest); // reset the fork entirely and reapply the genesis config fork.reset(forking.json_rpc_url.clone(), block_number).await?; let fork_block_number = fork.block_number(); let fork_block = fork .block_by_number(fork_block_number) .await? .ok_or(BlockchainError::BlockNotFound)?; // update all settings related to the forked block { if let Some(fork_url) = forking.json_rpc_url { self.reset_block_number(fork_url, fork_block_number).await?; } else { // If rpc url is unspecified, then update the fork with the new block number and // existing rpc url, this updates the cache path { let maybe_fork_url = { self.node_config.read().await.eth_rpc_url.clone() }; if let Some(fork_url) = maybe_fork_url { self.reset_block_number(fork_url, fork_block_number).await?; } } let gas_limit = self.node_config.read().await.fork_gas_limit(&fork_block); let mut env = self.env.write(); env.evm_env.cfg_env.chain_id = fork.chain_id(); env.evm_env.block_env = BlockEnv { number: U256::from(fork_block_number), timestamp: U256::from(fork_block.header.timestamp()), gas_limit, difficulty: fork_block.header.difficulty(), prevrandao: Some(fork_block.header.mix_hash().unwrap_or_default()), // 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(), gas_limit, fork_block.header.base_fee_per_gas().unwrap_or_default(), ); self.fees.set_base_fee(next_block_base_fee); } // reset the time to the timestamp of the forked block self.time.reset(fork_block.header.timestamp()); // also reset the total difficulty self.blockchain.storage.write().total_difficulty = fork.total_difficulty(); } // reset storage *self.blockchain.storage.write() = BlockchainStorage::forked( fork.block_number(), fork.block_hash(), fork.total_difficulty(), ); self.states.write().clear(); self.db.write().await.clear(); self.apply_genesis().await?; trace!(target: "backend", "reset fork"); Ok(()) } else { Err(RpcError::invalid_params("Forking not enabled").into()) } } /// Resets the backend to a fresh in-memory state, clearing all existing data pub async fn reset_to_in_mem(&self) -> Result<(), BlockchainError> { // Clear the fork if any exists *self.fork.write() = None; // Get environment and genesis config let env = self.env.read().clone(); let genesis_timestamp = self.genesis.timestamp; let genesis_number = self.genesis.number; let spec_id = self.spec_id(); // Reset environment to genesis state { let mut env = self.env.write(); env.evm_env.block_env.number = U256::from(genesis_number); env.evm_env.block_env.timestamp = U256::from(genesis_timestamp); // Reset other block env fields to their defaults env.evm_env.block_env.basefee = self.fees.base_fee(); env.evm_env.block_env.prevrandao = Some(B256::ZERO); } // Clear all storage and reinitialize with genesis let base_fee = if self.fees.is_eip1559() { Some(self.fees.base_fee()) } else { None }; *self.blockchain.storage.write() = BlockchainStorage::new(&env, spec_id, base_fee, genesis_timestamp, genesis_number); self.states.write().clear(); // Clear the database self.db.write().await.clear(); // Reset time manager self.time.reset(genesis_timestamp); // Reset fees to initial state if self.fees.is_eip1559() { self.fees.set_base_fee(crate::eth::fees::INITIAL_BASE_FEE); } self.fees.set_gas_price(crate::eth::fees::INITIAL_GAS_PRICE); // Reapply genesis configuration self.apply_genesis().await?; trace!(target: "backend", "reset to fresh in-memory state"); Ok(()) } async fn reset_block_number( &self, fork_url: String, fork_block_number: u64, ) -> Result<(), BlockchainError> { let mut node_config = self.node_config.write().await; node_config.fork_choice = Some(ForkChoice::Block(fork_block_number as i128)); let mut env = self.env.read().clone(); let (forked_db, client_fork_config) = node_config.setup_fork_db_config(fork_url, &mut env, &self.fees).await?; *self.db.write().await = Box::new(forked_db); let fork = ClientFork::new(client_fork_config, Arc::clone(&self.db)); *self.fork.write() = Some(fork); *self.env.write() = env; Ok(()) } /// Reverts the state to the state snapshot identified by the given `id`. pub async fn revert_state_snapshot(&self, id: U256) -> Result { let block = { self.active_state_snapshots.lock().remove(&id) }; if let Some((num, hash)) = block { let best_block_hash = { // revert the storage that's newer than the snapshot let current_height = self.best_number(); let mut storage = self.blockchain.storage.write(); for n in ((num + 1)..=current_height).rev() { trace!(target: "backend", "reverting block {}", n); if let Some(hash) = storage.hashes.remove(&n) && let Some(block) = storage.blocks.remove(&hash) { for tx in block.body.transactions { let _ = storage.transactions.remove(&tx.hash()); } } } storage.best_number = num; storage.best_hash = hash; hash }; let block = self.block_by_hash(best_block_hash).await?.ok_or(BlockchainError::BlockNotFound)?; let reset_time = block.header.timestamp(); self.time.reset(reset_time); let mut env = self.env.write(); env.evm_env.block_env = BlockEnv { number: U256::from(num), timestamp: U256::from(block.header.timestamp()), difficulty: block.header.difficulty(), // ensures prevrandao is set prevrandao: Some(block.header.mix_hash().unwrap_or_default()), 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)) } /// executes the transactions without writing to the underlying database pub async fn inspect_tx( &self, tx: Arc>, ) -> Result< (InstructionResult, Option, u64, State, Vec), BlockchainError, > { let mut env = self.next_env(); env.tx = FromRecoveredTx::from_recovered_tx( tx.pending_transaction.transaction.as_ref(), *tx.pending_transaction.sender(), ); if env.networks.is_optimism() { env.tx.enveloped_tx = Some(tx.pending_transaction.transaction.encoded_2718().into()); } let db = self.db.read().await; let mut inspector = self.build_inspector(); let mut evm = self.new_eth_evm_with_inspector_ref(&**db, &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)) } ExecutionResult::Revert { gas_used, output } => { (InstructionResult::Revert, gas_used, Some(Output::Call(output)), None) } ExecutionResult::Halt { reason, gas_used } => { let eth_reason = op_haltreason_to_instruction_result(reason); (eth_reason, gas_used, None, None) } }; drop(evm); inspector.print_logs(); if self.print_traces { inspector.print_traces(self.call_trace_decoder.clone()); } Ok((exit_reason, out, gas_used, state, logs.unwrap_or_default())) } } impl Backend where N::ReceiptEnvelope: alloy_consensus::TxReceipt + Clone, { /// 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, block_hash: B256) -> Vec { let mut all_logs = Vec::new(); let mut block_log_index = 0u32; let storage = self.blockchain.storage.read(); for tx in block.body.transactions { 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 { if filter.matches(log) { all_logs.push(Log { inner: log.clone(), block_hash: Some(block_hash), block_number: Some(block.header.number()), block_timestamp: Some(block.header.timestamp()), transaction_hash: Some(transaction_hash), transaction_index: Some(tx.info.transaction_index), log_index: Some(block_log_index as u64), removed: false, }); } block_log_index += 1; } } all_logs } /// Returns the logs of the block that match the filter async fn logs_for_block( &self, filter: Filter, hash: B256, ) -> Result, BlockchainError> { if let Some(block) = self.blockchain.get_block_by_hash(&hash) { return Ok(self.mined_logs_for_block(filter, block, hash)); } if let Some(fork) = self.get_fork() { return Ok(fork.logs(&filter).await?); } Ok(Vec::new()) } /// Returns the logs that match the filter in the given range of blocks async fn logs_for_range( &self, filter: &Filter, mut from: u64, to: u64, ) -> Result, BlockchainError> { let mut all_logs = Vec::new(); // get the range that predates the fork if any if let Some(fork) = self.get_fork() { let mut to_on_fork = to; if !fork.predates_fork(to) { // adjust the ranges to_on_fork = fork.block_number(); } if fork.predates_fork_inclusive(from) { // this data is only available on the forked client let filter = filter.clone().from_block(from).to_block(to_on_fork); all_logs = fork.logs(&filter).await?; // update the range from = fork.block_number() + 1; } } for number in from..=to { if let Some((block, hash)) = self.get_block_with_hash(number) { all_logs.extend(self.mined_logs_for_block(filter.clone(), block, hash)); } } Ok(all_logs) } /// Returns the logs according to the filter pub async fn logs(&self, filter: Filter) -> Result, BlockchainError> { trace!(target: "backend", "get logs [{:?}]", filter); if let Some(hash) = filter.get_block_hash() { self.logs_for_block(filter, hash).await } else { let best = self.best_number(); let to_block = self.convert_block_number(filter.block_option.get_to_block().copied()).min(best); let from_block = self.convert_block_number(filter.block_option.get_from_block().copied()); if from_block > best { return Err(BlockchainError::BlockOutOfRange(best, from_block)); } self.logs_for_range(&filter, from_block, to_block).await } } /// Returns all receipts of the block pub fn mined_receipts(&self, hash: B256) -> Option> { let block = self.mined_block_by_hash(hash)?; let mut receipts = Vec::new(); let storage = self.blockchain.storage.read(); for tx in block.transactions.hashes() { let receipt = storage.transactions.get(&tx)?.receipt.clone(); receipts.push(receipt); } Some(receipts) } } // Mining methods — generic over N: Network, with Foundry-associated-type bounds for now. impl Backend where Self: TransactionValidator, N: Network, { /// Mines a new block and stores it. /// /// this will execute all transaction in the order they come in and return all the markers they /// provide. pub async fn mine_block( &self, pool_transactions: Vec>>, ) -> MinedBlockOutcome { self.do_mine_block(pool_transactions).await } async fn do_mine_block( &self, pool_transactions: Vec>>, ) -> MinedBlockOutcome { let _mining_guard = self.mining.lock().await; trace!(target: "backend", "creating new block with {} transactions", pool_transactions.len()); let (outcome, header, block_hash) = { let current_base_fee = self.base_fee(); let current_excess_blob_gas_and_price = self.excess_blob_gas_and_price(); let mut env = self.env.read().clone(); 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.evm_env.cfg_env.disable_base_fee = true; } let block_number = self.blockchain.storage.read().best_number.saturating_add(1); // increase block number for this block if is_arbitrum(env.evm_env.cfg_env.chain_id) { // Temporary set `env.block.number` to `block_number` for Arbitrum chains. env.evm_env.block_env.number = U256::from(block_number); } else { env.evm_env.block_env.number = env.evm_env.block_env.number.saturating_add(U256::from(1)); } 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; let best_hash = self.blockchain.storage.read().best_hash; let mut input = Vec::with_capacity(40); input.extend_from_slice(best_hash.as_slice()); input.extend_from_slice(&block_number.to_le_bytes()); env.evm_env.block_env.prevrandao = Some(keccak256(&input)); if self.prune_state_history_config.is_state_history_supported() { let db = self.db.read().await.current_state(); // store current state before executing all transactions self.states.write().insert(best_hash, db); } let (block_info, included, invalid, block_hash) = { let mut db = self.db.write().await; // 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.evm_env.block_env.timestamp = U256::from(self.time.next_timestamp()); let spec_id = *env.evm_env.spec_id(); let is_shanghai = spec_id >= SpecId::SHANGHAI; let is_cancun = spec_id >= SpecId::CANCUN; let is_prague = spec_id >= SpecId::PRAGUE; let gas_limit = env.evm_env.block_env.gas_limit; let difficulty = env.evm_env.block_env.difficulty; let mix_hash = env.evm_env.block_env.prevrandao; let beneficiary = env.evm_env.block_env.beneficiary; let timestamp = env.evm_env.block_env.timestamp; let base_fee = if spec_id >= SpecId::LONDON { Some(env.evm_env.block_env.basefee) } else { None }; let excess_blob_gas = if is_cancun { env.evm_env.block_env.blob_excess_gas() } else { None }; // 1. Build inspector (per-block, NOT per-tx) let mut inspector = AnvilInspector::default().with_tracing(); if self.enable_steps_tracing { inspector = inspector.with_steps_tracing(); } if self.print_logs { inspector = inspector.with_log_collector(); } if self.print_traces { inspector = inspector.with_trace_printer(); } // 2. Create EVM let env_struct = Env::new(env.evm_env.clone(), Default::default(), env.networks); let mut evm = new_eth_evm_with_inspector(&mut **db, &env_struct, inspector); // 3. Inject precompiles (once, before the tx loop) env.networks.inject_precompiles(evm.precompiles_mut()); if let Some(factory) = &self.precompile_factory { evm.precompiles_mut().extend_precompiles(factory.precompiles()); } let cheats = self.cheats().clone(); if cheats.has_recover_overrides() { let cheats_arc = Arc::new(cheats.clone()); let cheat_ecrecover = CheatEcrecover::new(Arc::clone(&cheats_arc)); evm.precompiles_mut().apply_precompile(&EC_RECOVER, move |_| { Some(DynPrecompile::new_stateful( cheat_ecrecover.precompile_id().clone(), move |input| cheat_ecrecover.call(input), )) }); } // 4. Create executor via AnvilBlockExecutorFactory let exec_ctx = AnvilExecutionCtx { parent_hash: best_hash, is_prague, is_cancun }; let mut executor = AnvilBlockExecutorFactory::create_executor(evm, exec_ctx); executor.apply_pre_execution_changes().expect("pre-execution changes failed"); // 5. Per-tx loop let mut included: Vec>> = Vec::new(); let mut invalid: Vec>> = Vec::new(); let mut transaction_infos: Vec = Vec::new(); let mut transactions = Vec::new(); let mut bloom = Bloom::default(); let blob_params = self.blob_params(); let networks = env.networks; let mut cumulative_blob_gas_used = if is_cancun { Some(0u64) } else { None }; for pool_tx in pool_transactions { let pending = &pool_tx.pending_transaction; let sender = *pending.sender(); let account = match executor .evm_mut() .db_mut() .basic(sender) .map(|a| a.unwrap_or_default()) { Ok(acc) => acc, Err(err) => { trace!(target: "backend", ?err, "db error for tx {:?}, skipping", pool_tx.hash()); continue; } }; // Build the per-tx env let tx_env = build_tx_env_for_pending(pending, &cheats, networks, &env.evm_env); let full_env = Env::new(env.evm_env.clone(), tx_env.clone(), networks); // Gas limit checks (same logic as TransactionExecutor::next) let cumulative_gas = executor.receipts().last().map(|r| r.cumulative_gas_used()).unwrap_or(0); let max_block_gas = cumulative_gas.saturating_add(pending.transaction.gas_limit()); if !env.evm_env.cfg_env.disable_block_gas_limit && max_block_gas > gas_limit { trace!(target: "backend", tx_gas_limit = %pending.transaction.gas_limit(), ?pool_tx, "block gas limit exhausting, skipping transaction"); continue; } // Osaka EIP-7825 tx gas limit cap check if env.evm_env.cfg_env.tx_gas_limit_cap.is_none() && pending.transaction.gas_limit() > env.evm_env.cfg_env.tx_gas_limit_cap() { trace!(target: "backend", tx_gas_limit = %pending.transaction.gas_limit(), ?pool_tx, "transaction gas limit exhausting, skipping transaction"); continue; } // Blob gas check let tx_blob_gas = pending.transaction.blob_gas_used().unwrap_or(0); let current_blob_gas = cumulative_blob_gas_used.unwrap_or(0); if current_blob_gas.saturating_add(tx_blob_gas) > blob_params.max_blob_gas_per_block() { trace!(target: "backend", blob_gas = %tx_blob_gas, ?pool_tx, "block blob gas limit exhausting, skipping transaction"); continue; } // Validate if let Err(err) = self.validate_pool_transaction_for(pending, &account, &full_env) { warn!(target: "backend", "Skipping invalid tx execution [{:?}] {}", pool_tx.hash(), err); invalid.push(pool_tx.clone()); continue; } let nonce = account.nonce; // Execute via block executor let recovered = Recovered::new_unchecked(pending.transaction.as_ref().clone(), sender); trace!(target: "backend", "[{:?}] executing", pool_tx.hash()); match executor.execute_transaction_without_commit((tx_env, recovered)) { Ok(result) => { let exec_result = result.inner.result.result.clone(); let gas_used = result.inner.result.result.gas_used(); executor.commit_transaction(result).expect("commit failed"); // Drain per-tx traces from inspector let insp = executor.evm_mut().inspector_mut(); // Print before draining so the tracer is still populated. if self.print_traces { insp.print_traces(self.call_trace_decoder.clone()); } insp.print_logs(); let traces = insp .tracer .take() .map(|t| t.into_traces().into_nodes()) .unwrap_or_default(); // Reinstall tracer for next tx if self.enable_steps_tracing { insp.tracer = Some(TracingInspector::new( TracingInspectorConfig::all().with_state_diffs(), )); } else { insp.tracer = Some(TracingInspector::new( TracingInspectorConfig::all().set_steps(false), )); } // Reset log collector for next tx if self.print_logs { insp.log_collector = Some(foundry_evm::inspectors::LogCollector::Capture { logs: Vec::new(), }); } // Track blob gas if is_cancun { cumulative_blob_gas_used = Some( cumulative_blob_gas_used .unwrap_or(0) .saturating_add(tx_blob_gas), ); } let (exit_reason, out, logs) = match exec_result { ExecutionResult::Success { reason, gas_used: _, logs, output, .. } => (reason.into(), Some(output), logs), ExecutionResult::Revert { gas_used: _, output } => ( InstructionResult::Revert, Some(Output::Call(output)), Vec::new(), ), ExecutionResult::Halt { reason, gas_used: _ } => { (op_haltreason_to_instruction_result(reason), None, Vec::new()) } }; if exit_reason == InstructionResult::OutOfGas { warn!(target: "backend", "[{:?}] executed with out of gas", pool_tx.hash()); } trace!(target: "backend", ?exit_reason, ?gas_used, "[{:?}] executed with out={:?}", pool_tx.hash(), out); trace!(target: "backend::executor", "transacted [{:?}], result: {:?} gas {}", pool_tx.hash(), exit_reason, gas_used); // Build bloom from logs for log in &logs { bloom.accrue(BloomInput::Raw(&log.address[..])); for topic in log.topics() { bloom.accrue(BloomInput::Raw(&topic[..])); } } // Contract address for creation txs let contract_address = if pending.transaction.to().is_none() { let addr = sender.create(nonce); trace!(target: "backend", "Contract creation tx: computed address {:?}", addr); Some(addr) } else { None }; let transaction_index = transaction_infos.len() as u64; let info = TransactionInfo { transaction_hash: pool_tx.hash(), transaction_index, from: sender, to: pending.transaction.to(), contract_address, traces, exit: exit_reason, out: out.map(Output::into_data), nonce, gas_used, }; included.push(pool_tx.clone()); transaction_infos.push(info); transactions.push(pending.transaction.clone()); } Err(err) => { trace!(target: "backend", ?err, "tx execution error, skipping {:?}", pool_tx.hash()); } } } // 6. Finish — drop EVM BEFORE accessing db again let (evm, block_result) = executor.finish().expect("executor finish failed"); drop(evm); let state_root = db.maybe_state_root().unwrap_or_default(); // 7. Build block header let receipts_root = calculate_receipt_root(&block_result.receipts); let cumulative_gas_used = block_result.gas_used; let header = Header { parent_hash: best_hash, ommers_hash: Default::default(), beneficiary, state_root, transactions_root: Default::default(), receipts_root, logs_bloom: bloom, difficulty, number: block_number, gas_limit, gas_used: cumulative_gas_used, timestamp: timestamp.saturating_to(), extra_data: Default::default(), mix_hash: mix_hash.unwrap_or_default(), nonce: Default::default(), base_fee_per_gas: base_fee, parent_beacon_block_root: is_cancun.then_some(Default::default()), blob_gas_used: cumulative_blob_gas_used, excess_blob_gas, withdrawals_root: is_shanghai.then_some(EMPTY_WITHDRAWALS), requests_hash: is_prague.then_some(EMPTY_REQUESTS_HASH), }; let block = create_block(header, transactions); let block_info = TypedBlockInfo { block, transactions: transaction_infos, receipts: block_result.receipts, }; // update the new blockhash in the db itself let block_hash = block_info.block.header.hash_slow(); db.insert_block_hash(U256::from(block_info.block.header.number()), block_hash); (block_info, included, invalid, block_hash) }; // create the new block with the current timestamp let BlockInfo { block, transactions, receipts } = block_info; let header = block.header.clone(); trace!( target: "backend", "Mined block {} with {} tx {:?}", block_number, transactions.len(), transactions.iter().map(|tx| tx.transaction_hash).collect::>() ); let mut storage = self.blockchain.storage.write(); // update block metadata storage.best_number = block_number; storage.best_hash = block_hash; // Difficulty is removed and not used after Paris (aka TheMerge). Value is replaced with // prevrandao. https://github.com/bluealloy/revm/blob/1839b3fce8eaeebb85025576f2519b80615aca1e/crates/interpreter/src/instructions/host_env.rs#L27 if !self.is_eip3675() { storage.total_difficulty = storage.total_difficulty.saturating_add(header.difficulty); } storage.blocks.insert(block_hash, block); storage.hashes.insert(block_number, block_hash); node_info!(""); // insert all transactions for (info, receipt) in transactions.into_iter().zip(receipts) { // log some tx info node_info!(" Transaction: {:?}", info.transaction_hash); if let Some(contract) = &info.contract_address { node_info!(" Contract created: {contract}"); } node_info!(" Gas used: {}", receipt.cumulative_gas_used()); if !info.exit.is_ok() { let r = RevertDecoder::new().decode( info.out.as_ref().map(|b| &b[..]).unwrap_or_default(), Some(info.exit), ); node_info!(" Error: reverted with: {r}"); } node_info!(""); let mined_tx = MinedTransaction { info, receipt, block_hash, block_number }; storage.transactions.insert(mined_tx.info.transaction_hash, mined_tx); } // remove old transactions that exceed the transaction block keeper if let Some(transaction_block_keeper) = self.transaction_block_keeper && storage.blocks.len() > transaction_block_keeper { let to_clear = block_number .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.evm_env.block_env.difficulty = U256::from(0); // update env with new values *self.env.write() = env; let timestamp = utc_from_secs(header.timestamp); node_info!(" Block Number: {}", block_number); node_info!(" Block Hash: {:?}", block_hash); if timestamp.year() > 9999 { // rf2822 panics with more than 4 digits node_info!(" Block Time: {:?}\n", timestamp.to_rfc3339()); } else { node_info!(" Block Time: {:?}\n", timestamp.to_rfc2822()); } let outcome = MinedBlockOutcome { block_number, included, invalid }; (outcome, header, block_hash) }; 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, get_blob_base_fee_update_fraction_by_spec_id(*self.env.read().evm_env.spec_id()), )); // notify all listeners self.notify_on_new_block(header, block_hash); outcome } /// Reorg the chain to a common height and execute blocks to build new chain. /// /// The state of the chain is rewound using `rewind` to the common block, including the db, /// storage, and env. /// /// Finally, `do_mine_block` is called to create the new chain. pub async fn reorg( &self, depth: u64, tx_pairs: HashMap>>>, common_block: Block, ) -> Result<(), BlockchainError> { self.rollback(common_block).await?; // Create the new reorged chain, filling the blocks with transactions if supplied for i in 0..depth { let to_be_mined = tx_pairs.get(&i).cloned().unwrap_or_else(Vec::new); let outcome = self.do_mine_block(to_be_mined).await; node_info!( " Mined reorg block number {}. With {} valid txs and with invalid {} txs", outcome.block_number, outcome.included.len(), outcome.invalid.len() ); } Ok(()) } /// Creates the pending block /// /// This will execute all transaction in the order they come but will not mine the block pub async fn pending_block( &self, pool_transactions: Vec>>, ) -> BlockInfo { self.with_pending_block(pool_transactions, |_, block| block).await } /// Creates the pending block /// /// This will execute all transaction in the order they come but will not mine the block pub async fn with_pending_block( &self, pool_transactions: Vec>>, f: F, ) -> T where F: FnOnce(Box, BlockInfo) -> T, { let db = self.db.read().await; let env = self.next_env(); let mut cache_db = AnvilCacheDB::new(&*db); let parent_hash = self.blockchain.storage.read().best_hash; let spec_id = *env.evm_env.spec_id(); let is_shanghai = spec_id >= SpecId::SHANGHAI; let is_cancun = spec_id >= SpecId::CANCUN; let is_prague = spec_id >= SpecId::PRAGUE; let gas_limit = env.evm_env.block_env.gas_limit; let difficulty = env.evm_env.block_env.difficulty; let mix_hash = env.evm_env.block_env.prevrandao; let beneficiary = env.evm_env.block_env.beneficiary; let timestamp = env.evm_env.block_env.timestamp; let base_fee = if spec_id >= SpecId::LONDON { Some(env.evm_env.block_env.basefee) } else { None }; let excess_blob_gas = if is_cancun { env.evm_env.block_env.blob_excess_gas() } else { None }; let mut inspector = AnvilInspector::default().with_tracing(); if self.enable_steps_tracing { inspector = inspector.with_steps_tracing(); } if self.print_logs { inspector = inspector.with_log_collector(); } if self.print_traces { inspector = inspector.with_trace_printer(); } let env_struct = Env::new(env.evm_env.clone(), Default::default(), env.networks); let mut evm = new_eth_evm_with_inspector(&mut cache_db, &env_struct, inspector); env.networks.inject_precompiles(evm.precompiles_mut()); if let Some(factory) = &self.precompile_factory { evm.precompiles_mut().extend_precompiles(factory.precompiles()); } let cheats = self.cheats().clone(); if cheats.has_recover_overrides() { let cheats_arc = Arc::new(cheats.clone()); let cheat_ecrecover = CheatEcrecover::new(Arc::clone(&cheats_arc)); evm.precompiles_mut().apply_precompile(&EC_RECOVER, move |_| { Some(DynPrecompile::new_stateful( cheat_ecrecover.precompile_id().clone(), move |input| cheat_ecrecover.call(input), )) }); } let exec_ctx = AnvilExecutionCtx { parent_hash, is_prague, is_cancun }; let mut executor = AnvilBlockExecutorFactory::create_executor(evm, exec_ctx); executor.apply_pre_execution_changes().expect("pre-execution changes failed"); let mut transaction_infos: Vec = Vec::new(); let mut transactions = Vec::new(); let mut bloom = Bloom::default(); let blob_params = self.blob_params(); let networks = env.networks; let mut cumulative_blob_gas_used = if is_cancun { Some(0u64) } else { None }; for pool_tx in pool_transactions { let pending = &pool_tx.pending_transaction; let sender = *pending.sender(); let account = match executor .evm_mut() .db_mut() .basic(sender) .map(|a| a.unwrap_or_default()) { Ok(acc) => acc, Err(err) => { trace!(target: "backend", ?err, "db error for tx {:?}, skipping", pool_tx.hash()); continue; } }; let tx_env = build_tx_env_for_pending(pending, &cheats, networks, &env.evm_env); let full_env = Env::new(env.evm_env.clone(), tx_env.clone(), networks); let cumulative_gas = executor.receipts().last().map(|r| r.cumulative_gas_used()).unwrap_or(0); let max_block_gas = cumulative_gas.saturating_add(pending.transaction.gas_limit()); if !env.evm_env.cfg_env.disable_block_gas_limit && max_block_gas > gas_limit { trace!(target: "backend", tx_gas_limit = %pending.transaction.gas_limit(), ?pool_tx, "block gas limit exhausting, skipping transaction"); continue; } if env.evm_env.cfg_env.tx_gas_limit_cap.is_none() && pending.transaction.gas_limit() > env.evm_env.cfg_env.tx_gas_limit_cap() { trace!(target: "backend", tx_gas_limit = %pending.transaction.gas_limit(), ?pool_tx, "transaction gas limit exhausting, skipping transaction"); continue; } let tx_blob_gas = pending.transaction.blob_gas_used().unwrap_or(0); let current_blob_gas = cumulative_blob_gas_used.unwrap_or(0); if current_blob_gas.saturating_add(tx_blob_gas) > blob_params.max_blob_gas_per_block() { trace!(target: "backend", blob_gas = %tx_blob_gas, ?pool_tx, "block blob gas limit exhausting, skipping transaction"); continue; } if let Err(err) = self.validate_pool_transaction_for(pending, &account, &full_env) { warn!(target: "backend", "Skipping invalid tx execution [{:?}] {}", pool_tx.hash(), err); continue; } let nonce = account.nonce; let recovered = Recovered::new_unchecked(pending.transaction.as_ref().clone(), sender); trace!(target: "backend", "[{:?}] executing", pool_tx.hash()); match executor.execute_transaction_without_commit((tx_env, recovered)) { Ok(result) => { let exec_result = result.inner.result.result.clone(); let gas_used = result.inner.result.result.gas_used(); executor.commit_transaction(result).expect("commit failed"); let insp = executor.evm_mut().inspector_mut(); if self.print_traces { insp.print_traces(self.call_trace_decoder.clone()); } insp.print_logs(); let traces = insp .tracer .take() .map(|t| t.into_traces().into_nodes()) .unwrap_or_default(); if self.enable_steps_tracing { insp.tracer = Some(TracingInspector::new( TracingInspectorConfig::all().with_state_diffs(), )); } else { insp.tracer = Some(TracingInspector::new( TracingInspectorConfig::all().set_steps(false), )); } if self.print_logs { insp.log_collector = Some(foundry_evm::inspectors::LogCollector::Capture { logs: Vec::new(), }); } if is_cancun { cumulative_blob_gas_used = Some(cumulative_blob_gas_used.unwrap_or(0).saturating_add(tx_blob_gas)); } let (exit_reason, out, logs) = match exec_result { ExecutionResult::Success { reason, gas_used: _, logs, output, .. } => { (reason.into(), Some(output), logs) } ExecutionResult::Revert { gas_used: _, output } => { (InstructionResult::Revert, Some(Output::Call(output)), Vec::new()) } ExecutionResult::Halt { reason, gas_used: _ } => { (op_haltreason_to_instruction_result(reason), None, Vec::new()) } }; for log in &logs { bloom.accrue(BloomInput::Raw(&log.address[..])); for topic in log.topics() { bloom.accrue(BloomInput::Raw(&topic[..])); } } let contract_address = if pending.transaction.to().is_none() { let addr = sender.create(nonce); Some(addr) } else { None }; let transaction_index = transaction_infos.len() as u64; let info = TransactionInfo { transaction_hash: pool_tx.hash(), transaction_index, from: sender, to: pending.transaction.to(), contract_address, traces, exit: exit_reason, out: out.map(Output::into_data), nonce, gas_used, }; transaction_infos.push(info); transactions.push(pending.transaction.clone()); } Err(err) => { trace!(target: "backend", ?err, "tx execution error, skipping {:?}", pool_tx.hash()); } } } let (evm, block_result) = executor.finish().expect("executor finish failed"); drop(evm); // Extract inner CacheDB (which implements MaybeFullDatabase) let cache_db = cache_db.0; let state_root = cache_db.maybe_state_root().unwrap_or_default(); let receipts_root = calculate_receipt_root(&block_result.receipts); let cumulative_gas_used = block_result.gas_used; let header = Header { parent_hash, ommers_hash: Default::default(), beneficiary, state_root, transactions_root: Default::default(), receipts_root, logs_bloom: bloom, difficulty, number: env.evm_env.block_env.number.saturating_to(), gas_limit, gas_used: cumulative_gas_used, timestamp: timestamp.saturating_to(), extra_data: Default::default(), mix_hash: mix_hash.unwrap_or_default(), nonce: Default::default(), base_fee_per_gas: base_fee, parent_beacon_block_root: is_cancun.then_some(Default::default()), blob_gas_used: cumulative_blob_gas_used, excess_blob_gas, withdrawals_root: is_shanghai.then_some(EMPTY_WITHDRAWALS), requests_hash: is_prague.then_some(EMPTY_REQUESTS_HASH), }; let block = create_block(header, transactions); let block_info = TypedBlockInfo { block, transactions: transaction_infos, receipts: block_result.receipts, }; f(Box::new(cache_db), block_info) } /// Executes the [TransactionRequest] without writing to the DB /// /// # Errors /// /// Returns an error if the `block_number` is greater than the current height pub async fn call( &self, request: WithOtherFields, fee_details: FeeDetails, block_request: Option>, overrides: EvmOverrides, ) -> Result<(InstructionResult, Option, u128, State), BlockchainError> { 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 { apply_state_overrides(state_overrides.into_iter().collect(), &mut cache_db)?; } if let Some(block_overrides) = overrides.block { cache_db.apply_block_overrides(*block_overrides, &mut block); } self.call_with_state(&cache_db, request, fee_details, block) }?; trace!(target: "backend", "call return {:?} out: {:?} gas {} on block {}", exit, out, gas, block_number); Ok((exit, out, gas, state)) }).await? } pub async fn call_with_tracing( &self, request: WithOtherFields, fee_details: FeeDetails, block_request: Option>, opts: GethDebugTracingCallOptions, ) -> Result { let GethDebugTracingCallOptions { tracing_options, block_overrides, state_overrides, .. } = opts; let GethDebugTracingOptions { config, tracer, tracer_config, .. } = tracing_options; self.with_database_at(block_request, |state, mut block| { let block_number = block.number; let mut cache_db = CacheDB::new(state); if let Some(state_overrides) = state_overrides { apply_state_overrides(state_overrides, &mut cache_db)?; } if let Some(block_overrides) = block_overrides { cache_db.apply_block_overrides(block_overrides, &mut block); } if let Some(tracer) = tracer { return match tracer { GethDebugTracerType::BuiltInTracer(tracer) => match tracer { GethDebugBuiltInTracerType::CallTracer => { let call_config = tracer_config .into_call_config() .map_err(|e| RpcError::invalid_params(e.to_string()))?; let mut inspector = self.build_inspector().with_tracing_config( TracingInspectorConfig::from_geth_call_config(&call_config), ); let env = self.build_call_env(request, fee_details, block); let mut evm = self.new_eth_evm_with_inspector_ref( &cache_db, &env, &mut inspector, ); let ResultAndState { result, state: _ } = evm.transact(env.tx)?; drop(evm); inspector.print_logs(); if self.print_traces { inspector.print_traces(self.call_trace_decoder.clone()); } let tracing_inspector = inspector.tracer.expect("tracer disappeared"); Ok(tracing_inspector .into_geth_builder() .geth_call_traces(call_config, result.gas_used()) .into()) } GethDebugBuiltInTracerType::PreStateTracer => { let pre_state_config = tracer_config .into_pre_state_config() .map_err(|e| RpcError::invalid_params(e.to_string()))?; let mut inspector = TracingInspector::new( TracingInspectorConfig::from_geth_prestate_config( &pre_state_config, ), ); let env = self.build_call_env(request, fee_details, block); let mut evm = self.new_eth_evm_with_inspector_ref( &cache_db, &env, &mut inspector, ); let result = evm.transact(env.tx)?; drop(evm); Ok(inspector .into_geth_builder() .geth_prestate_traces(&result, &pre_state_config, cache_db)? .into()) } GethDebugBuiltInTracerType::NoopTracer => Ok(NoopFrame::default().into()), GethDebugBuiltInTracerType::FourByteTracer | GethDebugBuiltInTracerType::MuxTracer | GethDebugBuiltInTracerType::FlatCallTracer | GethDebugBuiltInTracerType::Erc7562Tracer => { Err(RpcError::invalid_params("unsupported tracer type").into()) } }, #[cfg(not(feature = "js-tracer"))] GethDebugTracerType::JsTracer(_) => { Err(RpcError::invalid_params("unsupported tracer type").into()) } #[cfg(feature = "js-tracer")] GethDebugTracerType::JsTracer(code) => { use alloy_evm::IntoTxEnv; let config = tracer_config.into_json(); let mut inspector = revm_inspectors::tracing::js::JsInspector::new(code, config) .map_err(|err| BlockchainError::Message(err.to_string()))?; let env = self.build_call_env(request, fee_details, block.clone()); let mut evm = self.new_eth_evm_with_inspector_ref(&cache_db, &env, &mut inspector); let result = evm.transact(env.tx.clone())?; let res = evm .inspector_mut() .json_result(result, &env.tx.into_tx_env(), &block, &cache_db) .map_err(|err| BlockchainError::Message(err.to_string()))?; Ok(GethTrace::JS(res)) } }; } // defaults to StructLog tracer used since no tracer is specified let mut inspector = self .build_inspector() .with_tracing_config(TracingInspectorConfig::from_geth_config(&config)); let env = self.build_call_env(request, fee_details, block); let mut evm = self.new_eth_evm_with_inspector_ref(&cache_db, &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)) } ExecutionResult::Revert { gas_used, output } => { (InstructionResult::Revert, gas_used, Some(Output::Call(output))) } ExecutionResult::Halt { reason, gas_used } => { (op_haltreason_to_instruction_result(reason), gas_used, None) } }; drop(evm); let tracing_inspector = inspector.tracer.expect("tracer disappeared"); let return_value = out.as_ref().map(|o| o.data()).cloned().unwrap_or_default(); trace!(target: "backend", ?exit_reason, ?out, %gas_used, %block_number, "trace call"); let res = tracing_inspector .into_geth_builder() .geth_traces(gas_used, return_value, config) .into(); Ok(res) }) .await? } /// Helper function to execute a closure with the database at a specific block pub async fn with_database_at( &self, block_request: Option>, f: F, ) -> Result where F: FnOnce(Box, BlockEnv) -> T, { let block_number = match block_request { Some(BlockRequest::Pending(pool_transactions)) => { let result = self .with_pending_block(pool_transactions, |state, block| { let block = block.block; f(state, block_env_from_header(&block.header)) }) .await; return Ok(result); } Some(BlockRequest::Number(bn)) => Some(BlockNumber::Number(bn)), None => None, }; let block_number = self.convert_block_number(block_number); let current_number = self.best_number(); // Reject requests for future blocks that don't exist yet if block_number > current_number { return Err(BlockchainError::BlockOutOfRange(current_number, block_number)); } if block_number < current_number { if let Some((block_hash, block)) = self .block_by_number(BlockNumber::Number(block_number)) .await? .map(|block| (block.header.hash, block)) { let read_guard = self.states.upgradable_read(); if let Some(state_db) = read_guard.get_state(&block_hash) { return Ok(f(Box::new(state_db), block_env_from_header(&block.header))); } else { let mut write_guard = RwLockUpgradableReadGuard::upgrade(read_guard); if let Some(state) = write_guard.get_on_disk_state(&block_hash) { return Ok(f(Box::new(state), block_env_from_header(&block.header))); } } } warn!(target: "backend", "Not historic state found for block={}", block_number); return Err(BlockchainError::BlockOutOfRange(current_number, block_number)); } let db = self.db.read().await; let block = self.env.read().evm_env.block_env.clone(); Ok(f(Box::new(&**db), block)) } pub async fn storage_at( &self, address: Address, index: U256, block_request: Option>, ) -> Result { self.with_database_at(block_request, |db, _| { trace!(target: "backend", "get storage for {:?} at {:?}", address, index); let val = db.storage_ref(address, index)?; Ok(val.into()) }) .await? } /// Returns the code of the address /// /// If the code is not present and fork mode is enabled then this will try to fetch it from the /// forked client pub async fn get_code( &self, address: Address, block_request: Option>, ) -> Result { self.with_database_at(block_request, |db, _| self.get_code_with_state(&db, address)).await? } /// Returns the balance of the address /// /// If the requested number predates the fork then this will fetch it from the endpoint pub async fn get_balance( &self, address: Address, block_request: Option>, ) -> Result { self.with_database_at(block_request, |db, _| self.get_balance_with_state(db, address)) .await? } pub async fn get_account_at_block( &self, address: Address, block_request: Option>, ) -> Result { self.with_database_at(block_request, |block_db, _| { let db = block_db.maybe_as_full_db().ok_or(BlockchainError::DataUnavailable)?; let account = db.get(&address).cloned().unwrap_or_default(); let storage_root = storage_root(&account.storage); let code_hash = account.info.code_hash; let balance = account.info.balance; let nonce = account.info.nonce; Ok(TrieAccount { balance, nonce, code_hash, storage_root }) }) .await? } /// Returns the nonce of the address /// /// If the requested number predates the fork then this will fetch it from the endpoint pub async fn get_nonce( &self, address: Address, block_request: BlockRequest, ) -> Result { if let BlockRequest::Pending(pool_transactions) = &block_request && let Some(value) = get_pool_transactions_nonce(pool_transactions, address) { return Ok(value); } let final_block_request = match block_request { BlockRequest::Pending(_) => BlockRequest::Number(self.best_number()), BlockRequest::Number(bn) => BlockRequest::Number(bn), }; self.with_database_at(Some(final_block_request), |db, _| { trace!(target: "backend", "get nonce for {:?}", address); Ok(db.basic_ref(address)?.unwrap_or_default().nonce) }) .await? } fn replay_tx_with_inspector( &self, hash: B256, mut inspector: I, f: F, ) -> Result where for<'a> I: Inspector>>>> + Inspector>>>> + 'a, for<'a> F: FnOnce(ResultAndState, CacheDB>, I, TxEnv, Env) -> T, { let block = { let storage = self.blockchain.storage.read(); let MinedTransaction { block_hash, .. } = storage .transactions .get(&hash) .cloned() .ok_or(BlockchainError::TransactionNotFound)?; storage.blocks.get(&block_hash).cloned().ok_or(BlockchainError::BlockNotFound)? }; let index = block .body .transactions .iter() .position(|tx| tx.hash() == hash) .expect("transaction not found in block"); let pool_txs: Vec>> = block.body.transactions [..index] .iter() .map(|tx| { let pending_tx = PendingTransaction::from_maybe_impersonated(tx.clone()).expect("is valid"); Arc::new(PoolTransaction { pending_transaction: pending_tx, requires: vec![], provides: vec![], priority: crate::eth::pool::transactions::TransactionPriority(0), }) }) .collect(); let trace = |parent_state: &StateDb| -> Result { let mut cache_db = AnvilCacheDB::new(Box::new(parent_state)); // configure the blockenv for the block of the transaction let mut env = self.env.read().clone(); env.evm_env.block_env = block_env_from_header(&block.header); let spec_id = *env.evm_env.spec_id(); let is_cancun = spec_id >= SpecId::CANCUN; let is_prague = spec_id >= SpecId::PRAGUE; let gas_limit = env.evm_env.block_env.gas_limit; let mut inspector_replay = AnvilInspector::default().with_tracing(); if self.enable_steps_tracing { inspector_replay = inspector_replay.with_steps_tracing(); } if self.print_logs { inspector_replay = inspector_replay.with_log_collector(); } if self.print_traces { inspector_replay = inspector_replay.with_trace_printer(); } let env_struct = Env::new(env.evm_env.clone(), Default::default(), env.networks); let mut evm_replay = new_eth_evm_with_inspector(&mut cache_db, &env_struct, inspector_replay); env.networks.inject_precompiles(evm_replay.precompiles_mut()); if let Some(factory) = &self.precompile_factory { evm_replay.precompiles_mut().extend_precompiles(factory.precompiles()); } let cheats = self.cheats().clone(); if cheats.has_recover_overrides() { let cheats_arc = Arc::new(cheats.clone()); let cheat_ecrecover = CheatEcrecover::new(Arc::clone(&cheats_arc)); evm_replay.precompiles_mut().apply_precompile(&EC_RECOVER, move |_| { Some(DynPrecompile::new_stateful( cheat_ecrecover.precompile_id().clone(), move |input| cheat_ecrecover.call(input), )) }); } let exec_ctx = AnvilExecutionCtx { parent_hash: block.header.parent_hash, is_prague, is_cancun }; let mut replay_executor = AnvilBlockExecutorFactory::create_executor(evm_replay, exec_ctx); replay_executor.apply_pre_execution_changes().expect("pre-execution changes failed"); let blob_params = self.blob_params(); let networks = env.networks; let mut cumulative_blob_gas_used = if is_cancun { Some(0u64) } else { None }; for pool_tx in pool_txs { let pending = &pool_tx.pending_transaction; let sender = *pending.sender(); let account = match replay_executor .evm_mut() .db_mut() .basic(sender) .map(|a| a.unwrap_or_default()) { Ok(acc) => acc, Err(_) => continue, }; let tx_env = build_tx_env_for_pending(pending, &cheats, networks, &env.evm_env); let full_env = Env::new(env.evm_env.clone(), tx_env.clone(), networks); let cumulative_gas = replay_executor.receipts().last().map(|r| r.cumulative_gas_used()).unwrap_or(0); let max_block_gas = cumulative_gas.saturating_add(pending.transaction.gas_limit()); if !env.evm_env.cfg_env.disable_block_gas_limit && max_block_gas > gas_limit { continue; } if env.evm_env.cfg_env.tx_gas_limit_cap.is_none() && pending.transaction.gas_limit() > env.evm_env.cfg_env.tx_gas_limit_cap() { continue; } let tx_blob_gas = pending.transaction.blob_gas_used().unwrap_or(0); let current_blob_gas = cumulative_blob_gas_used.unwrap_or(0); if current_blob_gas.saturating_add(tx_blob_gas) > blob_params.max_blob_gas_per_block() { continue; } if self.validate_pool_transaction_for(pending, &account, &full_env).is_err() { continue; } let recovered = Recovered::new_unchecked(pending.transaction.as_ref().clone(), sender); if let Ok(result) = replay_executor.execute_transaction_without_commit((tx_env, recovered)) { replay_executor.commit_transaction(result).expect("commit failed"); let insp = replay_executor.evm_mut().inspector_mut(); insp.print_logs(); if self.print_traces { insp.print_traces(self.call_trace_decoder.clone()); } insp.tracer.take(); if self.enable_steps_tracing { insp.tracer = Some(TracingInspector::new( TracingInspectorConfig::all().with_state_diffs(), )); } else { insp.tracer = Some(TracingInspector::new( TracingInspectorConfig::all().set_steps(false), )); } if self.print_logs { insp.log_collector = Some(foundry_evm::inspectors::LogCollector::Capture { logs: Vec::new(), }); } if is_cancun { cumulative_blob_gas_used = Some(cumulative_blob_gas_used.unwrap_or(0).saturating_add(tx_blob_gas)); } } } let (evm_done, _) = replay_executor.finish().expect("executor finish failed"); drop(evm_done); // Extract inner CacheDB to match the expected types for the target tx execution let cache_db = cache_db.0; let target_tx = block.body.transactions[index].clone(); let target_tx = PendingTransaction::from_maybe_impersonated(target_tx)?; let mut tx_env: OpTransaction = FromRecoveredTx::from_recovered_tx( target_tx.transaction.as_ref(), *target_tx.sender(), ); if env.networks.is_optimism() { tx_env.enveloped_tx = Some(target_tx.transaction.encoded_2718().into()); } let mut evm = self.new_eth_evm_with_inspector_ref(&cache_db, &env, &mut inspector); let result = evm .transact(tx_env.clone()) .map_err(|err| BlockchainError::Message(err.to_string()))?; Ok(f(result, cache_db, inspector, tx_env.base, env)) }; let read_guard = self.states.upgradable_read(); if let Some(state) = read_guard.get_state(&block.header.parent_hash) { trace(state) } else { let mut write_guard = RwLockUpgradableReadGuard::upgrade(read_guard); let state = write_guard .get_on_disk_state(&block.header.parent_hash) .ok_or(BlockchainError::BlockNotFound)?; trace(state) } } /// Traces the transaction with the js tracer #[cfg(feature = "js-tracer")] pub async fn trace_tx_with_js_tracer( &self, hash: B256, code: String, opts: GethDebugTracingOptions, ) -> Result { let GethDebugTracingOptions { tracer_config, .. } = opts; let config = tracer_config.into_json(); let inspector = revm_inspectors::tracing::js::JsInspector::new(code, config) .map_err(|err| BlockchainError::Message(err.to_string()))?; let trace = self.replay_tx_with_inspector( hash, inspector, |result, cache_db, mut inspector, tx_env, env| { inspector .json_result( result, &alloy_evm::IntoTxEnv::into_tx_env(tx_env), &env.evm_env.block_env, &cache_db, ) .map_err(|e| BlockchainError::Message(e.to_string())) }, )??; Ok(GethTrace::JS(trace)) } /// Prove an account's existence or nonexistence in the state trie. /// /// Returns a merkle proof of the account's trie node, `account_key` == keccak(address) pub async fn prove_account_at( &self, address: Address, keys: Vec, block_request: Option>, ) -> Result { let block_number = block_request.as_ref().map(|r| r.block_number()); self.with_database_at(block_request, |block_db, _| { trace!(target: "backend", "get proof for {:?} at {:?}", address, block_number); let db = block_db.maybe_as_full_db().ok_or(BlockchainError::DataUnavailable)?; let account = db.get(&address).cloned().unwrap_or_default(); let mut builder = HashBuilder::default() .with_proof_retainer(ProofRetainer::new(vec![Nibbles::unpack(keccak256(address))])); for (key, account) in trie_accounts(db) { builder.add_leaf(key, &account); } let _ = builder.root(); let proof = builder .take_proof_nodes() .into_nodes_sorted() .into_iter() .map(|(_, v)| v) .collect(); let (storage_hash, storage_proofs) = prove_storage(&account.storage, &keys); let account_proof = AccountProof { address, balance: account.info.balance, nonce: account.info.nonce, code_hash: account.info.code_hash, storage_hash, account_proof: proof, storage_proof: keys .into_iter() .zip(storage_proofs) .map(|(key, proof)| { let storage_key: U256 = key.into(); let value = account.storage.get(&storage_key).copied().unwrap_or_default(); StorageProof { key: JsonStorageKey::Hash(key), value, proof } }) .collect(), }; Ok(account_proof) }) .await? } /// Rollback the chain to a common height. /// /// The state of the chain is rewound using `rewind` to the common block, including the db, /// storage, and env. pub async fn rollback(&self, common_block: Block) -> Result<(), BlockchainError> { let hash = common_block.header.hash_slow(); // Get the database at the common block let common_state = { let return_state_or_throw_err = |db: Option<&StateDb>| -> Result, BlockchainError> { let state_db = db.ok_or(BlockchainError::DataUnavailable)?; let db_full = state_db.maybe_as_full_db().ok_or(BlockchainError::DataUnavailable)?; Ok(db_full.clone()) }; let read_guard = self.states.upgradable_read(); if let Some(db) = read_guard.get_state(&hash) { return_state_or_throw_err(Some(db))? } else { let mut write_guard = RwLockUpgradableReadGuard::upgrade(read_guard); return_state_or_throw_err(write_guard.get_on_disk_state(&hash))? } }; { // Unwind the storage back to the common ancestor first self.blockchain.storage.write().unwind_to(common_block.header.number(), hash); // Set environment back to common block let mut env = self.env.write(); env.evm_env.block_env.number = U256::from(common_block.header.number()); env.evm_env.block_env.timestamp = U256::from(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 = common_block.header.mix_hash(); self.time.reset(env.evm_env.block_env.timestamp.saturating_to()); } { // Collect block hashes before acquiring db lock to avoid holding blockchain storage // lock across await. Only collect the last 256 blocks since that's all BLOCKHASH can // access. let block_hashes: Vec<_> = { let storage = self.blockchain.storage.read(); let min_block = common_block.header.number().saturating_sub(256); storage .hashes .iter() .filter(|(num, _)| **num >= min_block) .map(|(&num, &hash)| (num, hash)) .collect() }; // Acquire db lock once for the entire restore operation to reduce lock churn. let mut db = self.db.write().await; db.clear(); // Insert account info before storage to prevent fork-mode RPC fetches after clear. for (address, acc) in common_state { db.insert_account(address, acc.info); for (key, value) in acc.storage { db.set_storage_at(address, key.into(), value.into())?; } } // Restore block hashes from blockchain storage (now unwound, contains only valid // blocks). for (block_num, hash) in block_hashes { db.insert_block_hash(U256::from(block_num), hash); } } Ok(()) } } impl Backend { /// Get the current state. pub async fn serialized_state( &self, preserve_historical_states: bool, ) -> Result { 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(); let historical_states = if preserve_historical_states { Some(self.states.write().serialized_states()) } else { None }; let state = self.db.read().await.dump_state( at, best_number, blocks, transactions, historical_states, )?; state.ok_or_else(|| { RpcError::invalid_params("Dumping state not supported with the current configuration") .into() }) } /// Write all chain data to serialized bytes buffer pub async fn dump_state( &self, preserve_historical_states: bool, ) -> Result { let state = self.serialized_state(preserve_historical_states).await?; let mut encoder = GzEncoder::new(Vec::new(), Compression::default()); encoder .write_all(&serde_json::to_vec(&state).unwrap_or_default()) .map_err(|_| BlockchainError::DataUnavailable)?; Ok(encoder.finish().unwrap_or_default().into()) } /// Apply [SerializableState] data to the backend storage. pub async fn load_state(&self, state: SerializableState) -> Result { // load the blocks and transactions into the storage self.blockchain.storage.write().load_blocks(state.blocks.clone()); self.blockchain.storage.write().load_transactions(state.transactions.clone()); // reset the block env if let Some(block) = state.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.saturating_to()); if let Some((number, hash)) = fork_num_and_hash { 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 > number { self.blockchain.storage.write().best_number = best_number; let best_hash = self.blockchain.storage.read().hash(best_number.into()).ok_or_else( || { BlockchainError::RpcError(RpcError::internal_error_with(format!( "Best hash not found for best number {best_number}", ))) }, )?; self.blockchain.storage.write().best_hash = best_hash; } 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 = number; self.blockchain.storage.write().best_hash = hash; } } else { self.blockchain.storage.write().best_number = best_number; // Set the current best block hash; let best_hash = self.blockchain.storage.read().hash(best_number.into()).ok_or_else(|| { BlockchainError::RpcError(RpcError::internal_error_with(format!( "Best hash not found for best number {best_number}", ))) })?; self.blockchain.storage.write().best_hash = best_hash; } } 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, get_blob_base_fee_update_fraction( self.env.read().evm_env.cfg_env.chain_id, header.timestamp, ), )); } if !self.db.write().await.load_state(state.clone())? { return Err(RpcError::invalid_params( "Loading state not supported with the current configuration", ) .into()); } if let Some(historical_states) = state.historical_states { self.states.write().load_states(historical_states); } Ok(true) } /// Deserialize and add all chain data to the backend storage pub async fn load_state_bytes(&self, buf: Bytes) -> Result { let orig_buf = &buf.0[..]; let mut decoder = GzDecoder::new(orig_buf); let mut decoded_data = Vec::new(); let state: SerializableState = serde_json::from_slice(if decoder.header().is_some() { decoder .read_to_end(decoded_data.as_mut()) .map_err(|_| BlockchainError::FailedToDecodeStateDump)?; &decoded_data } else { &buf.0 }) .map_err(|_| BlockchainError::FailedToDecodeStateDump)?; self.load_state(state).await } /// Simulates the payload by executing the calls in request. pub async fn simulate( &self, request: SimulatePayload, block_request: Option>, ) -> Result>, BlockchainError> { self.with_database_at(block_request, |state, mut block_env| { let SimulatePayload { block_state_calls, trace_transfers, validation, return_full_transactions, } = request; let mut cache_db = CacheDB::new(state); let mut block_res = Vec::with_capacity(block_state_calls.len()); // execute the blocks for block in block_state_calls { let SimBlock { block_overrides, state_overrides, calls } = block; let mut call_res = Vec::with_capacity(calls.len()); 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 { apply_state_overrides(state_overrides, &mut cache_db)?; } if let Some(block_overrides) = block_overrides { cache_db.apply_block_overrides(block_overrides, &mut block_env); } // execute all calls in that block for (req_idx, request) in calls.into_iter().enumerate() { let fee_details = FeeDetails::new( request.gas_price, request.max_fee_per_gas, request.max_priority_fee_per_gas, request.max_fee_per_blob_gas, )? .or_zero_fees(); let mut env = self.build_call_env( WithOtherFields::new(request.clone()), fee_details, block_env.clone(), ); // Always disable EIP-3607 env.evm_env.cfg_env.disable_eip3607 = true; if !validation { env.evm_env.cfg_env.disable_base_fee = !validation; env.evm_env.block_env.basefee = 0; } let mut inspector = self.build_inspector(); // transact let ResultAndState { result, state } = if trace_transfers { // prepare inspector to capture transfer inside the evm so they are // recorded and included in logs inspector = inspector.with_transfers(); let mut evm= self.new_eth_evm_with_inspector_ref( &cache_db, &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 evm = self.new_eth_evm_with_inspector_ref( &cache_db, &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"); inspector.print_logs(); if self.print_traces { inspector.into_print_traces(self.call_trace_decoder.clone()); } // commit the transaction cache_db.commit(state); gas_used += result.gas_used(); // create the transaction from a request let from = request.from.unwrap_or_default(); let mut request = Into::::into(WithOtherFields::new(request)); request.prep_for_submission(); let typed_tx = request.build_unsigned().map_err(|e| BlockchainError::InvalidTransactionRequest(e.to_string()))?; let tx = build_impersonated(typed_tx); let tx_hash = tx.hash(); let rpc_tx = transaction_build( None, MaybeImpersonatedTransaction::impersonated(tx, from), None, None, Some(block_env.basefee), ); transactions.push(rpc_tx); let return_data = result.output().cloned().unwrap_or_default(); let sim_res = SimCallResult { return_data, gas_used: result.gas_used(), status: result.is_success(), error: result.is_success().not().then(|| { alloy_rpc_types::simulate::SimulateError { code: -3200, message: "execution failed".to_string(), data: None, } }), logs: result.clone() .into_logs() .into_iter() .enumerate() .map(|(idx, log)| Log { inner: log, block_number: Some(block_env.number.saturating_to()), block_timestamp: Some(block_env.timestamp.saturating_to()), transaction_index: Some(req_idx as u64), log_index: Some((idx + log_index) as u64), removed: false, block_hash: None, transaction_hash: Some(tx_hash), }) .collect(), }; logs.extend(sim_res.logs.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: logs_bloom(logs.iter()), transactions_root: calculate_transaction_root(&transactions_envelopes), receipts_root: calculate_receipt_root(&transactions_envelopes), parent_hash: Default::default(), beneficiary: block_env.beneficiary, state_root: Default::default(), difficulty: Default::default(), number: block_env.number.saturating_to(), gas_limit: block_env.gas_limit, gas_used, timestamp: block_env.timestamp.saturating_to(), extra_data: Default::default(), mix_hash: Default::default(), nonce: Default::default(), 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 { hash: header.hash_slow(), inner: header.into(), total_difficulty: None, size: None, }, uncles: vec![], transactions: BlockTransactions::Full(transactions), withdrawals: None, }; if !return_full_transactions { 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 = simulated_block .inner .header .next_block_base_fee(self.fees.base_fee_params()) .unwrap_or_default(); block_res.push(simulated_block); } Ok(block_res) }) .await? } /// returns all receipts for the given transactions fn get_receipts( &self, tx_hashes: impl IntoIterator, ) -> Vec { let storage = self.blockchain.storage.read(); let mut receipts = vec![]; for hash in tx_hashes { if let Some(tx) = storage.transactions.get(&hash) { receipts.push(tx.receipt.clone()); } } receipts } /// Returns the traces for the given transaction pub async fn debug_trace_transaction( &self, hash: B256, opts: GethDebugTracingOptions, ) -> Result { #[cfg(feature = "js-tracer")] if let Some(tracer_type) = opts.tracer.as_ref() && tracer_type.is_js() { return self .trace_tx_with_js_tracer(hash, tracer_type.as_str().to_string(), opts.clone()) .await; } if let Some(trace) = self.mined_geth_trace_transaction(hash, opts.clone()).await { return trace; } if let Some(fork) = self.get_fork() { return Ok(fork.debug_trace_transaction(hash, opts).await?); } Ok(GethTrace::Default(Default::default())) } fn geth_trace( &self, tx: &MinedTransaction, opts: GethDebugTracingOptions, ) -> Result { let GethDebugTracingOptions { config, tracer, tracer_config, .. } = opts; if let Some(tracer) = tracer { match tracer { GethDebugTracerType::BuiltInTracer(tracer) => match tracer { GethDebugBuiltInTracerType::FourByteTracer => { let inspector = FourByteInspector::default(); let res = self.replay_tx_with_inspector( tx.info.transaction_hash, inspector, |_, _, inspector, _, _| FourByteFrame::from(inspector).into(), )?; return Ok(res); } GethDebugBuiltInTracerType::CallTracer => { return match tracer_config.into_call_config() { Ok(call_config) => { let inspector = TracingInspector::new( TracingInspectorConfig::from_geth_call_config(&call_config), ); let frame = self.replay_tx_with_inspector( tx.info.transaction_hash, inspector, |_, _, inspector, _, _| { inspector .geth_builder() .geth_call_traces( call_config, tx.receipt.cumulative_gas_used(), ) .into() }, )?; Ok(frame) } Err(e) => Err(RpcError::invalid_params(e.to_string()).into()), }; } GethDebugBuiltInTracerType::PreStateTracer => { return match tracer_config.into_pre_state_config() { Ok(pre_state_config) => { let inspector = TracingInspector::new( TracingInspectorConfig::from_geth_prestate_config( &pre_state_config, ), ); let frame = self.replay_tx_with_inspector( tx.info.transaction_hash, inspector, |state, db, inspector, _, _| { inspector.geth_builder().geth_prestate_traces( &state, &pre_state_config, db, ) }, )??; Ok(frame.into()) } Err(e) => Err(RpcError::invalid_params(e.to_string()).into()), }; } GethDebugBuiltInTracerType::NoopTracer | GethDebugBuiltInTracerType::MuxTracer | GethDebugBuiltInTracerType::Erc7562Tracer | GethDebugBuiltInTracerType::FlatCallTracer => {} }, GethDebugTracerType::JsTracer(_code) => {} } return Ok(NoopFrame::default().into()); } // default structlog tracer Ok(GethTraceBuilder::new(tx.info.traces.clone()) .geth_traces( tx.receipt.cumulative_gas_used(), tx.info.out.clone().unwrap_or_default(), config, ) .into()) } async fn mined_geth_trace_transaction( &self, hash: B256, opts: GethDebugTracingOptions, ) -> Option> { self.blockchain.storage.read().transactions.get(&hash).map(|tx| self.geth_trace(tx, opts)) } pub async fn transaction_receipt( &self, hash: B256, ) -> Result, BlockchainError> { if let Some(receipt) = self.mined_transaction_receipt(hash) { return Ok(Some(receipt.inner)); } if let Some(fork) = self.get_fork() { let receipt = fork.transaction_receipt(hash).await?; let number = self.convert_block_number( receipt.clone().and_then(|r| r.block_number()).map(BlockNumber::from), ); if fork.predates_fork_inclusive(number) { return Ok(receipt); } } Ok(None) } /// Returns all transaction receipts of the block pub fn mined_block_receipts(&self, id: impl Into) -> Option> { let mut receipts = Vec::new(); let block = self.get_block(id)?; for transaction in block.body.transactions { let receipt = self.mined_transaction_receipt(transaction.hash())?; receipts.push(receipt.inner); } Some(receipts) } /// Returns the transaction receipt for the given hash pub(crate) fn mined_transaction_receipt( &self, hash: B256, ) -> Option> { let MinedTransaction { info, receipt: tx_receipt, block_hash, .. } = self.blockchain.get_transaction_by_hash(&hash)?; let index = info.transaction_index as usize; let block = self.blockchain.get_block_by_hash(&block_hash)?; let transaction = block.body.transactions[index].clone(); // Cancun specific let excess_blob_gas = block.header.excess_blob_gas(); let blob_gas_price = alloy_eips::eip4844::calc_blob_gasprice(excess_blob_gas.unwrap_or_default()); let blob_gas_used = transaction.blob_gas_used(); let effective_gas_price = transaction.effective_gas_price(block.header.base_fee_per_gas()); let receipts = self.get_receipts(block.body.transactions.iter().map(|tx| tx.hash())); let next_log_index = receipts[..index].iter().map(|r| r.logs().len()).sum::(); let tx_receipt = tx_receipt.convert_logs_rpc( BlockNumHash::new(block.header.number(), block_hash), block.header.timestamp(), info.transaction_hash, info.transaction_index, next_log_index, ); let receipt = TransactionReceipt { inner: tx_receipt, transaction_hash: info.transaction_hash, transaction_index: Some(info.transaction_index), block_number: Some(block.header.number()), gas_used: info.gas_used, contract_address: info.contract_address, effective_gas_price, block_hash: Some(block_hash), from: info.from, to: info.to, blob_gas_price: Some(blob_gas_price), blob_gas_used, }; // Include timestamp in receipt to avoid extra block lookups (e.g., in Otterscan API) let inner = FoundryTxReceipt::with_timestamp(receipt, block.header.timestamp()); Some(MinedTransactionReceipt { inner, out: info.out }) } /// Returns the blocks receipts for the given number pub async fn block_receipts( &self, number: BlockId, ) -> Result>, BlockchainError> { if let Some(receipts) = self.mined_block_receipts(number) { return Ok(Some(receipts)); } if let Some(fork) = self.get_fork() { let number = match self.ensure_block_number(Some(number)).await { Err(_) => return Ok(None), Ok(n) => n, }; if fork.predates_fork_inclusive(number) { let receipts = fork.block_receipts(number).await?; return Ok(receipts); } } Ok(None) } pub fn get_blob_by_tx_hash(&self, hash: B256) -> Result>> { // Try to get the mined transaction by hash if let Some(tx) = self.mined_transaction_by_hash(hash) && let Ok(typed_tx) = FoundryTxEnvelope::try_from(tx) && let Some(sidecar) = typed_tx.sidecar() { return Ok(Some(sidecar.sidecar.blobs().to_vec())); } Ok(None) } } /// Get max nonce from transaction pool by address. fn get_pool_transactions_nonce( pool_transactions: &[Arc>], address: Address, ) -> Option { if let Some(highest_nonce) = pool_transactions .iter() .filter(|tx| *tx.pending_transaction.sender() == address) .map(|tx| tx.pending_transaction.nonce()) .max() { let tx_count = highest_nonce.saturating_add(1); return Some(tx_count); } None } #[async_trait::async_trait] impl TransactionValidator for Backend { async fn validate_pool_transaction( &self, tx: &PendingTransaction, ) -> Result<(), BlockchainError> { let address = *tx.sender(); let account = self.get_account(address).await?; let env = self.next_env(); Ok(self.validate_pool_transaction_for(tx, &account, &env)?) } fn validate_pool_transaction_for( &self, pending: &PendingTransaction, account: &AccountInfo, env: &Env, ) -> Result<(), InvalidTransactionError> { let tx = &pending.transaction; if let Some(tx_chain_id) = tx.chain_id() { let chain_id = self.chain_id(); if chain_id.to::() != tx_chain_id { if let FoundryTxEnvelope::Legacy(tx) = tx.as_ref() { // if env.evm_env.cfg_env.spec >= SpecId::SPURIOUS_DRAGON && tx.chain_id().is_none() { warn!(target: "backend", ?chain_id, ?tx_chain_id, "incompatible EIP155-based V"); return Err(InvalidTransactionError::IncompatibleEIP155); } } else { warn!(target: "backend", ?chain_id, ?tx_chain_id, "invalid chain id"); return Err(InvalidTransactionError::InvalidChainId); } } } // Nonce validation let is_deposit_tx = matches!(pending.transaction.as_ref(), FoundryTxEnvelope::Deposit(_)); let nonce = tx.nonce(); if nonce < account.nonce && !is_deposit_tx { warn!(target: "backend", "[{:?}] nonce too low", tx.hash()); return Err(InvalidTransactionError::NonceTooLow); } // EIP-4844 structural validation if env.evm_env.cfg_env.spec >= SpecId::CANCUN && tx.is_eip4844() { // Heavy (blob validation) checks let blob_tx = match tx.as_ref() { FoundryTxEnvelope::Eip4844(tx) => tx.tx(), _ => unreachable!(), }; let blob_count = blob_tx.tx().blob_versioned_hashes.len(); // Ensure there are blob hashes. if blob_count == 0 { return Err(InvalidTransactionError::NoBlobHashes); } // Ensure the tx does not exceed the max blobs per transaction. let max_blobs_per_tx = self.blob_params().max_blobs_per_tx as usize; if blob_count > max_blobs_per_tx { return Err(InvalidTransactionError::TooManyBlobs(blob_count, max_blobs_per_tx)); } // Check for any blob validation errors if not impersonating. if !self.skip_blob_validation(Some(*pending.sender())) && let Err(err) = blob_tx.validate(EnvKzgSettings::default().get()) { return Err(InvalidTransactionError::BlobTransactionValidationError(err)); } } // EIP-3860 initcode size validation, respects --code-size-limit / --disable-code-size-limit if env.evm_env.cfg_env.spec >= SpecId::SHANGHAI && tx.kind() == TxKind::Create { let max_initcode_size = env .evm_env .cfg_env .limit_contract_code_size .map(|limit| limit.saturating_mul(2)) .unwrap_or(revm::primitives::eip3860::MAX_INITCODE_SIZE); if tx.input().len() > max_initcode_size { return Err(InvalidTransactionError::MaxInitCodeSizeExceeded); } } // Balance and fee related checks if !self.disable_pool_balance_checks { // Gas limit validation if tx.gas_limit() < MIN_TRANSACTION_GAS as u64 { warn!(target: "backend", "[{:?}] gas too low", tx.hash()); return Err(InvalidTransactionError::GasTooLow); } // Check tx gas limit against block gas limit, if block gas limit is set. 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"), })); } // Check tx gas limit against tx gas limit cap (Osaka hard fork and later). if env.evm_env.cfg_env.tx_gas_limit_cap.is_none() && tx.gas_limit() > env.evm_env.cfg_env().tx_gas_limit_cap() { warn!(target: "backend", "[{:?}] gas too high", tx.hash()); return Err(InvalidTransactionError::GasTooHigh(ErrDetail { detail: String::from("tx.gas_limit > env.cfg.tx_gas_limit_cap"), })); } // EIP-1559 fee validation (London hard fork and later). if env.evm_env.cfg_env.spec >= SpecId::LONDON { if tx.max_fee_per_gas() < env.evm_env.block_env.basefee.into() && !is_deposit_tx { warn!(target: "backend", "max fee per gas={}, too low, block basefee={}", tx.max_fee_per_gas(), env.evm_env.block_env.basefee); return Err(InvalidTransactionError::FeeCapTooLow); } if let (Some(max_priority_fee_per_gas), max_fee_per_gas) = (tx.as_ref().max_priority_fee_per_gas(), tx.as_ref().max_fee_per_gas()) && max_priority_fee_per_gas > max_fee_per_gas { warn!(target: "backend", "max priority fee per gas={}, too high, max fee per gas={}", max_priority_fee_per_gas, max_fee_per_gas); return Err(InvalidTransactionError::TipAboveFeeCap); } } // EIP-4844 blob fee validation if env.evm_env.cfg_env.spec >= SpecId::CANCUN && tx.is_eip4844() && let Some(max_fee_per_blob_gas) = tx.max_fee_per_blob_gas() && let Some(blob_gas_and_price) = &env.evm_env.block_env.blob_excess_gas_and_price && 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( max_fee_per_blob_gas, blob_gas_and_price.blob_gasprice, )); } let max_cost = (tx.gas_limit() as u128).saturating_mul(tx.max_fee_per_gas()).saturating_add( tx.blob_gas_used() .map(|g| g as u128) .unwrap_or(0) .mul(tx.max_fee_per_blob_gas().unwrap_or(0)), ); let value = tx.value(); match tx.as_ref() { FoundryTxEnvelope::Deposit(deposit_tx) => { // Deposit transactions // https://specs.optimism.io/protocol/deposits.html#execution // 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 + 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.saturating_to()).ok_or_else(|| { warn!(target: "backend", "[{:?}] cost too high", tx.hash()); InvalidTransactionError::InsufficientFunds })?; if account.balance < U256::from(req_funds) { warn!(target: "backend", "[{:?}] insufficient balance={}, required={} account={:?}", tx.hash(), account.balance, req_funds, *pending.sender()); return Err(InvalidTransactionError::InsufficientFunds); } } } } Ok(()) } fn validate_for( &self, tx: &PendingTransaction, account: &AccountInfo, env: &Env, ) -> Result<(), InvalidTransactionError> { self.validate_pool_transaction_for(tx, account, env)?; if tx.nonce() > account.nonce { return Err(InvalidTransactionError::NonceTooHigh); } Ok(()) } } /// Creates a `AnyRpcTransaction` as it's expected for the `eth` RPC api from storage data pub fn transaction_build( tx_hash: Option, eth_transaction: MaybeImpersonatedTransaction, block: Option<&Block>, info: Option, base_fee: Option, ) -> AnyRpcTransaction { if let FoundryTxEnvelope::Deposit(deposit_tx) = eth_transaction.as_ref() { let dep_tx = deposit_tx; 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 { Ok(mut fields) => { // Add zeroed signature fields for backwards compatibility // https://specs.optimism.io/protocol/deposits.html#the-deposited-transaction-type 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("0x0").unwrap()); let inner = UnknownTypedTransaction { ty: AnyTxType(DEPOSIT_TX_TYPE_ID), fields, memo: Default::default(), }; let envelope = AnyTxEnvelope::Unknown(UnknownTxEnvelope { hash: eth_transaction.hash(), inner, }); let tx = Transaction { inner: Recovered::new_unchecked(envelope, deposit_tx.from), block_hash: block .as_ref() .map(|block| B256::from(keccak256(alloy_rlp::encode(&block.header)))), block_number: block.as_ref().map(|block| block.header.number()), transaction_index: info.as_ref().map(|info| info.transaction_index), effective_gas_price: None, block_timestamp: block.as_ref().map(|block| block.header.timestamp()), }; return AnyRpcTransaction::from(WithOtherFields::new(tx)); } Err(_) => { error!(target: "backend", "failed to serialize deposit transaction"); } } } let from = eth_transaction.recover().unwrap_or_default(); let effective_gas_price = eth_transaction.effective_gas_price(base_fee); // if a specific hash was provided we update the transaction's hash // This is important for impersonated transactions since they all use the // `BYPASS_SIGNATURE` which would result in different hashes // Note: for impersonated transactions this only concerns pending transactions because // there's no `info` yet. let hash = tx_hash.unwrap_or_else(|| eth_transaction.hash()); // TODO: this panics for non-standard tx types (e.g. Tempo) that aren't handled above // (pre-existing issue from the original `into_rpc_transaction`). let eth_envelope = FoundryTxEnvelope::from(eth_transaction) .try_into_eth() .expect("deposit transactions are handled above"); let envelope = match eth_envelope { TxEnvelope::Legacy(signed_tx) => { let (t, sig, _) = signed_tx.into_parts(); let new_signed = Signed::new_unchecked(t, sig, hash); AnyTxEnvelope::Ethereum(TxEnvelope::Legacy(new_signed)) } TxEnvelope::Eip1559(signed_tx) => { let (t, sig, _) = signed_tx.into_parts(); let new_signed = Signed::new_unchecked(t, sig, hash); AnyTxEnvelope::Ethereum(TxEnvelope::Eip1559(new_signed)) } TxEnvelope::Eip2930(signed_tx) => { let (t, sig, _) = signed_tx.into_parts(); let new_signed = Signed::new_unchecked(t, sig, hash); AnyTxEnvelope::Ethereum(TxEnvelope::Eip2930(new_signed)) } TxEnvelope::Eip4844(signed_tx) => { let (t, sig, _) = signed_tx.into_parts(); let new_signed = Signed::new_unchecked(t, sig, hash); AnyTxEnvelope::Ethereum(TxEnvelope::Eip4844(new_signed)) } TxEnvelope::Eip7702(signed_tx) => { let (t, sig, _) = signed_tx.into_parts(); let new_signed = Signed::new_unchecked(t, sig, hash); AnyTxEnvelope::Ethereum(TxEnvelope::Eip7702(new_signed)) } }; let tx = Transaction { inner: Recovered::new_unchecked(envelope, from), block_hash: block.as_ref().map(|block| block.header.hash_slow()), block_number: block.as_ref().map(|block| block.header.number()), transaction_index: info.as_ref().map(|info| info.transaction_index), // deprecated effective_gas_price: Some(effective_gas_price), block_timestamp: block.as_ref().map(|block| block.header.timestamp()), }; AnyRpcTransaction::from(WithOtherFields::new(tx)) } /// Prove a storage key's existence or nonexistence in the account's storage trie. /// /// `storage_key` is the hash of the desired storage key, meaning /// this will only work correctly under a secure trie. /// `storage_key` == keccak(key) pub fn prove_storage(storage: &HashMap, keys: &[B256]) -> (B256, Vec>) { let keys: Vec<_> = keys.iter().map(|key| Nibbles::unpack(keccak256(key))).collect(); let mut builder = HashBuilder::default().with_proof_retainer(ProofRetainer::new(keys.clone())); for (key, value) in trie_storage(storage) { builder.add_leaf(key, &value); } let root = builder.root(); let mut proofs = Vec::new(); let all_proof_nodes = builder.take_proof_nodes(); for proof_key in keys { // Iterate over all proof nodes and find the matching ones. // The filtered results are guaranteed to be in order. let matching_proof_nodes = all_proof_nodes.matching_nodes_sorted(&proof_key).into_iter().map(|(_, node)| node); proofs.push(matching_proof_nodes.collect()); } (root, proofs) } pub fn is_arbitrum(chain_id: u64) -> bool { if let Ok(chain) = NamedChain::try_from(chain_id) { return chain.is_arbitrum(); } 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, } } #[cfg(test)] mod tests { use crate::{NodeConfig, spawn}; #[tokio::test] async fn test_deterministic_block_mining() { // Test that mine_block produces deterministic block hashes with same initial conditions let genesis_timestamp = 1743944919u64; // Create two identical backends let config_a = NodeConfig::test().with_genesis_timestamp(genesis_timestamp.into()); let config_b = NodeConfig::test().with_genesis_timestamp(genesis_timestamp.into()); let (api_a, _handle_a) = spawn(config_a).await; let (api_b, _handle_b) = spawn(config_b).await; // Mine empty blocks (no transactions) on both backends let outcome_a_1 = api_a.backend.mine_block(vec![]).await; let outcome_b_1 = api_b.backend.mine_block(vec![]).await; // Both should mine the same block number assert_eq!(outcome_a_1.block_number, outcome_b_1.block_number); // Get the actual blocks to compare hashes let block_a_1 = api_a.block_by_number(outcome_a_1.block_number.into()).await.unwrap().unwrap(); let block_b_1 = api_b.block_by_number(outcome_b_1.block_number.into()).await.unwrap().unwrap(); // The block hashes should be identical assert_eq!( block_a_1.header.hash, block_b_1.header.hash, "Block hashes should be deterministic. Got {} vs {}", block_a_1.header.hash, block_b_1.header.hash ); // Mine another block to ensure it remains deterministic let outcome_a_2 = api_a.backend.mine_block(vec![]).await; let outcome_b_2 = api_b.backend.mine_block(vec![]).await; let block_a_2 = api_a.block_by_number(outcome_a_2.block_number.into()).await.unwrap().unwrap(); let block_b_2 = api_b.block_by_number(outcome_b_2.block_number.into()).await.unwrap().unwrap(); assert_eq!( block_a_2.header.hash, block_b_2.header.hash, "Second block hashes should also be deterministic. Got {} vs {}", block_a_2.header.hash, block_b_2.header.hash ); // Ensure the blocks are different (sanity check) assert_ne!( block_a_1.header.hash, block_a_2.header.hash, "Different blocks should have different hashes" ); } } ================================================ FILE: crates/anvil/src/eth/backend/mem/state.rs ================================================ //! Support for generating the state root for memdb storage use alloy_primitives::{ B256, U256, keccak256, map::{AddressMap, HashMap}, }; use alloy_rlp::Encodable; use alloy_trie::{HashBuilder, Nibbles}; use revm::{database::DbAccount, state::AccountInfo}; pub fn build_root(values: impl IntoIterator)>) -> B256 { let mut builder = HashBuilder::default(); for (key, value) in values { builder.add_leaf(key, value.as_ref()); } builder.root() } /// Builds state root from the given accounts pub fn state_root(accounts: &AddressMap) -> B256 { build_root(trie_accounts(accounts)) } /// Builds storage root from the given storage pub fn storage_root(storage: &HashMap) -> B256 { build_root(trie_storage(storage)) } /// Builds iterator over stored key-value pairs ready for storage trie root calculation. pub fn trie_storage(storage: &HashMap) -> Vec<(Nibbles, Vec)> { let mut storage = storage .iter() .map(|(key, value)| { let data = alloy_rlp::encode(value); (Nibbles::unpack(keccak256(key.to_be_bytes::<32>())), data) }) .collect::>(); storage.sort_by_key(|(key, _)| *key); storage } /// Builds iterator over stored key-value pairs ready for account trie root calculation. pub fn trie_accounts(accounts: &AddressMap) -> Vec<(Nibbles, Vec)> { let mut accounts: Vec<(Nibbles, Vec)> = accounts .iter() .map(|(address, account)| { let data = trie_account_rlp(&account.info, &account.storage); (Nibbles::unpack(keccak256(*address)), data) }) .collect(); accounts.sort_by_key(|(key, _)| *key); accounts } /// Returns the RLP for this account. pub fn trie_account_rlp(info: &AccountInfo, storage: &HashMap) -> Vec { let mut out: Vec = Vec::new(); let list: [&dyn Encodable; 4] = [&info.nonce, &info.balance, &storage_root(storage), &info.code_hash]; alloy_rlp::encode_list::<_, dyn Encodable>(&list, &mut out); out } ================================================ FILE: crates/anvil/src/eth/backend/mem/storage.rs ================================================ //! In-memory blockchain storage use crate::eth::{ backend::{ db::{ MaybeFullDatabase, SerializableBlock, SerializableHistoricalStates, SerializableTransaction, StateDb, }, env::Env, mem::cache::DiskStateCache, }, pool::transactions::PoolTransaction, }; use alloy_consensus::{BlockHeader, Header, constants::EMPTY_WITHDRAWALS}; use alloy_eips::eip7685::EMPTY_REQUESTS_HASH; use alloy_network::Network; use alloy_primitives::{ B256, Bytes, U256, map::{B256HashMap, HashMap}, }; use alloy_rpc_types::{ BlockId, BlockNumberOrTag, TransactionInfo as RethTransactionInfo, trace::{ otterscan::{InternalOperation, OperationType}, parity::LocalizedTransactionTrace, }, }; use anvil_core::eth::{ block::{Block, create_block}, transaction::{MaybeImpersonatedTransaction, TransactionInfo}, }; use foundry_evm::{ backend::MemDb, traces::{CallKind, ParityTraceBuilder, TracingInspectorConfig}, }; use foundry_primitives::{FoundryNetwork, FoundryTxEnvelope}; use parking_lot::RwLock; use revm::{context::Block as RevmBlock, primitives::hardfork::SpecId}; use std::{collections::VecDeque, fmt, path::PathBuf, sync::Arc, time::Duration}; // use yansi::Paint; // === various limits in number of blocks === pub const DEFAULT_HISTORY_LIMIT: usize = 500; const MIN_HISTORY_LIMIT: usize = 10; // 1hr of up-time at lowest 1s interval const MAX_ON_DISK_HISTORY_LIMIT: usize = 3_600; /// Represents the complete state of single block pub struct InMemoryBlockStates { /// The states at a certain block states: B256HashMap, /// states which data is moved to disk on_disk_states: B256HashMap, /// How many states to store at most in_memory_limit: usize, /// minimum amount of states we keep in memory min_in_memory_limit: usize, /// maximum amount of states we keep on disk /// /// Limiting the states will prevent disk blow up, especially in interval mining mode max_on_disk_limit: usize, /// the oldest states written to disk oldest_on_disk: VecDeque, /// all states present, used to enforce `in_memory_limit` present: VecDeque, /// Stores old states on disk disk_cache: DiskStateCache, } impl InMemoryBlockStates { /// Creates a new instance with limited slots pub fn new(in_memory_limit: usize, on_disk_limit: usize) -> Self { Self { states: Default::default(), on_disk_states: Default::default(), in_memory_limit, min_in_memory_limit: in_memory_limit.min(MIN_HISTORY_LIMIT), max_on_disk_limit: on_disk_limit, oldest_on_disk: Default::default(), present: Default::default(), disk_cache: Default::default(), } } /// Configures no disk caching pub fn memory_only(mut self) -> Self { self.max_on_disk_limit = 0; self } /// Configures the path on disk where the states will cached. pub fn disk_path(mut self, path: PathBuf) -> Self { self.disk_cache = self.disk_cache.with_path(path); self } /// This modifies the `limit` what to keep stored in memory. /// /// This will ensure the new limit adjusts based on the block time. /// The lowest blocktime is 1s which should increase the limit slightly pub fn update_interval_mine_block_time(&mut self, block_time: Duration) { let block_time = block_time.as_secs(); // for block times lower than 2s we increase the mem limit since we're mining _small_ blocks // very fast // this will gradually be decreased once the max limit was reached if block_time <= 2 { self.in_memory_limit = DEFAULT_HISTORY_LIMIT * 3; self.enforce_limits(); } } /// Returns true if only memory caching is supported. fn is_memory_only(&self) -> bool { self.max_on_disk_limit == 0 } /// Inserts a new (hash -> state) pair /// /// When the configured limit for the number of states that can be stored in memory is reached, /// the oldest state is removed. /// /// Since we keep a snapshot of the entire state as history, the size of the state will increase /// with the transactions processed. To counter this, we gradually decrease the cache limit with /// the number of states/blocks until we reached the `min_limit`. /// /// When a state that was previously written to disk is requested, it is simply read from disk. pub fn insert(&mut self, hash: B256, state: StateDb) { if !self.is_memory_only() && self.present.len() >= self.in_memory_limit { // once we hit the max limit we gradually decrease it self.in_memory_limit = self.in_memory_limit.saturating_sub(1).max(self.min_in_memory_limit); } self.enforce_limits(); self.states.insert(hash, state); self.present.push_back(hash); } /// Enforces configured limits fn enforce_limits(&mut self) { // enforce memory limits while self.present.len() >= self.in_memory_limit { // evict the oldest block if let Some((hash, mut state)) = self .present .pop_front() .and_then(|hash| self.states.remove(&hash).map(|state| (hash, state))) { // only write to disk if supported if !self.is_memory_only() { let state_snapshot = state.0.clear_into_state_snapshot(); if self.disk_cache.write(hash, &state_snapshot) { // Write succeeded, move state to on-disk tracking self.on_disk_states.insert(hash, state); self.oldest_on_disk.push_back(hash); } else { // Write failed, restore state to memory to avoid data loss state.init_from_state_snapshot(state_snapshot); self.states.insert(hash, state); self.present.push_front(hash); // Increase limit temporarily to prevent infinite retry loop self.in_memory_limit = self.in_memory_limit.saturating_add(1); break; } } } } // enforce on disk limit and purge the oldest state cached on disk while !self.is_memory_only() && self.oldest_on_disk.len() >= self.max_on_disk_limit { // evict the oldest block if let Some(hash) = self.oldest_on_disk.pop_front() { self.on_disk_states.remove(&hash); self.disk_cache.remove(hash); } } } /// Returns the in-memory state for the given `hash` if present pub fn get_state(&self, hash: &B256) -> Option<&StateDb> { self.states.get(hash) } /// Returns on-disk state for the given `hash` if present pub fn get_on_disk_state(&mut self, hash: &B256) -> Option<&StateDb> { if let Some(state) = self.on_disk_states.get_mut(hash) && let Some(cached) = self.disk_cache.read(*hash) { state.init_from_state_snapshot(cached); return Some(state); } None } /// Sets the maximum number of stats we keep in memory pub fn set_cache_limit(&mut self, limit: usize) { self.in_memory_limit = limit; } /// Clears all entries pub fn clear(&mut self) { self.states.clear(); self.on_disk_states.clear(); self.present.clear(); for on_disk in std::mem::take(&mut self.oldest_on_disk) { self.disk_cache.remove(on_disk) } } /// Serialize all states to a list of serializable historical states pub fn serialized_states(&mut self) -> SerializableHistoricalStates { // Get in-memory states let mut states = self .states .iter_mut() .map(|(hash, state)| (*hash, state.serialize_state())) .collect::>(); // Get on-disk state snapshots self.on_disk_states.iter().for_each(|(hash, _)| { if let Some(state_snapshot) = self.disk_cache.read(*hash) { states.push((*hash, state_snapshot)); } }); SerializableHistoricalStates::new(states) } /// Load states from serialized data pub fn load_states(&mut self, states: SerializableHistoricalStates) { for (hash, state_snapshot) in states { let mut state_db = StateDb::new(MemDb::default()); state_db.init_from_state_snapshot(state_snapshot); self.insert(hash, state_db); } } } impl fmt::Debug for InMemoryBlockStates { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("InMemoryBlockStates") .field("in_memory_limit", &self.in_memory_limit) .field("min_in_memory_limit", &self.min_in_memory_limit) .field("max_on_disk_limit", &self.max_on_disk_limit) .field("oldest_on_disk", &self.oldest_on_disk) .field("present", &self.present) .finish_non_exhaustive() } } impl Default for InMemoryBlockStates { fn default() -> Self { // enough in memory to store `DEFAULT_HISTORY_LIMIT` blocks in memory Self::new(DEFAULT_HISTORY_LIMIT, MAX_ON_DISK_HISTORY_LIMIT) } } /// Stores the blockchain data (blocks, transactions) #[derive(Clone, Debug)] pub struct BlockchainStorage { /// all stored blocks (block hash -> block) pub blocks: B256HashMap, /// mapping from block number -> block hash pub hashes: HashMap, /// The current best hash pub best_hash: B256, /// The current best block number 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 /// transaction receipt pub transactions: B256HashMap>, /// The total difficulty of the chain until this block pub total_difficulty: U256, } impl BlockchainStorage { /// Creates a new storage with a genesis block pub fn new( env: &Env, spec_id: SpecId, base_fee: Option, timestamp: u64, genesis_number: u64, ) -> Self { let is_shanghai = spec_id >= SpecId::SHANGHAI; let is_cancun = spec_id >= SpecId::CANCUN; let is_prague = spec_id >= SpecId::PRAGUE; // create a dummy genesis block let header = Header { timestamp, base_fee_per_gas: base_fee, 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), requests_hash: is_prague.then_some(EMPTY_REQUESTS_HASH), ..Default::default() }; let block = create_block(header, Vec::>::new()); let genesis_hash = block.header.hash_slow(); let best_hash = genesis_hash; let best_number = genesis_number; let mut blocks = B256HashMap::default(); blocks.insert(genesis_hash, block); let mut hashes = HashMap::default(); hashes.insert(best_number, genesis_hash); Self { blocks, hashes, best_hash, best_number, genesis_hash, transactions: Default::default(), total_difficulty: Default::default(), } } pub fn forked(block_number: u64, block_hash: B256, total_difficulty: U256) -> Self { let mut hashes = HashMap::default(); hashes.insert(block_number, block_hash); Self { blocks: B256HashMap::default(), hashes, best_hash: block_hash, best_number: block_number, genesis_hash: Default::default(), transactions: Default::default(), total_difficulty, } } /// Unwind the chain state back to the given block in storage. /// /// 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) -> Vec { let mut removed = vec![]; let best_num: u64 = self.best_number; for i in (block_number + 1)..=best_num { if let Some(hash) = self.hashes.get(&i).copied() { // First remove the block's transactions while the mappings still exist self.remove_block_transactions_by_number(i); // Now remove the block from storage (may already be empty of txs) and drop mapping if let Some(block) = self.blocks.remove(&hash) { removed.push(block); } self.hashes.remove(&i); } } self.best_hash = block_hash; self.best_number = block_number; removed } pub fn empty() -> Self { Self { blocks: Default::default(), hashes: Default::default(), best_hash: Default::default(), best_number: Default::default(), genesis_hash: Default::default(), transactions: Default::default(), total_difficulty: Default::default(), } } /// 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(&num).copied() { self.remove_block_transactions(hash); } } /// Removes all stored transactions for the given block hash pub fn remove_block_transactions(&mut self, block_hash: B256) { if let Some(block) = self.blocks.get_mut(&block_hash) { for tx in &block.body.transactions { self.transactions.remove(&tx.hash()); } block.body.transactions.clear(); } } /// Serialize all blocks in storage pub fn serialized_blocks(&self) -> Vec { self.blocks.values().map(|block| block.clone().into()).collect() } /// Deserialize and add all blocks data to the backend storage pub fn load_blocks(&mut self, serializable_blocks: Vec) { for serializable_block in &serializable_blocks { let block: Block = serializable_block.clone().into(); let block_hash = block.header.hash_slow(); let block_number = block.header.number(); self.blocks.insert(block_hash, block); self.hashes.insert(block_number, block_hash); // Update genesis_hash if we are loading block 0, so that Finalized/Safe/Earliest // block tag lookups return the correct hash. // See: https://github.com/foundry-rs/foundry/issues/12645 if block_number == 0 { self.genesis_hash = block_hash; } } } /// Returns the hash for [BlockNumberOrTag] pub fn hash(&self, number: BlockNumberOrTag) -> Option { 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(&num).copied(), BlockNumberOrTag::Safe => { if self.best_number > (slots_in_an_epoch) { self.hashes.get(&(self.best_number - (slots_in_an_epoch))).copied() } else { Some(self.genesis_hash) // treat the genesis block as safe "by definition" } } BlockNumberOrTag::Finalized => { 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) } } } } } impl BlockchainStorage { pub fn serialized_transactions(&self) -> Vec { self.transactions .values() .map(|tx: &MinedTransaction| tx.clone().into()) .collect() } /// Deserialize and add all transactions data to the backend storage pub fn load_transactions(&mut self, serializable_transactions: Vec) { for serializable_transaction in &serializable_transactions { let transaction: MinedTransaction = serializable_transaction.clone().into(); self.transactions.insert(transaction.info.transaction_hash, transaction); } } } /// A simple in-memory blockchain #[derive(Clone, Debug)] pub struct Blockchain { /// underlying storage that supports concurrent reads pub storage: Arc>>, } impl Blockchain { /// Creates a new storage with a genesis block pub fn new( env: &Env, spec_id: SpecId, base_fee: Option, timestamp: u64, genesis_number: u64, ) -> Self { Self { storage: Arc::new(RwLock::new(BlockchainStorage::new( env, spec_id, base_fee, timestamp, genesis_number, ))), } } pub fn forked(block_number: u64, block_hash: B256, total_difficulty: U256) -> Self { Self { storage: Arc::new(RwLock::new(BlockchainStorage::forked( block_number, block_hash, total_difficulty, ))), } } /// returns the header hash of given block pub fn hash(&self, id: BlockId) -> Option { match id { BlockId::Hash(h) => Some(h.block_hash), BlockId::Number(num) => self.storage.read().hash(num), } } pub fn get_block_by_hash(&self, hash: &B256) -> Option { self.storage.read().blocks.get(hash).cloned() } pub fn get_transaction_by_hash(&self, hash: &B256) -> Option> { self.storage.read().transactions.get(hash).cloned() } /// Returns the total number of blocks pub fn blocks_count(&self) -> usize { self.storage.read().blocks.len() } } /// Represents the outcome of mining a new block pub struct MinedBlockOutcome { /// The block that was mined 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 /// execution pub invalid: Vec>>, } impl Clone for MinedBlockOutcome { fn clone(&self) -> Self { Self { block_number: self.block_number, included: self.included.clone(), invalid: self.invalid.clone(), } } } impl fmt::Debug for MinedBlockOutcome { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("MinedBlockOutcome") .field("block_number", &self.block_number) .field("included", &self.included.len()) .field("invalid", &self.invalid.len()) .finish() } } /// Container type for a mined transaction #[derive(Clone, Debug)] pub struct MinedTransaction { pub info: TransactionInfo, pub receipt: N::ReceiptEnvelope, pub block_hash: B256, pub block_number: u64, } impl MinedTransaction { /// Returns the traces of the transaction for `trace_transaction` pub fn parity_traces(&self) -> Vec { ParityTraceBuilder::new( self.info.traces.clone(), None, TracingInspectorConfig::default_parity(), ) .into_localized_transaction_traces(RethTransactionInfo { hash: Some(self.info.transaction_hash), index: Some(self.info.transaction_index), block_hash: Some(self.block_hash), block_number: Some(self.block_number), base_fee: None, block_timestamp: None, }) } pub fn ots_internal_operations(&self) -> Vec { self.info .traces .iter() .filter_map(|node| { let r#type = match node.trace.kind { _ if node.is_selfdestruct() => OperationType::OpSelfDestruct, CallKind::Call if !node.trace.value.is_zero() => OperationType::OpTransfer, CallKind::Create => OperationType::OpCreate, CallKind::Create2 => OperationType::OpCreate2, _ => return None, }; let mut from = node.trace.caller; let mut to = node.trace.address; let mut value = node.trace.value; if node.is_selfdestruct() { from = node.trace.address; to = node.trace.selfdestruct_refund_target.unwrap_or_default(); value = node.trace.selfdestruct_transferred_value.unwrap_or_default(); } Some(InternalOperation { r#type, from, to, value }) }) .collect() } } /// Intermediary Anvil representation of a receipt #[derive(Clone, Debug)] pub struct MinedTransactionReceipt { /// The actual json rpc receipt object pub inner: N::ReceiptResponse, /// Output data for the transaction pub out: Option, } #[cfg(test)] mod tests { use super::*; use crate::eth::backend::db::Db; use alloy_primitives::{Address, hex}; use alloy_rlp::Decodable; use revm::{database::DatabaseRef, state::AccountInfo}; #[test] fn test_interval_update() { let mut storage = InMemoryBlockStates::default(); storage.update_interval_mine_block_time(Duration::from_secs(1)); assert_eq!(storage.in_memory_limit, DEFAULT_HISTORY_LIMIT * 3); } #[test] fn test_init_state_limits() { let mut storage = InMemoryBlockStates::default(); assert_eq!(storage.in_memory_limit, DEFAULT_HISTORY_LIMIT); assert_eq!(storage.min_in_memory_limit, MIN_HISTORY_LIMIT); assert_eq!(storage.max_on_disk_limit, MAX_ON_DISK_HISTORY_LIMIT); storage = storage.memory_only(); assert!(storage.is_memory_only()); storage = InMemoryBlockStates::new(1, 0); assert!(storage.is_memory_only()); assert_eq!(storage.in_memory_limit, 1); assert_eq!(storage.min_in_memory_limit, 1); assert_eq!(storage.max_on_disk_limit, 0); storage = InMemoryBlockStates::new(1, 2); assert!(!storage.is_memory_only()); assert_eq!(storage.in_memory_limit, 1); assert_eq!(storage.min_in_memory_limit, 1); assert_eq!(storage.max_on_disk_limit, 2); } #[tokio::test(flavor = "multi_thread")] async fn can_read_write_cached_state() { let mut storage = InMemoryBlockStates::new(1, MAX_ON_DISK_HISTORY_LIMIT); let one = B256::from(U256::from(1)); let two = B256::from(U256::from(2)); let mut state = MemDb::default(); let addr = Address::random(); let info = AccountInfo::from_balance(U256::from(1337)); state.insert_account(addr, info); storage.insert(one, StateDb::new(state)); storage.insert(two, StateDb::new(MemDb::default())); // wait for files to be flushed tokio::time::sleep(std::time::Duration::from_secs(1)).await; assert_eq!(storage.on_disk_states.len(), 1); assert!(storage.on_disk_states.contains_key(&one)); let loaded = storage.get_on_disk_state(&one).unwrap(); let acc = loaded.basic_ref(addr).unwrap().unwrap(); assert_eq!(acc.balance, U256::from(1337u64)); } #[tokio::test(flavor = "multi_thread")] async fn can_decrease_state_cache_size() { let limit = 15; let mut storage = InMemoryBlockStates::new(limit, MAX_ON_DISK_HISTORY_LIMIT); let num_states = 30; for idx in 0..num_states { let mut state = MemDb::default(); let hash = B256::from(U256::from(idx)); let addr = Address::from_word(hash); let balance = (idx * 2) as u64; let info = AccountInfo::from_balance(U256::from(balance)); state.insert_account(addr, info); storage.insert(hash, StateDb::new(state)); } // wait for files to be flushed tokio::time::sleep(std::time::Duration::from_secs(1)).await; let on_disk_states_len = num_states - storage.min_in_memory_limit; assert_eq!(storage.on_disk_states.len(), on_disk_states_len); assert_eq!(storage.present.len(), storage.min_in_memory_limit); for idx in 0..num_states { let hash = B256::from(U256::from(idx)); let addr = Address::from_word(hash); let loaded = if idx < on_disk_states_len { storage.get_on_disk_state(&hash).unwrap() } else { storage.get_state(&hash).unwrap() }; let acc = loaded.basic_ref(addr).unwrap().unwrap(); let balance = (idx * 2) as u64; assert_eq!(acc.balance, U256::from(balance)); } } // verifies that blocks and transactions in BlockchainStorage remain the same when dumped and // reloaded #[test] fn test_storage_dump_reload_cycle() { let mut dump_storage = BlockchainStorage::::empty(); let header = Header { gas_limit: 123456, ..Default::default() }; let bytes_first = &mut &hex::decode("f86b02843b9aca00830186a094d3e8763675e4c425df46cc3b5c0f6cbdac39604687038d7ea4c68000802ba00eb96ca19e8a77102767a41fc85a36afd5c61ccb09911cec5d3e86e193d9c5aea03a456401896b1b6055311536bf00a718568c744d8c1f9df59879e8350220ca18").unwrap()[..]; let tx: MaybeImpersonatedTransaction = FoundryTxEnvelope::decode(&mut &bytes_first[..]).unwrap().into(); let block = create_block(header.clone(), vec![tx.clone()]); let block_hash = block.header.hash_slow(); dump_storage.blocks.insert(block_hash, block); let serialized_blocks = dump_storage.serialized_blocks(); let serialized_transactions = dump_storage.serialized_transactions(); let mut load_storage = BlockchainStorage::::empty(); load_storage.load_blocks(serialized_blocks); load_storage.load_transactions(serialized_transactions); let loaded_block = load_storage.blocks.get(&block_hash).unwrap(); assert_eq!(loaded_block.header.gas_limit(), header.gas_limit()); let loaded_tx = loaded_block.body.transactions.first().unwrap(); assert_eq!(loaded_tx, &tx); } } ================================================ FILE: crates/anvil/src/eth/backend/mod.rs ================================================ //! blockchain Backend /// [revm](foundry_evm::revm) related types pub mod db; /// In-memory Backend pub mod mem; pub mod cheats; pub mod time; pub mod env; pub mod executor; pub mod fork; pub mod genesis; pub mod info; pub mod notifications; pub mod validate; ================================================ FILE: crates/anvil/src/eth/backend/notifications.rs ================================================ //! Notifications emitted from the backed use alloy_consensus::Header; use alloy_primitives::B256; use futures::channel::mpsc::UnboundedReceiver; use std::sync::Arc; /// A notification that's emitted when a new block was imported #[derive(Clone, Debug)] pub struct NewBlockNotification { /// Hash of the imported block pub hash: B256, /// block header pub header: Arc
, } /// Type alias for a receiver that receives [NewBlockNotification] pub type NewBlockNotifications = UnboundedReceiver; ================================================ FILE: crates/anvil/src/eth/backend/time.rs ================================================ //! Manages the block time use crate::eth::error::BlockchainError; use chrono::{DateTime, Utc}; use parking_lot::RwLock; use std::{sync::Arc, time::Duration}; /// Returns the `Utc` datetime for the given seconds since unix epoch pub fn utc_from_secs(secs: u64) -> DateTime { DateTime::from_timestamp(secs as i64, 0).unwrap_or(DateTime::::MAX_UTC) } /// Manages block time #[derive(Clone, Debug)] pub struct TimeManager { /// tracks the overall applied timestamp offset offset: Arc>, /// The timestamp of the last block header last_timestamp: Arc>, /// Contains the next timestamp to use /// if this is set then the next time `[TimeManager::current_timestamp()]` is called this value /// will be taken and returned. After which the `offset` will be updated accordingly next_exact_timestamp: Arc>>, /// The interval to use when determining the next block's timestamp interval: Arc>>, } impl TimeManager { pub fn new(start_timestamp: u64) -> Self { let time_manager = Self { last_timestamp: Default::default(), offset: Default::default(), next_exact_timestamp: Default::default(), interval: Default::default(), }; time_manager.reset(start_timestamp); time_manager } /// Resets the current time manager to the given timestamp, resetting the offsets and /// next block timestamp option pub fn reset(&self, start_timestamp: u64) { let current = duration_since_unix_epoch().as_secs() as i128; *self.last_timestamp.write() = start_timestamp; *self.offset.write() = (start_timestamp as i128) - current; self.next_exact_timestamp.write().take(); } pub fn offset(&self) -> i128 { *self.offset.read() } /// Adds the given `offset` to the already tracked offset and returns the result fn add_offset(&self, offset: i128) -> i128 { let mut current = self.offset.write(); let next = current.saturating_add(offset); trace!(target: "time", "adding timestamp offset={}, total={}", offset, next); *current = next; next } /// Jumps forward in time by the given seconds /// /// This will apply a permanent offset to the natural UNIX Epoch timestamp pub fn increase_time(&self, seconds: u64) -> i128 { self.add_offset(seconds as i128) } /// Sets the exact timestamp to use in the next block /// Fails if it's before (or at the same time) the last timestamp pub fn set_next_block_timestamp(&self, timestamp: u64) -> Result<(), BlockchainError> { trace!(target: "time", "override next timestamp {}", timestamp); if timestamp < *self.last_timestamp.read() { return Err(BlockchainError::TimestampError(format!( "{timestamp} is lower than previous block's timestamp" ))); } self.next_exact_timestamp.write().replace(timestamp); Ok(()) } /// Sets an interval to use when computing the next timestamp /// /// If an interval already exists, this will update the interval, otherwise a new interval will /// be set starting with the current timestamp. pub fn set_block_timestamp_interval(&self, interval: u64) { trace!(target: "time", "set interval {}", interval); self.interval.write().replace(interval); } /// Removes the interval if it exists pub fn remove_block_timestamp_interval(&self) -> bool { if self.interval.write().take().is_some() { trace!(target: "time", "removed interval"); true } else { false } } /// Computes the next timestamp without updating internals fn compute_next_timestamp(&self) -> (u64, Option) { let current = duration_since_unix_epoch().as_secs() as i128; let last_timestamp = *self.last_timestamp.read(); let (mut next_timestamp, update_offset) = if let Some(next) = *self.next_exact_timestamp.read() { (next, true) } else if let Some(interval) = *self.interval.read() { (last_timestamp.saturating_add(interval), false) } else { (current.saturating_add(self.offset()) as u64, false) }; // Ensures that the timestamp is always increasing if next_timestamp < last_timestamp { next_timestamp = last_timestamp + 1; } let next_offset = update_offset.then_some((next_timestamp as i128) - current); (next_timestamp, next_offset) } /// Returns the current timestamp and updates the underlying offset and interval accordingly pub fn next_timestamp(&self) -> u64 { let (next_timestamp, next_offset) = self.compute_next_timestamp(); // Make sure we reset the `next_exact_timestamp` self.next_exact_timestamp.write().take(); if let Some(next_offset) = next_offset { *self.offset.write() = next_offset; } *self.last_timestamp.write() = next_timestamp; next_timestamp } /// Returns the current timestamp for a call that does _not_ update the value pub fn current_call_timestamp(&self) -> u64 { let (next_timestamp, _) = self.compute_next_timestamp(); next_timestamp } } /// Returns the current duration since unix epoch. pub fn duration_since_unix_epoch() -> Duration { use std::time::SystemTime; let now = SystemTime::now(); now.duration_since(SystemTime::UNIX_EPOCH) .unwrap_or_else(|err| panic!("Current time {now:?} is invalid: {err:?}")) } ================================================ FILE: crates/anvil/src/eth/backend/validate.rs ================================================ //! Support for validating transactions at certain stages use crate::eth::{ backend::env::Env, error::{BlockchainError, InvalidTransactionError}, }; use anvil_core::eth::transaction::PendingTransaction; use revm::state::AccountInfo; /// A trait for validating transactions #[async_trait::async_trait] pub trait TransactionValidator { /// Validates the transaction's validity when it comes to nonce, payment /// /// This is intended to be checked before the transaction makes it into the pool and whether it /// should rather be outright rejected if the sender has insufficient funds. async fn validate_pool_transaction( &self, tx: &PendingTransaction, ) -> Result<(), BlockchainError>; /// Validates the transaction against a specific account before entering the pool fn validate_pool_transaction_for( &self, tx: &PendingTransaction, account: &AccountInfo, env: &Env, ) -> Result<(), InvalidTransactionError>; /// Validates the transaction against a specific account /// /// This should succeed if the transaction is ready to be executed fn validate_for( &self, tx: &PendingTransaction, account: &AccountInfo, env: &Env, ) -> Result<(), InvalidTransactionError>; } ================================================ FILE: crates/anvil/src/eth/error.rs ================================================ //! Aggregated error type for this module use alloy_consensus::crypto::RecoveryError; use alloy_evm::overrides::StateOverrideError; use alloy_primitives::{B256, Bytes, SignatureError, TxHash}; use alloy_rpc_types::BlockNumberOrTag; use alloy_signer::Error as SignerError; use alloy_transport::TransportError; use anvil_core::eth::wallet::WalletError; use anvil_rpc::{ error::{ErrorCode, RpcError}, response::ResponseResult, }; use foundry_evm::{backend::DatabaseError, decode::RevertDecoder}; use op_revm::OpTransactionError; use revm::{ context_interface::result::{EVMError, InvalidHeader, InvalidTransaction}, interpreter::InstructionResult, }; use serde::Serialize; use tokio::time::Duration; pub(crate) type Result = std::result::Result; #[derive(Debug, thiserror::Error)] pub enum BlockchainError { #[error(transparent)] Pool(#[from] PoolError), #[error("No signer available")] NoSignerAvailable, #[error("Chain Id not available")] ChainIdNotAvailable, #[error("Invalid input: `max_priority_fee_per_gas` greater than `max_fee_per_gas`")] InvalidFeeInput, #[error("Transaction data is empty")] EmptyRawTransactionData, #[error("Failed to decode signed transaction")] FailedToDecodeSignedTransaction, #[error("Failed to decode transaction")] FailedToDecodeTransaction, #[error("Failed to decode receipt")] FailedToDecodeReceipt, #[error("Failed to decode state")] FailedToDecodeStateDump, #[error("Prevrandao not in the EVM's environment after merge")] PrevrandaoNotSet, #[error(transparent)] SignatureError(#[from] SignatureError), #[error(transparent)] RecoveryError(#[from] RecoveryError), #[error(transparent)] SignerError(#[from] SignerError), #[error("Rpc Endpoint not implemented")] RpcUnimplemented, #[error("Rpc error {0:?}")] RpcError(RpcError), #[error(transparent)] InvalidTransaction(#[from] InvalidTransactionError), #[error(transparent)] FeeHistory(#[from] FeeHistoryError), #[error(transparent)] 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:?}")] Internal(String), #[error("BlockOutOfRangeError: block height is {0} but requested was {1}")] BlockOutOfRange(u64, u64), #[error("Resource not found")] BlockNotFound, /// Thrown when a requested transaction is not found #[error("transaction not found")] TransactionNotFound, #[error("Required data unavailable")] DataUnavailable, #[error("Trie error: {0}")] TrieError(String), #[error("{0}")] UintConversion(&'static str), #[error("State override error: {0}")] StateOverrideError(String), #[error("Timestamp error: {0}")] TimestampError(String), #[error(transparent)] DatabaseError(#[from] DatabaseError), #[error( "EIP-1559 style fee params (maxFeePerGas or maxPriorityFeePerGas) received but they are not supported by the current hardfork.\n\nYou can use them by running anvil with '--hardfork london' or later." )] EIP1559TransactionUnsupportedAtHardfork, #[error( "Access list received but is not supported by the current hardfork.\n\nYou can use it by running anvil with '--hardfork berlin' or later." )] EIP2930TransactionUnsupportedAtHardfork, #[error( "EIP-4844 fields received but is not supported by the current hardfork.\n\nYou can use it by running anvil with '--hardfork cancun' or later." )] EIP4844TransactionUnsupportedAtHardfork, #[error( "EIP-7702 fields received but is not supported by the current hardfork.\n\nYou can use it by running anvil with '--hardfork prague' or later." )] EIP7702TransactionUnsupportedAtHardfork, #[error( "op-stack deposit tx received but is not supported.\n\nYou can use it by running anvil with '--optimism'." )] DepositTransactionUnsupported, #[error("Unknown transaction type not supported")] UnknownTransactionType, #[error("Excess blob gas not set.")] ExcessBlobGasNotSet, #[error("{0}")] Message(String), #[error("Transaction {hash} was added to the mempool but wasn't confirmed within {duration:?}")] TransactionConfirmationTimeout { /// Hash of the transaction that timed out hash: B256, /// Duration that was waited before timing out duration: Duration, }, #[error("Invalid transaction request: {0}")] InvalidTransactionRequest(String), #[error("filter not found")] FilterNotFound, } impl From for BlockchainError { fn from(err: eyre::Report) -> Self { Self::Message(err.to_string()) } } impl From for BlockchainError { fn from(err: RpcError) -> Self { Self::RpcError(err) } } impl From> for BlockchainError where T: Into, { fn from(err: EVMError) -> Self { match err { EVMError::Transaction(err) => InvalidTransactionError::from(err).into(), EVMError::Header(err) => match err { InvalidHeader::ExcessBlobGasNotSet => Self::ExcessBlobGasNotSet, InvalidHeader::PrevrandaoNotSet => Self::PrevrandaoNotSet, }, EVMError::Database(err) => err.into(), 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 } OpTransactionError::MissingEnvelopedTx => Self::InvalidTransaction(err.into()), }, EVMError::Header(err) => match err { InvalidHeader::ExcessBlobGasNotSet => Self::ExcessBlobGasNotSet, InvalidHeader::PrevrandaoNotSet => Self::PrevrandaoNotSet, }, EVMError::Database(err) => err.into(), EVMError::Custom(err) => Self::Message(err), } } } impl From for BlockchainError { fn from(value: WalletError) -> Self { Self::Message(value.to_string()) } } impl From> for BlockchainError where E: Into, { fn from(value: StateOverrideError) -> Self { match value { StateOverrideError::InvalidBytecode(err) => Self::StateOverrideError(err.to_string()), StateOverrideError::BothStateAndStateDiff(addr) => Self::StateOverrideError(format!( "state and state_diff can't be used together for account {addr}", )), StateOverrideError::Database(err) => err.into(), } } } /// Errors that can occur in the transaction pool #[derive(Debug, thiserror::Error)] pub enum PoolError { #[error("Transaction with cyclic dependent transactions")] CyclicTransaction, /// Thrown if a replacement transaction's gas price is below the already imported transaction #[error("Tx: [{0:?}] insufficient gas price to replace existing transaction")] ReplacementUnderpriced(TxHash), #[error("Tx: [{0:?}] already Imported")] AlreadyImported(TxHash), } /// Errors that can occur with `eth_feeHistory` #[derive(Debug, thiserror::Error)] pub enum FeeHistoryError { #[error("requested block range is out of bounds")] InvalidBlockRange, #[error("could not find newest block number requested: {0}")] BlockNotFound(BlockNumberOrTag), } #[derive(Debug)] pub struct ErrDetail { pub detail: String, } /// An error due to invalid transaction #[derive(Debug, thiserror::Error)] pub enum InvalidTransactionError { /// returned if the nonce of a transaction is lower than the one present in the local chain. #[error("nonce too low")] NonceTooLow, /// returned if the nonce of a transaction is higher than the next one expected based on the /// local chain. #[error("Nonce too high")] NonceTooHigh, /// Returned if the nonce of a transaction is too high /// Incrementing the nonce would lead to invalid state (overflow) #[error("nonce has max value")] NonceMaxValue, /// thrown if the transaction sender doesn't have enough funds for a transfer #[error("insufficient funds for transfer")] InsufficientFundsForTransfer, /// thrown if creation transaction provides the init code bigger than init code size limit. #[error("max initcode size exceeded")] MaxInitCodeSizeExceeded, /// Represents the inability to cover max cost + value (account balance too low). #[error("Insufficient funds for gas * price + value")] InsufficientFunds, /// Thrown when calculating gas usage #[error("gas uint64 overflow")] GasUintOverflow, /// returned if the transaction is specified to use less gas than required to start the /// invocation. #[error("intrinsic gas too low")] GasTooLow, /// returned if the transaction gas exceeds the limit #[error("intrinsic gas too high -- {}",.0.detail)] GasTooHigh(ErrDetail), /// Thrown to ensure no one is able to specify a transaction with a tip higher than the total /// fee cap. #[error("max priority fee per gas higher than max fee per gas")] TipAboveFeeCap, /// Thrown post London if the transaction's fee is less than the base fee of the block #[error("max fee per gas less than block base fee")] FeeCapTooLow, /// Thrown during estimate if caller has insufficient funds to cover the tx. #[error("Out of gas: gas required exceeds allowance: {0:?}")] BasicOutOfGas(u128), /// Thrown if executing a transaction failed during estimate/call #[error("execution reverted: {0:?}")] Revert(Option), /// Thrown if the sender of a transaction is a contract. #[error("sender not an eoa")] SenderNoEOA, /// Thrown when a tx was signed with a different chain_id #[error("invalid chain id for signer")] InvalidChainId, /// Thrown when a legacy tx was signed for a different chain #[error("Incompatible EIP-155 transaction, signed for another chain")] IncompatibleEIP155, /// Thrown when an access list is used before the berlin hard fork. #[error("Access lists are not supported before the Berlin hardfork")] AccessListNotSupported, /// Thrown when the block's `blob_gas_price` is greater than tx-specified /// `max_fee_per_blob_gas` after Cancun. #[error("Block `blob_gas_price` is greater than tx-specified `max_fee_per_blob_gas`")] BlobFeeCapTooLow(u128, u128), /// Thrown when we receive a tx with `blob_versioned_hashes` and we're not on the Cancun hard /// fork. #[error("Block `blob_versioned_hashes` is not supported before the Cancun hardfork")] BlobVersionedHashesNotSupported, /// Thrown when `max_fee_per_blob_gas` is not supported for blocks before the Cancun hardfork. #[error("`max_fee_per_blob_gas` is not supported for blocks before the Cancun hardfork.")] MaxFeePerBlobGasNotSupported, /// 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}, max: {1}")] TooManyBlobs(usize, usize), /// Thrown when there's a blob validation error #[error(transparent)] BlobTransactionValidationError(#[from] alloy_consensus::BlobTransactionValidationError), /// Thrown when Blob transaction is a create transaction. `to` must be present. #[error("Blob transaction can't be a create transaction. `to` must be present.")] BlobCreateTransaction, /// Thrown when Blob transaction contains a versioned hash with an incorrect version. #[error("Blob transaction contains a versioned hash with an incorrect version")] BlobVersionNotSupported, /// Thrown when there are no `blob_hashes` in the transaction. #[error("There should be at least one blob in a Blob transaction.")] EmptyBlobs, /// Thrown when an access list is used before the berlin hard fork. #[error("EIP-7702 authorization lists are not supported before the Prague hardfork")] AuthorizationListNotSupported, #[error("Transaction gas limit is greater than the block gas limit, gas_limit: {0}, cap: {1}")] TxGasLimitGreaterThanCap(u64, u64), /// Forwards error from the revm #[error(transparent)] Revm(revm::context_interface::result::InvalidTransaction), /// Deposit transaction error post regolith #[error("op-deposit failure post regolith")] DepositTxErrorPostRegolith, /// Missing enveloped transaction #[error("missing enveloped transaction")] MissingEnvelopedTx, } impl From for InvalidTransactionError { fn from(err: InvalidTransaction) -> Self { match err { InvalidTransaction::InvalidChainId => Self::InvalidChainId, InvalidTransaction::PriorityFeeGreaterThanMaxFee => Self::TipAboveFeeCap, InvalidTransaction::GasPriceLessThanBasefee => Self::FeeCapTooLow, InvalidTransaction::CallerGasLimitMoreThanBlock => { Self::GasTooHigh(ErrDetail { detail: String::from("CallerGasLimitMoreThanBlock") }) } InvalidTransaction::CallGasCostMoreThanGasLimit { .. } => { Self::GasTooHigh(ErrDetail { detail: String::from("CallGasCostMoreThanGasLimit") }) } InvalidTransaction::GasFloorMoreThanGasLimit { .. } => { Self::GasTooHigh(ErrDetail { detail: String::from("GasFloorMoreThanGasLimit") }) } InvalidTransaction::RejectCallerWithCode => Self::SenderNoEOA, InvalidTransaction::LackOfFundForMaxFee { .. } => Self::InsufficientFunds, InvalidTransaction::OverflowPaymentInTransaction => Self::GasUintOverflow, InvalidTransaction::NonceOverflowInTransaction => Self::NonceMaxValue, InvalidTransaction::CreateInitCodeSizeLimit => Self::MaxInitCodeSizeExceeded, InvalidTransaction::NonceTooHigh { .. } => Self::NonceTooHigh, InvalidTransaction::NonceTooLow { .. } => Self::NonceTooLow, InvalidTransaction::AccessListNotSupported => Self::AccessListNotSupported, InvalidTransaction::BlobGasPriceGreaterThanMax { block_blob_gas_price, tx_max_fee_per_blob_gas, } => Self::BlobFeeCapTooLow(block_blob_gas_price, tx_max_fee_per_blob_gas), InvalidTransaction::BlobVersionedHashesNotSupported => { Self::BlobVersionedHashesNotSupported } InvalidTransaction::MaxFeePerBlobGasNotSupported => Self::MaxFeePerBlobGasNotSupported, InvalidTransaction::BlobCreateTransaction => Self::BlobCreateTransaction, InvalidTransaction::BlobVersionNotSupported => Self::BlobVersionNotSupported, InvalidTransaction::EmptyBlobs => Self::EmptyBlobs, InvalidTransaction::TooManyBlobs { have, max } => Self::TooManyBlobs(have, max), InvalidTransaction::AuthorizationListNotSupported => { Self::AuthorizationListNotSupported } InvalidTransaction::TxGasLimitGreaterThanCap { gas_limit, cap } => { Self::TxGasLimitGreaterThanCap(gas_limit, cap) } InvalidTransaction::AuthorizationListInvalidFields | InvalidTransaction::Eip1559NotSupported | InvalidTransaction::Eip2930NotSupported | InvalidTransaction::Eip4844NotSupported | InvalidTransaction::Eip7702NotSupported | InvalidTransaction::EmptyAuthorizationList | InvalidTransaction::Eip7873NotSupported | InvalidTransaction::Eip7873MissingTarget | InvalidTransaction::MissingChainId | InvalidTransaction::Str(_) => 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, OpTransactionError::MissingEnvelopedTx => Self::MissingEnvelopedTx, } } } /// Helper trait to easily convert results to rpc results pub(crate) trait ToRpcResponseResult { fn to_rpc_result(self) -> ResponseResult; } /// Converts a serializable value into a `ResponseResult` pub fn to_rpc_result(val: T) -> ResponseResult { match serde_json::to_value(val) { Ok(success) => ResponseResult::Success(success), Err(err) => { error!(%err, "Failed serialize rpc response"); ResponseResult::error(RpcError::internal_error()) } } } impl ToRpcResponseResult for Result { fn to_rpc_result(self) -> ResponseResult { match self { Ok(val) => to_rpc_result(val), Err(err) => match err { BlockchainError::Pool(err) => { error!(%err, "txpool error"); match err { PoolError::CyclicTransaction => { RpcError::transaction_rejected("Cyclic transaction detected") } PoolError::ReplacementUnderpriced(_) => { RpcError::transaction_rejected("replacement transaction underpriced") } PoolError::AlreadyImported(_) => { RpcError::transaction_rejected("transaction already imported") } } } BlockchainError::NoSignerAvailable => { RpcError::invalid_params("No Signer available") } BlockchainError::ChainIdNotAvailable => { RpcError::invalid_params("Chain Id not available") } BlockchainError::TransactionConfirmationTimeout { .. } => { RpcError::internal_error_with("Transaction confirmation timeout") } BlockchainError::InvalidTransaction(err) => match err { InvalidTransactionError::Revert(data) => { // this mimics geth revert error let mut msg = "execution reverted".to_string(); if let Some(reason) = data .as_ref() .and_then(|data| RevertDecoder::new().maybe_decode(data, None)) { msg = format!("{msg}: {reason}"); } RpcError { // geth returns this error code on reverts, See code: ErrorCode::ExecutionError, message: msg.into(), data: serde_json::to_value(data).ok(), } } InvalidTransactionError::GasTooLow => { // RpcError { code: ErrorCode::ServerError(-32000), message: err.to_string().into(), data: None, } } InvalidTransactionError::GasTooHigh(_) => { // RpcError { code: ErrorCode::ServerError(-32000), message: err.to_string().into(), data: None, } } _ => RpcError::transaction_rejected(err.to_string()), }, BlockchainError::FeeHistory(err) => RpcError::invalid_params(err.to_string()), BlockchainError::EmptyRawTransactionData => { RpcError::invalid_params("Empty transaction data") } BlockchainError::FailedToDecodeSignedTransaction => { RpcError::invalid_params("Failed to decode transaction") } BlockchainError::FailedToDecodeTransaction => { RpcError::invalid_params("Failed to decode transaction") } BlockchainError::FailedToDecodeReceipt => { RpcError::invalid_params("Failed to decode receipt") } BlockchainError::FailedToDecodeStateDump => { RpcError::invalid_params("Failed to decode state dump") } BlockchainError::SignerError(err) => RpcError::invalid_params(err.to_string()), BlockchainError::SignatureError(err) => RpcError::invalid_params(err.to_string()), BlockchainError::RpcUnimplemented => { RpcError::internal_error_with("Not implemented") } BlockchainError::PrevrandaoNotSet => RpcError::internal_error_with(err.to_string()), BlockchainError::RpcError(err) => err, BlockchainError::InvalidFeeInput => RpcError::invalid_params( "Invalid input: `max_priority_fee_per_gas` greater than `max_fee_per_gas`", ), BlockchainError::AlloyForkProvider(err) => { error!(target: "backend", %err, "fork provider error"); match err { TransportError::ErrorResp(err) => RpcError { code: ErrorCode::from(err.code), message: err.message, data: err.data.and_then(|data| serde_json::to_value(data).ok()), }, err => RpcError::internal_error_with(format!("Fork Error: {err:?}")), } } 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(_, _) => { RpcError::invalid_params(err.to_string()) } err @ BlockchainError::BlockNotFound => RpcError { // code: ErrorCode::ServerError(-32001), message: err.to_string().into(), data: None, }, err @ BlockchainError::TransactionNotFound => RpcError { code: ErrorCode::ServerError(-32001), message: err.to_string().into(), data: None, }, err @ BlockchainError::DataUnavailable => { RpcError::internal_error_with(err.to_string()) } err @ BlockchainError::TrieError(_) => { RpcError::internal_error_with(err.to_string()) } BlockchainError::UintConversion(err) => RpcError::invalid_params(err), err @ BlockchainError::StateOverrideError(_) => { RpcError::invalid_params(err.to_string()) } err @ BlockchainError::TimestampError(_) => { RpcError::invalid_params(err.to_string()) } BlockchainError::DatabaseError(err) => { RpcError::internal_error_with(err.to_string()) } err @ BlockchainError::EIP1559TransactionUnsupportedAtHardfork => { RpcError::invalid_params(err.to_string()) } err @ BlockchainError::EIP2930TransactionUnsupportedAtHardfork => { RpcError::invalid_params(err.to_string()) } err @ BlockchainError::EIP4844TransactionUnsupportedAtHardfork => { RpcError::invalid_params(err.to_string()) } err @ BlockchainError::EIP7702TransactionUnsupportedAtHardfork => { RpcError::invalid_params(err.to_string()) } err @ BlockchainError::DepositTransactionUnsupported => { RpcError::invalid_params(err.to_string()) } err @ BlockchainError::ExcessBlobGasNotSet => { RpcError::invalid_params(err.to_string()) } err @ BlockchainError::Message(_) => RpcError::internal_error_with(err.to_string()), err @ BlockchainError::UnknownTransactionType => { RpcError::invalid_params(err.to_string()) } err @ BlockchainError::InvalidTransactionRequest(_) => { RpcError::invalid_params(err.to_string()) } err @ BlockchainError::RecoveryError(_) => { RpcError::invalid_params(err.to_string()) } BlockchainError::FilterNotFound => RpcError { code: ErrorCode::ServerError(-32000), message: "filter not found".into(), data: None, }, } .into(), } } } ================================================ FILE: crates/anvil/src/eth/fees.rs ================================================ use std::{ collections::BTreeMap, fmt, pin::Pin, sync::Arc, task::{Context, Poll}, }; use alloy_consensus::{BlockHeader, Header, Transaction}; use alloy_eips::{calc_next_block_base_fee, eip1559::BaseFeeParams, eip7840::BlobParams}; use alloy_primitives::B256; use futures::StreamExt; use parking_lot::{Mutex, RwLock}; use revm::{context_interface::block::BlobExcessGasAndPrice, primitives::hardfork::SpecId}; use crate::eth::{ backend::{info::StorageInfo, notifications::NewBlockNotifications}, error::BlockchainError, }; use foundry_primitives::FoundryNetwork; /// Maximum number of entries in the fee history cache pub const MAX_FEE_HISTORY_CACHE_SIZE: u64 = 2048u64; /// Initial base fee for EIP-1559 blocks. pub const INITIAL_BASE_FEE: u64 = 1_000_000_000; /// Initial default gas price for the first block pub const INITIAL_GAS_PRICE: u128 = 1_875_000_000; /// Bounds the amount the base fee can change between blocks. pub const BASE_FEE_CHANGE_DENOMINATOR: u128 = 8; /// Minimum suggested priority fee pub const MIN_SUGGESTED_PRIORITY_FEE: u128 = 1e9 as u128; /// Stores the fee related information #[derive(Clone, Debug)] pub struct FeeManager { /// Hardfork identifier spec_id: SpecId, /// The blob params that determine blob fees blob_params: Arc>, /// Tracks the base fee for the next block post London /// /// This value will be updated after a new block was mined base_fee: Arc>, /// Whether the minimum suggested priority fee is enforced is_min_priority_fee_enforced: bool, /// 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>, /// The base price to use Pre London /// /// This will be constant value unless changed manually gas_price: Arc>, elasticity: Arc>, /// Network-specific base fee params for EIP-1559 calculations base_fee_params: BaseFeeParams, } impl FeeManager { pub fn new( spec_id: SpecId, base_fee: u64, is_min_priority_fee_enforced: bool, gas_price: u128, blob_excess_gas_and_price: BlobExcessGasAndPrice, blob_params: BlobParams, base_fee_params: BaseFeeParams, ) -> Self { let elasticity = 1f64 / base_fee_params.elasticity_multiplier as f64; Self { spec_id, blob_params: Arc::new(RwLock::new(blob_params)), base_fee: Arc::new(RwLock::new(base_fee)), is_min_priority_fee_enforced, gas_price: Arc::new(RwLock::new(gas_price)), blob_excess_gas_and_price: Arc::new(RwLock::new(blob_excess_gas_and_price)), elasticity: Arc::new(RwLock::new(elasticity)), base_fee_params, } } /// Returns the base fee params used for EIP-1559 calculations pub fn base_fee_params(&self) -> BaseFeeParams { self.base_fee_params } pub fn elasticity(&self) -> f64 { *self.elasticity.read() } /// Returns true for post London pub fn is_eip1559(&self) -> bool { (self.spec_id as u8) >= (SpecId::LONDON as u8) } pub fn is_eip4844(&self) -> bool { (self.spec_id as u8) >= (SpecId::CANCUN as u8) } /// Calculates the current blob gas price pub fn blob_gas_price(&self) -> u128 { if self.is_eip4844() { self.base_fee_per_blob_gas() } else { 0 } } pub fn base_fee(&self) -> u64 { if self.is_eip1559() { *self.base_fee.read() } else { 0 } } pub fn is_min_priority_fee_enforced(&self) -> bool { self.is_min_priority_fee_enforced } /// Raw base gas price pub fn raw_gas_price(&self) -> u128 { *self.gas_price.read() } pub fn excess_blob_gas_and_price(&self) -> Option { if self.is_eip4844() { Some(*self.blob_excess_gas_and_price.read()) } else { None } } pub fn base_fee_per_blob_gas(&self) -> u128 { if self.is_eip4844() { self.blob_excess_gas_and_price.read().blob_gasprice } else { 0 } } /// Returns the current gas price pub fn set_gas_price(&self, price: u128) { let mut gas = self.gas_price.write(); *gas = price; } /// Returns the current base fee pub fn set_base_fee(&self, fee: u64) { trace!(target: "backend::fees", "updated base fee {:?}", fee); let mut base = self.base_fee.write(); *base = fee; } /// Sets the current blob excess gas and price pub fn set_blob_excess_gas_and_price(&self, blob_excess_gas_and_price: BlobExcessGasAndPrice) { trace!(target: "backend::fees", "updated blob base fee {:?}", blob_excess_gas_and_price); let mut base = self.blob_excess_gas_and_price.write(); *base = blob_excess_gas_and_price; } /// Calculates the base fee for the next block pub fn get_next_block_base_fee_per_gas( &self, gas_used: u64, gas_limit: u64, last_fee_per_gas: u64, ) -> u64 { // It's naturally impossible for base fee to be 0; // It means it was set by the user deliberately and therefore we treat it as a constant. // Therefore, we skip the base fee calculation altogether and we return 0. if self.base_fee() == 0 { return 0; } calc_next_block_base_fee(gas_used, gas_limit, last_fee_per_gas, self.base_fee_params) } /// Calculates the next block blob base fee. pub fn get_next_block_blob_base_fee_per_gas(&self) -> u128 { self.blob_params().calc_blob_fee(self.blob_excess_gas_and_price.read().excess_blob_gas) } /// Calculates the next block blob excess gas, using the provided parent blob excess gas and /// parent blob gas used pub fn get_next_block_blob_excess_gas(&self, blob_excess_gas: u64, blob_gas_used: u64) -> u64 { self.blob_params().next_block_excess_blob_gas_osaka( blob_excess_gas, blob_gas_used, self.base_fee(), ) } /// Configures the blob params pub fn set_blob_params(&self, blob_params: BlobParams) { *self.blob_params.write() = blob_params; } /// Returns the active [`BlobParams`] pub fn blob_params(&self) -> BlobParams { *self.blob_params.read() } } /// 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 cache: FeeHistoryCache, /// number of items to consider fee_history_limit: u64, /// a type that can fetch ethereum-storage data storage_info: StorageInfo, } impl FeeHistoryService { pub fn new( blob_params: BlobParams, new_blocks: NewBlockNotifications, cache: FeeHistoryCache, storage_info: StorageInfo, ) -> Self { Self { blob_params, new_blocks, cache, fee_history_limit: MAX_FEE_HISTORY_CACHE_SIZE, storage_info, } } /// Returns the configured history limit pub fn fee_history_limit(&self) -> u64 { self.fee_history_limit } /// Inserts a new cache entry for the given block pub(crate) fn insert_cache_entry_for_block(&self, hash: B256, header: &Header) { let (result, block_number) = self.create_cache_entry(hash, header); self.insert_cache_entry(result, block_number); } /// Create a new history entry for the block fn create_cache_entry( &self, hash: B256, header: &Header, ) -> (FeeHistoryCacheItem, Option) { // percentile list from 0.0 to 100.0 with a 0.5 resolution. // this will create 200 percentile points let reward_percentiles: Vec = { let mut percentile: f64 = 0.0; (0..=200) .map(|_| { let val = percentile; percentile += 0.5; val }) .collect() }; let mut block_number: Option = None; let base_fee = header.base_fee_per_gas.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(self.blob_params); let mut item = FeeHistoryCacheItem { base_fee: base_fee as u128, gas_used_ratio: 0f64, blob_gas_used_ratio: 0f64, rewards: Vec::new(), excess_blob_gas, base_fee_per_blob_gas, blob_gas_used, }; let current_block = self.storage_info.block(hash); let current_receipts = self.storage_info.receipts(hash); if let (Some(block), Some(receipts)) = (current_block, current_receipts) { block_number = Some(block.header.number()); let gas_used = block.header.gas_used() as f64; 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| { let max = self.blob_params.max_blob_gas_per_block() as f64; if max == 0.0 { 0.0 } else { g / max } }) .unwrap_or(0.0); // extract useful tx info (gas_used, effective_reward) let mut transactions: Vec<(_, _)> = receipts .iter() .enumerate() .map(|(i, receipt)| { let cumulative = receipt.cumulative_gas_used(); let prev_cumulative = if i > 0 { receipts[i - 1].cumulative_gas_used() } else { 0 }; let gas_used = cumulative - prev_cumulative; let effective_reward = block .body .transactions .get(i) .map(|tx| tx.as_ref().effective_tip_per_gas(base_fee).unwrap_or(0)) .unwrap_or(0); (gas_used, effective_reward) }) .collect(); // sort by effective reward asc transactions.sort_by_key(|(_, reward)| *reward); // calculate percentile rewards item.rewards = reward_percentiles .into_iter() .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().copied() { sum_gas += gas_used; if target_gas <= sum_gas { return Some(effective_reward); } } None }) .collect(); } else { item.rewards = vec![0; reward_percentiles.len()]; } (item, block_number) } fn insert_cache_entry(&self, item: FeeHistoryCacheItem, block_number: Option) { if let Some(block_number) = block_number { trace!(target: "fees", "insert new history item={:?} for {}", item, block_number); let mut cache = self.cache.lock(); cache.insert(block_number, item); // adhere to cache limit let pop_next = block_number.saturating_sub(self.fee_history_limit); let num_remove = (cache.len() as u64).saturating_sub(self.fee_history_limit); for num in 0..num_remove { let key = pop_next - num; cache.remove(&key); } } } } // An endless future that listens for new blocks and updates the cache impl Future for FeeHistoryService { type Output = (); fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { let pin = self.get_mut(); while let Poll::Ready(Some(notification)) = pin.new_blocks.poll_next_unpin(cx) { // add the imported block. pin.insert_cache_entry_for_block(notification.hash, notification.header.as_ref()); } Poll::Pending } } pub type FeeHistoryCache = Arc>>; /// A single item in the whole fee history cache #[derive(Clone, Debug)] pub struct FeeHistoryCacheItem { pub base_fee: u128, pub gas_used_ratio: f64, pub base_fee_per_blob_gas: Option, pub blob_gas_used_ratio: f64, pub excess_blob_gas: Option, pub blob_gas_used: Option, pub rewards: Vec, } #[derive(Clone, Default)] pub struct FeeDetails { pub gas_price: Option, pub max_fee_per_gas: Option, pub max_priority_fee_per_gas: Option, pub max_fee_per_blob_gas: Option, } impl FeeDetails { /// All values zero pub fn zero() -> Self { Self { gas_price: Some(0), max_fee_per_gas: Some(0), max_priority_fee_per_gas: Some(0), max_fee_per_blob_gas: None, } } /// If neither `gas_price` nor `max_fee_per_gas` is `Some`, this will set both to `0` pub fn or_zero_fees(self) -> Self { let Self { gas_price, max_fee_per_gas, max_priority_fee_per_gas, max_fee_per_blob_gas } = self; let no_fees = gas_price.is_none() && max_fee_per_gas.is_none(); let gas_price = if no_fees { Some(0) } else { gas_price }; let max_fee_per_gas = if no_fees { Some(0) } else { max_fee_per_gas }; let max_fee_per_blob_gas = if no_fees { None } else { max_fee_per_blob_gas }; Self { gas_price, max_fee_per_gas, max_priority_fee_per_gas, max_fee_per_blob_gas } } /// Turns this type into a tuple pub fn split(self) -> (Option, Option, Option, Option) { let Self { gas_price, max_fee_per_gas, max_priority_fee_per_gas, max_fee_per_blob_gas } = self; (gas_price, max_fee_per_gas, max_priority_fee_per_gas, max_fee_per_blob_gas) } /// Creates a new instance from the request's gas related values pub fn new( request_gas_price: Option, request_max_fee: Option, request_priority: Option, max_fee_per_blob_gas: Option, ) -> Result { match (request_gas_price, request_max_fee, request_priority, max_fee_per_blob_gas) { (gas_price, None, None, None) => { // Legacy request, all default to gas price. Ok(Self { gas_price, max_fee_per_gas: gas_price, max_priority_fee_per_gas: gas_price, max_fee_per_blob_gas: None, }) } (_, max_fee, max_priority, None) => { // eip-1559 // Ensure `max_priority_fee_per_gas` is less or equal to `max_fee_per_gas`. if let Some(max_priority) = max_priority { let max_fee = max_fee.unwrap_or_default(); if max_priority > max_fee { return Err(BlockchainError::InvalidFeeInput); } } Ok(Self { gas_price: max_fee, max_fee_per_gas: max_fee, max_priority_fee_per_gas: max_priority, max_fee_per_blob_gas: None, }) } (_, max_fee, max_priority, max_fee_per_blob_gas) => { // eip-1559 // Ensure `max_priority_fee_per_gas` is less or equal to `max_fee_per_gas`. if let Some(max_priority) = max_priority { let max_fee = max_fee.unwrap_or_default(); if max_priority > max_fee { return Err(BlockchainError::InvalidFeeInput); } } Ok(Self { gas_price: max_fee, max_fee_per_gas: max_fee, max_priority_fee_per_gas: max_priority, max_fee_per_blob_gas, }) } } } } impl fmt::Debug for FeeDetails { fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { write!(fmt, "Fees {{ ")?; write!(fmt, "gas_price: {:?}, ", self.gas_price)?; write!(fmt, "max_fee_per_gas: {:?}, ", self.max_fee_per_gas)?; write!(fmt, "max_priority_fee_per_gas: {:?}, ", self.max_priority_fee_per_gas)?; write!(fmt, "}}")?; Ok(()) } } ================================================ FILE: crates/anvil/src/eth/macros.rs ================================================ /// A `info!` helper macro that emits to the target, the node logger listens for macro_rules! node_info { ($($arg:tt)*) => { tracing::info!(target: $crate::logging::NODE_USER_LOG_TARGET, $($arg)*); }; } pub(crate) use node_info; ================================================ FILE: crates/anvil/src/eth/miner.rs ================================================ //! Mines transactions use crate::eth::pool::{Pool, transactions::PoolTransaction}; use alloy_primitives::TxHash; use futures::{ channel::mpsc::Receiver, stream::{Fuse, StreamExt}, task::AtomicWaker, }; use parking_lot::{RawRwLock, RwLock, lock_api::RwLockWriteGuard}; use std::{ fmt, sync::Arc, task::{Context, Poll, ready}, time::Duration, }; use tokio::time::{Interval, MissedTickBehavior}; pub struct Miner { /// The mode this miner currently operates in mode: Arc>, /// used for task wake up when the mining mode was forcefully changed /// /// This will register the task so we can manually wake it up if the mining mode was changed inner: Arc, /// Transactions included into the pool before any others are. /// Done once on startup. force_transactions: Option>>>, } impl Clone for Miner { fn clone(&self) -> Self { Self { mode: self.mode.clone(), inner: self.inner.clone(), force_transactions: self.force_transactions.clone(), } } } impl fmt::Debug for Miner { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("Miner") .field("mode", &self.mode) .field("force_transactions", &self.force_transactions.as_ref().map(|txs| txs.len())) .finish_non_exhaustive() } } impl Miner { /// Returns a new miner with that operates in the given `mode`. pub fn new(mode: MiningMode) -> Self { Self { mode: Arc::new(RwLock::new(mode)), inner: Default::default(), force_transactions: None, } } /// Provide transactions that will cause a block to be mined with transactions /// as soon as the miner is polled. /// Providing an empty list of transactions will cause the miner to mine an empty block assuming /// there are not other transactions in the pool. pub fn with_forced_transactions( mut self, force_transactions: Option>>, ) -> Self { self.force_transactions = force_transactions.map(|tx| tx.into_iter().map(Arc::new).collect()); self } /// Returns the write lock of the mining mode pub fn mode_write(&self) -> RwLockWriteGuard<'_, RawRwLock, MiningMode> { self.mode.write() } /// Returns `true` if auto mining is enabled pub fn is_auto_mine(&self) -> bool { let mode = self.mode.read(); matches!(*mode, MiningMode::Auto(_)) } pub fn get_interval(&self) -> Option { let mode = self.mode.read(); if let MiningMode::FixedBlockTime(ref mm) = *mode { return Some(mm.interval.period().as_secs()); } None } /// Sets the mining mode to operate in pub fn set_mining_mode(&self, mode: MiningMode) { let new_mode = format!("{mode:?}"); let mode = std::mem::replace(&mut *self.mode_write(), mode); trace!(target: "miner", "updated mining mode from {:?} to {}", mode, new_mode); self.inner.wake(); } /// polls the [Pool] and returns those transactions that should be put in a block according to /// the current mode. /// /// May return an empty list, if no transactions are ready. pub fn poll( &mut self, pool: &Arc>, cx: &mut Context<'_>, ) -> Poll>>> { self.inner.register(cx); let next = ready!(self.mode.write().poll(pool, cx)); if let Some(mut transactions) = self.force_transactions.take() { transactions.extend(next); Poll::Ready(transactions) } else { Poll::Ready(next) } } } /// A Mining mode that does nothing #[derive(Debug)] pub struct MinerInner { waker: AtomicWaker, } impl MinerInner { /// Call the waker again fn wake(&self) { self.waker.wake(); } fn register(&self, cx: &Context<'_>) { self.waker.register(cx.waker()); } } impl Default for MinerInner { fn default() -> Self { Self { waker: AtomicWaker::new() } } } /// Mode of operations for the `Miner` #[derive(Debug)] pub enum MiningMode { /// A miner that does nothing None, /// A miner that listens for new transactions that are ready. /// /// Either one transaction will be mined per block, or any number of transactions will be /// allowed Auto(ReadyTransactionMiner), /// A miner that constructs a new block every `interval` tick FixedBlockTime(FixedBlockTimeMiner), /// A miner that uses both Auto and FixedBlockTime Mixed(ReadyTransactionMiner, FixedBlockTimeMiner), } impl MiningMode { pub fn instant(max_transactions: usize, listener: Receiver) -> Self { Self::Auto(ReadyTransactionMiner { max_transactions, has_pending_txs: None, rx: listener.fuse(), }) } pub fn interval(duration: Duration) -> Self { Self::FixedBlockTime(FixedBlockTimeMiner::new(duration)) } pub fn mixed(max_transactions: usize, listener: Receiver, duration: Duration) -> Self { Self::Mixed( ReadyTransactionMiner { max_transactions, has_pending_txs: None, rx: listener.fuse() }, FixedBlockTimeMiner::new(duration), ) } /// polls the [Pool] and returns those transactions that should be put in a block, if any. pub fn poll( &mut self, pool: &Arc>, cx: &mut Context<'_>, ) -> Poll>>> { match self { Self::None => Poll::Pending, Self::Auto(miner) => miner.poll(pool, cx), Self::FixedBlockTime(miner) => miner.poll(pool, cx), Self::Mixed(auto, fixed) => { let auto_txs = auto.poll(pool, cx); let fixed_txs = fixed.poll(pool, cx); match (auto_txs, fixed_txs) { // Both auto and fixed transactions are ready, combine them (Poll::Ready(mut auto_txs), Poll::Ready(fixed_txs)) => { for tx in fixed_txs { // filter unique transactions if auto_txs.iter().any(|auto_tx| auto_tx.hash() == tx.hash()) { continue; } auto_txs.push(tx); } Poll::Ready(auto_txs) } // Only auto transactions are ready, return them (Poll::Ready(auto_txs), Poll::Pending) => Poll::Ready(auto_txs), // Only fixed transactions are ready or both are pending, // return fixed transactions or pending status (Poll::Pending, fixed_txs) => fixed_txs, } } } } } /// A miner that's supposed to create a new block every `interval`, mining all transactions that are /// ready at that time. /// /// The default blocktime is set to 6 seconds #[derive(Debug)] pub struct FixedBlockTimeMiner { /// The interval this fixed block time miner operates with interval: Interval, } impl FixedBlockTimeMiner { /// Creates a new instance with an interval of `duration` pub fn new(duration: Duration) -> Self { let start = tokio::time::Instant::now() + duration; let mut interval = tokio::time::interval_at(start, duration); // we use delay here, to ensure ticks are not shortened and to tick at multiples of interval // from when tick was called rather than from start interval.set_missed_tick_behavior(MissedTickBehavior::Delay); Self { interval } } fn poll( &mut self, pool: &Arc>, cx: &mut Context<'_>, ) -> Poll>>> { if self.interval.poll_tick(cx).is_ready() { // drain the pool return Poll::Ready(pool.ready_transactions().collect()); } Poll::Pending } } impl Default for FixedBlockTimeMiner { fn default() -> Self { Self::new(Duration::from_secs(6)) } } /// A miner that Listens for new ready transactions pub struct ReadyTransactionMiner { /// how many transactions to mine per block max_transactions: usize, /// stores whether there are pending transactions (if known) has_pending_txs: Option, /// Receives hashes of transactions that are ready rx: Fuse>, } impl ReadyTransactionMiner { fn poll( &mut self, pool: &Arc>, cx: &mut Context<'_>, ) -> Poll>>> { // always drain the notification stream so that we're woken up as soon as there's a new tx while let Poll::Ready(Some(_hash)) = self.rx.poll_next_unpin(cx) { self.has_pending_txs = Some(true); } if self.has_pending_txs == Some(false) { return Poll::Pending; } let transactions = pool.ready_transactions().take(self.max_transactions).collect::>(); // there are pending transactions if we didn't drain the pool self.has_pending_txs = Some(transactions.len() >= self.max_transactions); if transactions.is_empty() { return Poll::Pending; } Poll::Ready(transactions) } } impl fmt::Debug for ReadyTransactionMiner { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("ReadyTransactionMiner") .field("max_transactions", &self.max_transactions) .finish_non_exhaustive() } } ================================================ FILE: crates/anvil/src/eth/mod.rs ================================================ pub mod api; pub mod otterscan; pub mod sign; pub use api::EthApi; pub mod backend; pub mod error; pub mod fees; pub(crate) mod macros; pub mod miner; pub mod pool; pub mod util; ================================================ FILE: crates/anvil/src/eth/otterscan/api.rs ================================================ use crate::eth::{ EthApi, error::{BlockchainError, Result}, macros::node_info, }; use alloy_consensus::{BlockHeader, Transaction as TransactionTrait}; use alloy_network::{ AnyHeader, AnyRpcBlock, AnyRpcHeader, AnyRpcTransaction, AnyTxEnvelope, BlockResponse, ReceiptResponse, TransactionResponse, }; use alloy_primitives::{Address, B256, Bytes, U256}; use alloy_rpc_types::{ Block, BlockId, BlockNumberOrTag as BlockNumber, BlockTransactions, trace::{ otterscan::{ BlockDetails, ContractCreator, InternalOperation, OtsBlock, OtsBlockTransactions, OtsReceipt, OtsSlimBlock, OtsTransactionReceipt, TraceEntry, TransactionsWithReceipts, }, parity::{Action, CreateAction, CreateOutput, TraceOutput}, }, }; use foundry_primitives::FoundryNetwork; use futures::future::join_all; use itertools::Itertools; impl EthApi { /// Otterscan currently requires this endpoint, even though it's not part of the `ots_*`. /// Ref: /// /// As a faster alternative to `eth_getBlockByNumber` (by excluding uncle block /// information), which is not relevant in the context of an anvil node pub async fn erigon_get_header_by_number( &self, number: BlockNumber, ) -> Result> { node_info!("erigon_getHeaderByNumber"); self.backend.block_by_number(number).await } /// As per the latest Otterscan source code, at least version 8 is needed. /// Ref: pub async fn ots_get_api_level(&self) -> Result { node_info!("ots_getApiLevel"); // as required by current otterscan's source code Ok(8) } /// Trace internal ETH transfers, contracts creation (CREATE/CREATE2) and self-destructs for a /// certain transaction. pub async fn ots_get_internal_operations(&self, hash: B256) -> Result> { node_info!("ots_getInternalOperations"); self.backend .mined_transaction(hash) .map(|tx| tx.ots_internal_operations()) .ok_or_else(|| BlockchainError::DataUnavailable) } /// Check if an ETH address contains code at a certain block number. pub async fn ots_has_code(&self, address: Address, block_number: BlockNumber) -> Result { node_info!("ots_hasCode"); let block_id = Some(BlockId::Number(block_number)); Ok(!self.get_code(address, block_id).await?.is_empty()) } /// Trace a transaction and generate a trace call tree. /// Converts the list of traces for a transaction into the expected Otterscan format. /// /// Follows format specified in the [`ots_traceTransaction`](https://docs.otterscan.io/api-docs/ots-api#ots_tracetransaction) spec. pub async fn ots_trace_transaction(&self, hash: B256) -> Result> { node_info!("ots_traceTransaction"); let traces = self .backend .trace_transaction(hash) .await? .into_iter() .filter_map(|trace| TraceEntry::from_transaction_trace(&trace.trace)) .collect(); Ok(traces) } /// Given a transaction hash, returns its raw revert reason. pub async fn ots_get_transaction_error(&self, hash: B256) -> Result { node_info!("ots_getTransactionError"); if let Some(receipt) = self.backend.mined_transaction_receipt(hash) && !receipt.inner.as_ref().status() { return Ok(receipt.out.unwrap_or_default()); } Ok(Bytes::default()) } /// For simplicity purposes, we return the entire block instead of emptying the values that /// Otterscan doesn't want. This is the original purpose of the endpoint (to save bandwidth), /// but it doesn't seem necessary in the context of an anvil node pub async fn ots_get_block_details( &self, number: BlockNumber, ) -> Result> { node_info!("ots_getBlockDetails"); if let Some(block) = self.backend.block_by_number(number).await? { let ots_block = self.build_ots_block_details(block).await?; Ok(ots_block) } else { Err(BlockchainError::BlockNotFound) } } /// For simplicity purposes, we return the entire block instead of emptying the values that /// Otterscan doesn't want. This is the original purpose of the endpoint (to save bandwidth), /// but it doesn't seem necessary in the context of an anvil node pub async fn ots_get_block_details_by_hash( &self, hash: B256, ) -> Result> { node_info!("ots_getBlockDetailsByHash"); if let Some(block) = self.backend.block_by_hash(hash).await? { let ots_block = self.build_ots_block_details(block).await?; Ok(ots_block) } else { Err(BlockchainError::BlockNotFound) } } /// Gets paginated transaction data for a certain block. Return data is similar to /// eth_getBlockBy* + eth_getTransactionReceipt. pub async fn ots_get_block_transactions( &self, number: u64, page: usize, page_size: usize, ) -> Result> { node_info!("ots_getBlockTransactions"); match self.backend.block_by_number_full(number.into()).await? { Some(block) => self.build_ots_block_tx(block, page, page_size).await, None => Err(BlockchainError::BlockNotFound), } } /// Address history navigation. searches backwards from certain point in time. pub async fn ots_search_transactions_before( &self, address: Address, block_number: u64, page_size: usize, ) -> Result>> { node_info!("ots_searchTransactionsBefore"); let best = self.backend.best_number(); // we go from given block (defaulting to best) down to first block // considering only post-fork let from = if block_number == 0 { best } else { block_number - 1 }; let to = self.get_fork().map(|f| f.block_number() + 1).unwrap_or(1); let first_page = from >= best; let mut last_page = false; let mut res: Vec<_> = vec![]; for n in (to..=from).rev() { if let Some(traces) = self.backend.mined_parity_trace_block(n) { let hashes = traces .into_iter() .rev() .filter(|trace| trace.contains_address(address)) .filter_map(|trace| trace.transaction_hash) .unique(); if res.len() >= page_size { break; } res.extend(hashes); } if n == to { last_page = true; } } self.build_ots_search_transactions(res, first_page, last_page).await } /// Address history navigation. searches forward from certain point in time. pub async fn ots_search_transactions_after( &self, address: Address, block_number: u64, page_size: usize, ) -> Result>> { node_info!("ots_searchTransactionsAfter"); let best = self.backend.best_number(); // we go from the first post-fork block, up to the tip let first_block = self.get_fork().map(|f| f.block_number() + 1).unwrap_or(1); let from = if block_number == 0 { first_block } else { block_number + 1 }; let to = best; let mut first_page = from >= best; let mut last_page = false; let mut res: Vec<_> = vec![]; for n in from..=to { if n == first_block { last_page = true; } if let Some(traces) = self.backend.mined_parity_trace_block(n) { let hashes = traces .into_iter() .rev() .filter(|trace| trace.contains_address(address)) .filter_map(|trace| trace.transaction_hash) .unique(); if res.len() >= page_size { break; } res.extend(hashes); } if n == to { first_page = true; } } // Results are always sent in reverse chronological order, according to the Otterscan spec res.reverse(); self.build_ots_search_transactions(res, first_page, last_page).await } /// Given a sender address and a nonce, returns the tx hash or null if not found. It returns /// only the tx hash on success, you can use the standard eth_getTransactionByHash after that to /// get the full transaction data. pub async fn ots_get_transaction_by_sender_and_nonce( &self, address: Address, nonce: U256, ) -> Result> { node_info!("ots_getTransactionBySenderAndNonce"); let from = self.get_fork().map(|f| f.block_number() + 1).unwrap_or_default(); let to = self.backend.best_number(); for n in (from..=to).rev() { if let Some(txs) = self.backend.mined_transactions_by_block_number(n.into()).await { for tx in txs { if U256::from(tx.nonce()) == nonce && tx.from() == address { return Ok(Some(tx.tx_hash())); } } } } Ok(None) } /// Given an ETH contract address, returns the tx hash and the direct address who created the /// contract. pub async fn ots_get_contract_creator(&self, addr: Address) -> Result> { node_info!("ots_getContractCreator"); let from = self.get_fork().map(|f| f.block_number()).unwrap_or_default(); let to = self.backend.best_number(); // loop in reverse, since we want the latest deploy to the address for n in (from..=to).rev() { if let Some(traces) = self.backend.mined_parity_trace_block(n) { for trace in traces.into_iter().rev() { match (trace.trace.action, trace.trace.result) { ( Action::Create(CreateAction { from, .. }), Some(TraceOutput::Create(CreateOutput { address, .. })), ) if address == addr => { return Ok(Some(ContractCreator { hash: trace.transaction_hash.unwrap(), creator: from, })); } _ => {} } } } } Ok(None) } /// The response for ots_getBlockDetails includes an `issuance` object that requires computing /// the total gas spent in a given block. /// /// The only way to do this with the existing API is to explicitly fetch all receipts, to get /// their `gas_used`. This would be extremely inefficient in a real blockchain RPC, but we can /// get away with that in this context. /// /// The [original spec](https://docs.otterscan.io/api-docs/ots-api#ots_getblockdetails) /// also mentions we can hardcode `transactions` and `logsBloom` to an empty array to save /// bandwidth, because fields weren't intended to be used in the Otterscan UI at this point. /// /// This has two problems though: /// - It makes the endpoint too specific to Otterscan's implementation /// - It breaks the abstraction built in `OtsBlock` which computes `transaction_count` /// based on the existing list. /// /// Therefore we keep it simple by keeping the data in the response pub async fn build_ots_block_details( &self, block: AnyRpcBlock, ) -> Result>> { if block.transactions.is_uncle() { return Err(BlockchainError::DataUnavailable); } let receipts_futs = block .transactions .hashes() .map(|hash| async move { self.transaction_receipt(hash).await }); // fetch all receipts let receipts = join_all(receipts_futs) .await .into_iter() .map(|r| match r { Ok(Some(r)) => Ok(r), _ => Err(BlockchainError::DataUnavailable), }) .collect::>>()?; let total_fees = receipts.iter().fold(0, |acc, receipt| { acc + (receipt.gas_used() as u128) * receipt.effective_gas_price() }); let Block { header, uncles, transactions, withdrawals } = block.into_inner(); let block = OtsSlimBlock { header, uncles, transaction_count: transactions.len(), withdrawals }; Ok(BlockDetails { block, total_fees: U256::from(total_fees), // issuance has no meaningful value in anvil's backend. just default to 0 issuance: Default::default(), }) } /// Fetches all receipts for the blocks's transactions, as required by the /// [`ots_getBlockTransactions`] endpoint spec, and returns the final response object. /// /// [`ots_getBlockTransactions`]: https://docs.otterscan.io/api-docs/ots-api#ots_getblocktransactions pub async fn build_ots_block_tx( &self, mut block: AnyRpcBlock, page: usize, page_size: usize, ) -> Result> { if block.transactions.is_uncle() { return Err(BlockchainError::DataUnavailable); } block.transactions = match block.transactions() { BlockTransactions::Full(txs) => BlockTransactions::Full( txs.iter().skip(page * page_size).take(page_size).cloned().collect(), ), BlockTransactions::Hashes(txs) => BlockTransactions::Hashes( txs.iter().skip(page * page_size).take(page_size).copied().collect(), ), BlockTransactions::Uncle => unreachable!(), }; let receipt_futs = block.transactions.hashes().map(|hash| self.transaction_receipt(hash)); // Reuse timestamp from the block we already have let timestamp = block.header.timestamp(); let receipts = join_all(receipt_futs.map(|r| async move { if let Ok(Some(r)) = r.await { let receipt = r.as_ref().inner.clone().map_inner(OtsReceipt::from); Ok(OtsTransactionReceipt { receipt, timestamp: Some(timestamp) }) } else { Err(BlockchainError::BlockNotFound) } })) .await .into_iter() .collect::>>()?; let transaction_count = block.transactions().len(); let fullblock = OtsBlock { block: block.inner.clone(), transaction_count }; let ots_block_txs = OtsBlockTransactions { fullblock, receipts }; Ok(ots_block_txs) } pub async fn build_ots_search_transactions( &self, hashes: Vec, first_page: bool, last_page: bool, ) -> Result>> { let txs_futs = hashes.iter().map(|hash| async { self.transaction_by_hash(*hash).await }); let txs = join_all(txs_futs) .await .into_iter() .map(|t| match t { Ok(Some(t)) => Ok(t.into_inner()), _ => Err(BlockchainError::DataUnavailable), }) .collect::>>()?; let receipt_futs = hashes.iter().map(|hash| self.transaction_receipt(*hash)); let receipts = join_all(receipt_futs.map(|r| async { if let Ok(Some(r)) = r.await { // Try to get timestamp from receipt's other fields first (set by mined receipts), // fallback to block lookup for fork receipts that may not have it let timestamp = if let Some(ts) = r.block_timestamp() { ts } else { let block = self.block_by_number(r.block_number().unwrap().into()).await?; block.ok_or(BlockchainError::BlockNotFound)?.header.timestamp() }; let receipt = r.as_ref().inner.clone().map_inner(OtsReceipt::from); Ok(OtsTransactionReceipt { receipt, timestamp: Some(timestamp) }) } else { Err(BlockchainError::BlockNotFound) } })) .await .into_iter() .collect::>>()?; Ok(TransactionsWithReceipts { txs, receipts, first_page, last_page }) } } ================================================ FILE: crates/anvil/src/eth/otterscan/mod.rs ================================================ pub mod api; ================================================ FILE: crates/anvil/src/eth/pool/mod.rs ================================================ //! # Transaction Pool implementation //! //! The transaction pool is responsible for managing a set of transactions that can be included in //! upcoming blocks. //! //! The main task of the pool is to prepare an ordered list of transactions that are ready to be //! included in a new block. //! //! Each imported block can affect the validity of transactions already in the pool. //! The miner expects the most up-to-date transactions when attempting to create a new block. //! After being included in a block, a transaction should be removed from the pool, this process is //! called _pruning_ and due to separation of concerns is triggered externally. //! The pool essentially performs following services: //! * import transactions //! * order transactions //! * provide ordered set of transactions that are ready for inclusion //! * prune transactions //! //! Each transaction in the pool contains markers that it _provides_ or _requires_. This property is //! used to determine whether it can be included in a block (transaction is ready) or whether it //! still _requires_ other transactions to be mined first (transaction is pending). //! A transaction is associated with the nonce of the account it's sent from. A unique identifying //! marker for a transaction is therefore the pair `(nonce + account)`. An incoming transaction with //! a `nonce > nonce on chain` will _require_ `(nonce -1, account)` first, before it is ready to be //! included in a block. //! //! This implementation is adapted from use crate::{ eth::{ error::PoolError, pool::transactions::{ PendingPoolTransaction, PendingTransactions, PoolTransaction, ReadyTransactions, TransactionsIterator, TxMarker, }, }, mem::storage::MinedBlockOutcome, }; use alloy_consensus::Transaction; use alloy_primitives::{Address, TxHash}; use alloy_rpc_types::txpool::TxpoolStatus; use anvil_core::eth::transaction::PendingTransaction; use futures::channel::mpsc::{Receiver, Sender, channel}; use parking_lot::{Mutex, RwLock}; use std::{collections::VecDeque, fmt, sync::Arc}; pub mod transactions; /// Transaction pool that performs validation. pub struct Pool { /// processes all pending transactions inner: RwLock>, /// listeners for new ready transactions transaction_listener: Mutex>>, } impl Default for Pool { fn default() -> Self { Self { inner: RwLock::new(PoolInner::default()), transaction_listener: Default::default() } } } // == impl Pool == impl Pool { /// Returns an iterator that yields all transactions that are currently ready pub fn ready_transactions(&self) -> TransactionsIterator { self.inner.read().ready_transactions() } /// Returns all transactions that are not ready to be included in a block yet pub fn pending_transactions(&self) -> Vec>> { self.inner.read().pending_transactions.transactions().collect() } /// Returns the number of tx that are ready and queued for further execution pub fn txpool_status(&self) -> TxpoolStatus { // Note: naming differs here compared to geth's `TxpoolStatus` let pending: u64 = self.inner.read().ready_transactions.len().try_into().unwrap_or(0); let queued: u64 = self.inner.read().pending_transactions.len().try_into().unwrap_or(0); TxpoolStatus { pending, queued } } /// Adds a new transaction listener to the pool that gets notified about every new ready /// transaction pub fn add_ready_listener(&self) -> Receiver { const TX_LISTENER_BUFFER_SIZE: usize = 2048; let (tx, rx) = channel(TX_LISTENER_BUFFER_SIZE); self.transaction_listener.lock().push(tx); rx } /// Returns true if this pool already contains the transaction pub fn contains(&self, tx_hash: &TxHash) -> bool { self.inner.read().contains(tx_hash) } /// Removes all transactions from the pool pub fn clear(&self) { let mut pool = self.inner.write(); pool.clear(); } /// Remove the given transactions from the pool pub fn remove_invalid(&self, tx_hashes: Vec) -> Vec>> { self.inner.write().remove_invalid(tx_hashes) } /// Remove transactions by sender pub fn remove_transactions_by_address(&self, sender: Address) -> Vec>> { self.inner.write().remove_transactions_by_address(sender) } /// Removes a single transaction from the pool /// /// This is similar to `[Pool::remove_invalid()]` but for a single transaction. /// /// **Note**: this will also drop any transaction that depend on the `tx` pub fn drop_transaction(&self, tx: TxHash) -> Option>> { trace!(target: "txpool", "Dropping transaction: [{:?}]", tx); let removed = { let mut pool = self.inner.write(); pool.ready_transactions.remove_with_markers(vec![tx], None) }; trace!(target: "txpool", "Dropped transactions: {:?}", removed.iter().map(|tx| tx.hash()).collect::>()); let mut dropped = None; if !removed.is_empty() { dropped = removed.into_iter().find(|t| *t.pending_transaction.hash() == tx); } dropped } /// Notifies listeners if the transaction was added to the ready queue. fn notify_ready(&self, tx: &AddedTransaction) { if let AddedTransaction::Ready(ready) = tx { self.notify_listener(ready.hash); for promoted in ready.promoted.iter().copied() { self.notify_listener(promoted); } } } /// notifies all listeners about the transaction fn notify_listener(&self, hash: TxHash) { let mut listener = self.transaction_listener.lock(); // this is basically a retain but with mut reference for n in (0..listener.len()).rev() { let mut listener_tx = listener.swap_remove(n); let retain = match listener_tx.try_send(hash) { Ok(()) => true, Err(e) => { if e.is_full() { warn!( target: "txpool", "[{:?}] Failed to send tx notification because channel is full", hash, ); true } else { false } } }; if retain { listener.push(listener_tx) } } } } impl Pool { /// Returns the _pending_ transaction for that `hash` if it exists in the mempool pub fn get_transaction(&self, hash: TxHash) -> Option> { self.inner.read().get_transaction(hash) } } impl Pool { /// Invoked when a set of transactions ([Self::ready_transactions()]) was executed. /// /// This will remove the transactions from the pool. pub fn on_mined_block(&self, outcome: MinedBlockOutcome) -> PruneResult { let MinedBlockOutcome { block_number, included, invalid } = outcome; // remove invalid transactions from the pool self.remove_invalid(invalid.into_iter().map(|tx| tx.hash()).collect()); // prune all the markers the mined transactions provide let res = self .prune_markers(block_number, included.into_iter().flat_map(|tx| tx.provides.clone())); trace!(target: "txpool", "pruned transaction markers {:?}", res); res } /// Removes ready transactions for the given iterator of identifying markers. /// /// For each marker we can remove transactions in the pool that either provide the marker /// directly or are a dependency of the transaction associated with that marker. pub fn prune_markers( &self, block_number: u64, markers: impl IntoIterator, ) -> PruneResult { debug!(target: "txpool", ?block_number, "pruning transactions"); let res = self.inner.write().prune_markers(markers); for tx in &res.promoted { self.notify_ready(tx); } res } /// Adds a new transaction to the pool pub fn add_transaction( &self, tx: PoolTransaction, ) -> Result, PoolError> { let added = self.inner.write().add_transaction(tx)?; self.notify_ready(&added); Ok(added) } } /// A Transaction Pool /// /// Contains all transactions that are ready to be executed #[derive(Debug)] struct PoolInner { ready_transactions: ReadyTransactions, pending_transactions: PendingTransactions, } impl Default for PoolInner { fn default() -> Self { Self { ready_transactions: Default::default(), pending_transactions: Default::default() } } } // == impl PoolInner == impl PoolInner { /// Returns an iterator over transactions that are ready. fn ready_transactions(&self) -> TransactionsIterator { self.ready_transactions.get_transactions() } /// Clears fn clear(&mut self) { self.ready_transactions.clear(); self.pending_transactions.clear(); } /// Returns an iterator over all transactions in the pool filtered by the sender pub fn transactions_by_sender( &self, sender: Address, ) -> impl Iterator>> + '_ { let pending_txs = self .pending_transactions .transactions() .filter(move |tx| tx.pending_transaction.sender().eq(&sender)); let ready_txs = self .ready_transactions .get_transactions() .filter(move |tx| tx.pending_transaction.sender().eq(&sender)); pending_txs.chain(ready_txs) } /// Returns true if this pool already contains the transaction fn contains(&self, tx_hash: &TxHash) -> bool { self.pending_transactions.contains(tx_hash) || self.ready_transactions.contains(tx_hash) } /// Remove the given transactions from the pool fn remove_invalid(&mut self, tx_hashes: Vec) -> Vec>> { // early exit in case there is no invalid transactions. if tx_hashes.is_empty() { return vec![]; } trace!(target: "txpool", "Removing invalid transactions: {:?}", tx_hashes); let mut removed = self.ready_transactions.remove_with_markers(tx_hashes.clone(), None); removed.extend(self.pending_transactions.remove(tx_hashes)); trace!(target: "txpool", "Removed invalid transactions: {:?}", removed.iter().map(|tx| tx.hash()).collect::>()); removed } /// Remove transactions by sender address fn remove_transactions_by_address(&mut self, sender: Address) -> Vec>> { let tx_hashes = self.transactions_by_sender(sender).map(move |tx| tx.hash()).collect::>(); if tx_hashes.is_empty() { return vec![]; } trace!(target: "txpool", "Removing transactions: {:?}", tx_hashes); let mut removed = self.ready_transactions.remove_with_markers(tx_hashes.clone(), None); removed.extend(self.pending_transactions.remove(tx_hashes)); trace!(target: "txpool", "Removed transactions: {:?}", removed.iter().map(|tx| tx.hash()).collect::>()); removed } } impl PoolInner { /// checks both pools for the matching transaction /// /// Returns `None` if the transaction does not exist in the pool fn get_transaction(&self, hash: TxHash) -> Option> { if let Some(pending) = self.pending_transactions.get(&hash) { return Some(pending.transaction.pending_transaction.clone()); } Some( self.ready_transactions.get(&hash)?.transaction.transaction.pending_transaction.clone(), ) } } impl PoolInner { fn add_transaction( &mut self, tx: PoolTransaction, ) -> Result, PoolError> { if self.contains(&tx.hash()) { warn!(target: "txpool", "[{:?}] Already imported", tx.hash()); return Err(PoolError::AlreadyImported(tx.hash())); } let tx = PendingPoolTransaction::new(tx, self.ready_transactions.provided_markers()); trace!(target: "txpool", "[{:?}] ready={}", tx.transaction.hash(), tx.is_ready()); // If all markers are not satisfied import to future if !tx.is_ready() { let hash = tx.transaction.hash(); self.pending_transactions.add_transaction(tx)?; return Ok(AddedTransaction::Pending { hash }); } self.add_ready_transaction(tx) } /// Adds the transaction to the ready queue fn add_ready_transaction( &mut self, tx: PendingPoolTransaction, ) -> Result, PoolError> { let hash = tx.transaction.hash(); trace!(target: "txpool", "adding ready transaction [{:?}]", hash); let mut ready = ReadyTransaction::new(hash); let mut tx_queue = VecDeque::from([tx]); // tracks whether we're processing the given `tx` let mut is_new_tx = true; // take first transaction from the list while let Some(current_tx) = tx_queue.pop_front() { // also add the transaction that the current transaction unlocks tx_queue.extend( self.pending_transactions.mark_and_unlock(¤t_tx.transaction.provides), ); let current_hash = current_tx.transaction.hash(); // try to add the transaction to the ready pool match self.ready_transactions.add_transaction(current_tx) { Ok(replaced_transactions) => { if !is_new_tx { ready.promoted.push(current_hash); } // tx removed from ready pool ready.removed.extend(replaced_transactions); } Err(err) => { // failed to add transaction if is_new_tx { debug!(target: "txpool", "[{:?}] Failed to add tx: {:?}", current_hash, err); return Err(err); } else { ready.discarded.push(current_hash); } } } is_new_tx = false; } // check for a cycle where importing a transaction resulted in pending transactions to be // added while removing current transaction. in which case we move this transaction back to // the pending queue if ready.removed.iter().any(|tx| *tx.hash() == hash) { self.ready_transactions.clear_transactions(&ready.promoted); return Err(PoolError::CyclicTransaction); } Ok(AddedTransaction::Ready(ready)) } /// Prunes the transactions that provide the given markers /// /// This will effectively remove those transactions that satisfy the markers and transactions /// from the pending queue might get promoted to if the markers unlock them. pub fn prune_markers(&mut self, markers: impl IntoIterator) -> PruneResult { let mut imports = vec![]; let mut pruned = vec![]; for marker in markers { // mark as satisfied and store the transactions that got unlocked imports.extend(self.pending_transactions.mark_and_unlock(Some(&marker))); // prune transactions pruned.extend(self.ready_transactions.prune_tags(marker.clone())); } let mut promoted = vec![]; let mut failed = vec![]; for tx in imports { let hash = tx.transaction.hash(); match self.add_ready_transaction(tx) { Ok(res) => promoted.push(res), Err(e) => { warn!(target: "txpool", "Failed to promote tx [{:?}] : {:?}", hash, e); failed.push(hash) } } } PruneResult { pruned, failed, promoted } } } /// Represents the outcome of a prune pub struct PruneResult { /// a list of added transactions that a pruned marker satisfied pub promoted: Vec>, /// all transactions that failed to be promoted and now are discarded pub failed: Vec, /// all transactions that were pruned from the ready pool pub pruned: Vec>>, } impl fmt::Debug for PruneResult { fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { write!(fmt, "PruneResult {{ ")?; write!( fmt, "promoted: {:?}, ", self.promoted.iter().map(|tx| *tx.hash()).collect::>() )?; write!(fmt, "failed: {:?}, ", self.failed)?; write!( fmt, "pruned: {:?}, ", self.pruned.iter().map(|tx| *tx.pending_transaction.hash()).collect::>() )?; write!(fmt, "}}")?; Ok(()) } } #[derive(Clone, Debug)] pub struct ReadyTransaction { /// the hash of the submitted transaction hash: TxHash, /// transactions promoted to the ready queue promoted: Vec, /// transaction that failed and became discarded discarded: Vec, /// Transactions removed from the Ready pool removed: Vec>>, } impl ReadyTransaction { pub fn new(hash: TxHash) -> Self { Self { hash, promoted: Default::default(), discarded: Default::default(), removed: Default::default(), } } } #[derive(Clone, Debug)] pub enum AddedTransaction { /// transaction was successfully added and being processed Ready(ReadyTransaction), /// Transaction was successfully added but not yet queued for processing Pending { /// the hash of the submitted transaction hash: TxHash, }, } impl AddedTransaction { pub fn hash(&self) -> &TxHash { match self { Self::Ready(tx) => &tx.hash, Self::Pending { hash } => hash, } } } ================================================ FILE: crates/anvil/src/eth/pool/transactions.rs ================================================ use crate::eth::{error::PoolError, util::hex_fmt_many}; use alloy_consensus::{Transaction, Typed2718}; use alloy_network::AnyRpcTransaction; use alloy_primitives::{ Address, TxHash, map::{HashMap, HashSet}, }; use anvil_core::eth::transaction::PendingTransaction; use foundry_primitives::FoundryTxEnvelope; use parking_lot::RwLock; use std::{cmp::Ordering, collections::BTreeSet, fmt, str::FromStr, sync::Arc, time::Instant}; /// A unique identifying marker for a transaction pub type TxMarker = Vec; /// Result type for replaced transactions: the replaced pool transactions and the hashes they /// unlock. type ReplacedTransactions = (Vec>>, Vec); /// creates an unique identifier for aan (`nonce` + `Address`) combo pub fn to_marker(nonce: u64, from: Address) -> TxMarker { let mut data = [0u8; 28]; data[..8].copy_from_slice(&nonce.to_le_bytes()[..]); data[8..].copy_from_slice(&from.0[..]); data.to_vec() } /// Modes that determine the transaction ordering of the mempool /// /// This type controls the transaction order via the priority metric of a transaction #[derive(Clone, Copy, Debug, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize)] pub enum TransactionOrder { /// Keep the pool transaction transactions sorted in the order they arrive. /// /// This will essentially assign every transaction the exact priority so the order is /// determined by their internal id Fifo, /// This means that it prioritizes transactions based on the fees paid to the miner. #[default] Fees, } impl TransactionOrder { /// Returns the priority of the transactions pub fn priority(&self, tx: &T) -> TransactionPriority { match self { Self::Fifo => TransactionPriority::default(), Self::Fees => TransactionPriority(tx.max_fee_per_gas()), } } } impl FromStr for TransactionOrder { type Err = String; fn from_str(s: &str) -> Result { let s = s.to_lowercase(); let order = match s.as_str() { "fees" => Self::Fees, "fifo" => Self::Fifo, _ => return Err(format!("Unknown TransactionOrder: `{s}`")), }; Ok(order) } } /// Metric value for the priority of a transaction. /// /// The `TransactionPriority` determines the ordering of two transactions that have all their /// markers satisfied. #[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord)] pub struct TransactionPriority(pub u128); /// Internal Transaction type #[derive(Clone, PartialEq, Eq)] pub struct PoolTransaction { /// the pending eth transaction pub pending_transaction: PendingTransaction, /// Markers required by the transaction pub requires: Vec, /// Markers that this transaction provides pub provides: Vec, /// priority of the transaction pub priority: TransactionPriority, } // == impl PoolTransaction == impl PoolTransaction { pub fn new(transaction: PendingTransaction) -> Self { Self { pending_transaction: transaction, requires: vec![], provides: vec![], priority: TransactionPriority(0), } } /// Returns the hash of this transaction pub fn hash(&self) -> TxHash { *self.pending_transaction.hash() } } impl PoolTransaction { /// Returns the max fee per gas of this transaction pub fn max_fee_per_gas(&self) -> u128 { self.pending_transaction.transaction.max_fee_per_gas() } } impl PoolTransaction { /// Returns the type of the transaction pub fn tx_type(&self) -> u8 { self.pending_transaction.transaction.ty() } } impl fmt::Debug for PoolTransaction { fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { write!(fmt, "Transaction {{ ")?; write!(fmt, "hash: {:?}, ", &self.pending_transaction.hash())?; write!(fmt, "requires: [{}], ", hex_fmt_many(self.requires.iter()))?; write!(fmt, "provides: [{}], ", hex_fmt_many(self.provides.iter()))?; write!(fmt, "raw tx: {:?}", &self.pending_transaction)?; write!(fmt, "}}")?; Ok(()) } } impl TryFrom for PoolTransaction { type Error = eyre::Error; fn try_from(value: AnyRpcTransaction) -> Result { let typed_transaction = FoundryTxEnvelope::try_from(value)?; let pending_transaction = PendingTransaction::new(typed_transaction)?; Ok(Self { pending_transaction, requires: vec![], provides: vec![], priority: TransactionPriority(0), }) } } /// A waiting pool of transaction that are pending, but not yet ready to be included in a new block. /// /// Keeps a set of transactions that are waiting for other transactions #[derive(Clone, Debug)] pub struct PendingTransactions { /// markers that aren't yet provided by any transaction required_markers: HashMap>, /// mapping of the markers of a transaction to the hash of the transaction waiting_markers: HashMap, TxHash>, /// the transactions that are not ready yet are waiting for another tx to finish waiting_queue: HashMap>, } impl Default for PendingTransactions { fn default() -> Self { Self { required_markers: Default::default(), waiting_markers: Default::default(), waiting_queue: Default::default(), } } } impl PendingTransactions { /// Returns the number of transactions that are currently waiting pub fn len(&self) -> usize { self.waiting_queue.len() } pub fn is_empty(&self) -> bool { self.waiting_queue.is_empty() } /// Clears internal state pub fn clear(&mut self) { self.required_markers.clear(); self.waiting_markers.clear(); self.waiting_queue.clear(); } /// Returns an iterator over all transactions in the waiting pool pub fn transactions(&self) -> impl Iterator>> + '_ { self.waiting_queue.values().map(|tx| tx.transaction.clone()) } /// Returns true if given transaction is part of the queue pub fn contains(&self, hash: &TxHash) -> bool { self.waiting_queue.contains_key(hash) } /// Returns the transaction for the hash if it's pending pub fn get(&self, hash: &TxHash) -> Option<&PendingPoolTransaction> { self.waiting_queue.get(hash) } /// This will check off the markers of pending transactions. /// /// Returns the those transactions that become unlocked (all markers checked) and can be moved /// to the ready queue. pub fn mark_and_unlock( &mut self, markers: impl IntoIterator>, ) -> Vec> { let mut unlocked_ready = Vec::new(); for mark in markers { let mark = mark.as_ref(); if let Some(tx_hashes) = self.required_markers.remove(mark) { for hash in tx_hashes { let tx = self.waiting_queue.get_mut(&hash).expect("tx is included;"); tx.mark(mark); if tx.is_ready() { let tx = self.waiting_queue.remove(&hash).expect("tx is included;"); self.waiting_markers.remove(&tx.transaction.provides); unlocked_ready.push(tx); } } } } unlocked_ready } /// Removes the transactions associated with the given hashes /// /// Returns all removed transactions. pub fn remove(&mut self, hashes: Vec) -> Vec>> { let mut removed = vec![]; for hash in hashes { if let Some(waiting_tx) = self.waiting_queue.remove(&hash) { self.waiting_markers.remove(&waiting_tx.transaction.provides); for marker in waiting_tx.missing_markers { let remove = if let Some(required) = self.required_markers.get_mut(&marker) { required.remove(&hash); required.is_empty() } else { false }; if remove { self.required_markers.remove(&marker); } } removed.push(waiting_tx.transaction) } } removed } } impl PendingTransactions { /// Adds a transaction to Pending queue of transactions pub fn add_transaction(&mut self, tx: PendingPoolTransaction) -> Result<(), PoolError> { assert!(!tx.is_ready(), "transaction must not be ready"); assert!( !self.waiting_queue.contains_key(&tx.transaction.hash()), "transaction is already added" ); if let Some(replace) = self .waiting_markers .get(&tx.transaction.provides) .and_then(|hash| self.waiting_queue.get(hash)) { // check if underpriced if tx.transaction.max_fee_per_gas() < replace.transaction.max_fee_per_gas() { warn!(target: "txpool", "pending replacement transaction underpriced [{:?}]", tx.transaction.hash()); return Err(PoolError::ReplacementUnderpriced(tx.transaction.hash())); } } // add all missing markers for marker in &tx.missing_markers { self.required_markers.entry(marker.clone()).or_default().insert(tx.transaction.hash()); } // also track identifying markers self.waiting_markers.insert(tx.transaction.provides.clone(), tx.transaction.hash()); // add tx to the queue self.waiting_queue.insert(tx.transaction.hash(), tx); Ok(()) } } /// A transaction in the pool #[derive(Clone)] pub struct PendingPoolTransaction { pub transaction: Arc>, /// markers required and have not been satisfied yet by other transactions in the pool pub missing_markers: HashSet, /// timestamp when the tx was added pub added_at: Instant, } impl PendingPoolTransaction { /// Creates a new `PendingPoolTransaction`. /// /// Determines the markers that are still missing before this transaction can be moved to the /// ready queue. pub fn new(transaction: PoolTransaction, provided: &HashMap) -> Self { let missing_markers = transaction .requires .iter() .filter(|marker| { // is true if the marker is already satisfied either via transaction in the pool !provided.contains_key(&**marker) }) .cloned() .collect(); Self { transaction: Arc::new(transaction), missing_markers, added_at: Instant::now() } } /// Removes the required marker pub fn mark(&mut self, marker: &TxMarker) { self.missing_markers.remove(marker); } /// Returns true if transaction has all requirements satisfied. pub fn is_ready(&self) -> bool { self.missing_markers.is_empty() } } impl fmt::Debug for PendingPoolTransaction { fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { write!(fmt, "PendingTransaction {{ ")?; write!(fmt, "added_at: {:?}, ", self.added_at)?; write!(fmt, "tx: {:?}, ", self.transaction)?; write!(fmt, "missing_markers: {{{}}}", hex_fmt_many(self.missing_markers.iter()))?; write!(fmt, "}}") } } pub struct TransactionsIterator { all: HashMap>, awaiting: HashMap)>, independent: BTreeSet>, _invalid: HashSet, } impl TransactionsIterator { /// Depending on number of satisfied requirements insert given ref /// either to awaiting set or to best set. fn independent_or_awaiting(&mut self, satisfied: usize, tx_ref: PoolTransactionRef) { if satisfied >= tx_ref.transaction.requires.len() { // If we have satisfied all deps insert to best self.independent.insert(tx_ref); } else { // otherwise we're still awaiting for some deps self.awaiting.insert(tx_ref.transaction.hash(), (satisfied, tx_ref)); } } } impl Iterator for TransactionsIterator { type Item = Arc>; fn next(&mut self) -> Option { loop { let best = self.independent.iter().next_back()?.clone(); let best = self.independent.take(&best)?; let hash = best.transaction.hash(); let ready = if let Some(ready) = self.all.get(&hash).cloned() { ready } else { continue }; // Insert transactions that just got unlocked. for hash in &ready.unlocks { // first check local awaiting transactions let res = if let Some((mut satisfied, tx_ref)) = self.awaiting.remove(hash) { satisfied += 1; Some((satisfied, tx_ref)) // then get from the pool } else { self.all .get(hash) .map(|next| (next.requires_offset + 1, next.transaction.clone())) }; if let Some((satisfied, tx_ref)) = res { self.independent_or_awaiting(satisfied, tx_ref) } } return Some(best.transaction); } } } /// transactions that are ready to be included in a block. #[derive(Clone, Debug)] pub struct ReadyTransactions { /// keeps track of transactions inserted in the pool /// /// this way we can determine when transactions where submitted to the pool id: u64, /// markers that are provided by `ReadyTransaction`s provided_markers: HashMap, /// transactions that are ready ready_tx: Arc>>>, /// independent transactions that can be included directly and don't require other transactions /// Sorted by their id independent_transactions: BTreeSet>, } impl Default for ReadyTransactions { fn default() -> Self { Self { id: 0, provided_markers: Default::default(), ready_tx: Default::default(), independent_transactions: Default::default(), } } } impl ReadyTransactions { /// Returns an iterator over all transactions pub fn get_transactions(&self) -> TransactionsIterator { TransactionsIterator { all: self.ready_tx.read().clone(), independent: self.independent_transactions.clone(), awaiting: Default::default(), _invalid: Default::default(), } } /// Clears the internal state pub fn clear(&mut self) { self.provided_markers.clear(); self.ready_tx.write().clear(); self.independent_transactions.clear(); } /// Returns true if the transaction is part of the queue. pub fn contains(&self, hash: &TxHash) -> bool { self.ready_tx.read().contains_key(hash) } /// Returns the number of ready transactions without cloning the snapshot pub fn len(&self) -> usize { self.ready_tx.read().len() } /// Returns true if there are no ready transactions pub fn is_empty(&self) -> bool { self.ready_tx.read().is_empty() } /// Returns the transaction for the hash if it's in the ready pool but not yet mined pub fn get(&self, hash: &TxHash) -> Option> { self.ready_tx.read().get(hash).cloned() } pub fn provided_markers(&self) -> &HashMap { &self.provided_markers } fn next_id(&mut self) -> u64 { let id = self.id; self.id = self.id.wrapping_add(1); id } /// Removes the transactions from the ready queue and returns the removed transactions. /// This will also remove all transactions that depend on those. pub fn clear_transactions(&mut self, tx_hashes: &[TxHash]) -> Vec>> { self.remove_with_markers(tx_hashes.to_vec(), None) } /// Removes the transactions that provide the marker /// /// This will also remove all transactions that lead to the transaction that provides the /// marker. pub fn prune_tags(&mut self, marker: TxMarker) -> Vec>> { let mut removed_tx = vec![]; // the markers to remove let mut remove = vec![marker]; while let Some(marker) = remove.pop() { let res = self .provided_markers .remove(&marker) .and_then(|hash| self.ready_tx.write().remove(&hash)); if let Some(tx) = res { let unlocks = tx.unlocks; self.independent_transactions.remove(&tx.transaction); let tx = tx.transaction.transaction; // also prune previous transactions { let hash = tx.hash(); let mut ready = self.ready_tx.write(); let mut previous_markers = |marker| -> Option> { let prev_hash = self.provided_markers.get(marker)?; let tx2 = ready.get_mut(prev_hash)?; // remove hash if let Some(idx) = tx2.unlocks.iter().position(|i| i == &hash) { tx2.unlocks.swap_remove(idx); } if tx2.unlocks.is_empty() { Some(tx2.transaction.transaction.provides.clone()) } else { None } }; // find previous transactions for marker in &tx.requires { if let Some(mut tags_to_remove) = previous_markers(marker) { remove.append(&mut tags_to_remove); } } } // add the transactions that just got unlocked to independent set for hash in unlocks { if let Some(tx) = self.ready_tx.write().get_mut(&hash) { tx.requires_offset += 1; if tx.requires_offset == tx.transaction.transaction.requires.len() { self.independent_transactions.insert(tx.transaction.clone()); } } } // finally, remove the markers that this transaction provides let current_marker = ▮ for marker in &tx.provides { let removed = self.provided_markers.remove(marker); assert_eq!( removed, if current_marker == marker { None } else { Some(tx.hash()) }, "The pool contains exactly one transaction providing given tag; the removed transaction claims to provide that tag, so it has to be mapped to it's hash; qed" ); } removed_tx.push(tx); } } removed_tx } /// Removes transactions and those that depend on them and satisfy at least one marker in the /// given filter set. pub fn remove_with_markers( &mut self, mut tx_hashes: Vec, marker_filter: Option>, ) -> Vec>> { let mut removed = Vec::new(); let mut ready = self.ready_tx.write(); while let Some(hash) = tx_hashes.pop() { if let Some(mut tx) = ready.remove(&hash) { let invalidated = tx.transaction.transaction.provides.iter().filter(|mark| { marker_filter.as_ref().map(|filter| !filter.contains(&**mark)).unwrap_or(true) }); let mut removed_some_marks = false; // remove entries from provided_markers for mark in invalidated { removed_some_marks = true; self.provided_markers.remove(mark); } // remove from unlocks for mark in &tx.transaction.transaction.requires { if let Some(provider_hash) = self.provided_markers.get(mark) && let Some(provider_tx) = ready.get_mut(provider_hash) && let Some(idx) = provider_tx.unlocks.iter().position(|i| i == &hash) { provider_tx.unlocks.swap_remove(idx); } } // remove from the independent set self.independent_transactions.remove(&tx.transaction); if removed_some_marks { // remove all transactions that the current one unlocks tx_hashes.append(&mut tx.unlocks); } // remove transaction removed.push(tx.transaction.transaction); } } removed } } impl ReadyTransactions { /// Adds a new transactions to the ready queue. /// /// # Panics /// /// If the pending transaction is not ready ([`PendingPoolTransaction::is_ready`]) /// or the transaction is already included. pub fn add_transaction( &mut self, tx: PendingPoolTransaction, ) -> Result>>, PoolError> { assert!(tx.is_ready(), "transaction must be ready",); assert!( !self.ready_tx.read().contains_key(&tx.transaction.hash()), "transaction already included" ); let (replaced_tx, unlocks) = self.replaced_transactions(&tx.transaction)?; let id = self.next_id(); let hash = tx.transaction.hash(); let mut independent = true; let mut requires_offset = 0; let mut ready = self.ready_tx.write(); // Add links to transactions that unlock the current one for mark in &tx.transaction.requires { // Check if the transaction that satisfies the mark is still in the queue. if let Some(other) = self.provided_markers.get(mark) { let tx = ready.get_mut(other).expect("hash included;"); tx.unlocks.push(hash); // tx still depends on other tx independent = false; } else { requires_offset += 1; } } // update markers for mark in tx.transaction.provides.iter().cloned() { self.provided_markers.insert(mark, hash); } let transaction = PoolTransactionRef { id, transaction: tx.transaction }; // add to the independent set if independent { self.independent_transactions.insert(transaction.clone()); } // insert to ready queue ready.insert(hash, ReadyTransaction { transaction, unlocks, requires_offset }); Ok(replaced_tx) } /// Removes and returns those transactions that got replaced by the `tx` fn replaced_transactions( &mut self, tx: &PoolTransaction, ) -> Result, PoolError> { // check if we are replacing transactions let remove_hashes: HashSet<_> = tx.provides.iter().filter_map(|mark| self.provided_markers.get(mark)).collect(); // early exit if we are not replacing anything. if remove_hashes.is_empty() { return Ok((Vec::new(), Vec::new())); } // check if we're replacing the same transaction and if it can be replaced let mut unlocked_tx = Vec::new(); { // construct a list of unlocked transactions // also check for transactions that shouldn't be replaced because underpriced let ready = self.ready_tx.read(); for to_remove in remove_hashes.iter().filter_map(|hash| ready.get(*hash)) { // if we're attempting to replace a transaction that provides the exact same markers // (addr + nonce) then we check for gas price if to_remove.provides() == tx.provides { // check if underpriced if tx.pending_transaction.transaction.max_fee_per_gas() <= to_remove.max_fee_per_gas() { warn!(target: "txpool", "ready replacement transaction underpriced [{:?}]", tx.hash()); return Err(PoolError::ReplacementUnderpriced(tx.hash())); } else { trace!(target: "txpool", "replacing ready transaction [{:?}] with higher gas price [{:?}]", to_remove.transaction.transaction.hash(), tx.hash()); } } unlocked_tx.extend(to_remove.unlocks.iter().copied()) } } let remove_hashes = remove_hashes.into_iter().copied().collect::>(); let new_provides = tx.provides.iter().cloned().collect::>(); let removed_tx = self.remove_with_markers(remove_hashes, Some(new_provides)); Ok((removed_tx, unlocked_tx)) } } /// A reference to a transaction in the pool #[derive(Debug)] pub struct PoolTransactionRef { /// actual transaction pub transaction: Arc>, /// identifier used to internally compare the transaction in the pool pub id: u64, } impl Clone for PoolTransactionRef { fn clone(&self) -> Self { Self { transaction: Arc::clone(&self.transaction), id: self.id } } } impl Eq for PoolTransactionRef {} impl PartialEq for PoolTransactionRef { fn eq(&self, other: &Self) -> bool { self.cmp(other) == Ordering::Equal } } impl PartialOrd for PoolTransactionRef { fn partial_cmp(&self, other: &Self) -> Option { Some(self.cmp(other)) } } impl Ord for PoolTransactionRef { fn cmp(&self, other: &Self) -> Ordering { self.transaction .priority .cmp(&other.transaction.priority) .then_with(|| other.id.cmp(&self.id)) } } #[derive(Debug)] pub struct ReadyTransaction { /// ref to the actual transaction pub transaction: PoolTransactionRef, /// tracks the transactions that get unlocked by this transaction pub unlocks: Vec, /// amount of required markers that are inherently provided pub requires_offset: usize, } impl Clone for ReadyTransaction { fn clone(&self) -> Self { Self { transaction: self.transaction.clone(), unlocks: self.unlocks.clone(), requires_offset: self.requires_offset, } } } impl ReadyTransaction { pub fn provides(&self) -> &[TxMarker] { &self.transaction.transaction.provides } } impl ReadyTransaction { pub fn max_fee_per_gas(&self) -> u128 { self.transaction.transaction.max_fee_per_gas() } } #[cfg(test)] mod tests { use super::*; #[test] fn can_id_txs() { let addr = Address::random(); assert_eq!(to_marker(1, addr), to_marker(1, addr)); assert_ne!(to_marker(2, addr), to_marker(1, addr)); } } ================================================ FILE: crates/anvil/src/eth/sign.rs ================================================ use crate::eth::error::BlockchainError; use alloy_consensus::{Sealed, SignableTransaction}; use alloy_dyn_abi::TypedData; use alloy_network::{Network, TxSignerSync}; use alloy_primitives::{Address, B256, Signature, map::AddressHashMap}; use alloy_signer::Signer as AlloySigner; use alloy_signer_local::PrivateKeySigner; use foundry_primitives::{FoundryTxEnvelope, FoundryTypedTx}; use tempo_primitives::TempoSignature; /// Network-agnostic signing: messages, typed data, and hashes. #[async_trait::async_trait] pub trait MessageSigner: Send + Sync { /// returns the available accounts for this signer fn accounts(&self) -> Vec
; /// Returns `true` whether this signer can sign for this address fn is_signer_for(&self, addr: Address) -> bool { self.accounts().contains(&addr) } /// Returns the signature async fn sign(&self, address: Address, message: &[u8]) -> Result; /// Encodes and signs the typed data according EIP-712. Payload must conform to the EIP-712 /// standard. async fn sign_typed_data( &self, address: Address, payload: &TypedData, ) -> Result; /// Signs the given hash. async fn sign_hash(&self, address: Address, hash: B256) -> Result; } /// A transaction signer, generic over the network. /// /// Modelled after alloy's `NetworkWallet`: the /// [`sign_transaction_from`](Signer::sign_transaction_from) method takes an /// unsigned transaction and returns the fully-signed envelope in one step. pub trait Signer: MessageSigner { /// Signs an unsigned transaction and returns the signed envelope. /// /// Mirrors `NetworkWallet::sign_transaction_from`. fn sign_transaction_from( &self, sender: &Address, tx: N::UnsignedTx, ) -> Result; } /// Maintains developer keys pub struct DevSigner { addresses: Vec
, accounts: AddressHashMap, } impl DevSigner { pub fn new(accounts: Vec) -> Self { let addresses = accounts.iter().map(|wallet| wallet.address()).collect::>(); let accounts = addresses.iter().copied().zip(accounts).collect(); Self { addresses, accounts } } } #[async_trait::async_trait] impl MessageSigner for DevSigner { fn accounts(&self) -> Vec
{ self.addresses.clone() } fn is_signer_for(&self, addr: Address) -> bool { self.accounts.contains_key(&addr) } async fn sign(&self, address: Address, message: &[u8]) -> Result { let signer = self.accounts.get(&address).ok_or(BlockchainError::NoSignerAvailable)?; Ok(signer.sign_message(message).await?) } async fn sign_typed_data( &self, address: Address, payload: &TypedData, ) -> Result { let mut signer = self.accounts.get(&address).ok_or(BlockchainError::NoSignerAvailable)?.to_owned(); // Explicitly set chainID as none, to avoid any EIP-155 application to `v` when signing // typed data. signer.set_chain_id(None); Ok(signer.sign_dynamic_typed_data(payload).await?) } async fn sign_hash(&self, address: Address, hash: B256) -> Result { let signer = self.accounts.get(&address).ok_or(BlockchainError::NoSignerAvailable)?; Ok(signer.sign_hash(&hash).await?) } } impl Signer for DevSigner { fn sign_transaction_from( &self, sender: &Address, tx: FoundryTypedTx, ) -> Result { let signer = self.accounts.get(sender).ok_or(BlockchainError::NoSignerAvailable)?; let envelope = match tx { FoundryTypedTx::Legacy(mut t) => { let sig = signer.sign_transaction_sync(&mut t)?; FoundryTxEnvelope::Legacy(t.into_signed(sig)) } FoundryTypedTx::Eip2930(mut t) => { let sig = signer.sign_transaction_sync(&mut t)?; FoundryTxEnvelope::Eip2930(t.into_signed(sig)) } FoundryTypedTx::Eip1559(mut t) => { let sig = signer.sign_transaction_sync(&mut t)?; FoundryTxEnvelope::Eip1559(t.into_signed(sig)) } FoundryTypedTx::Eip7702(mut t) => { let sig = signer.sign_transaction_sync(&mut t)?; FoundryTxEnvelope::Eip7702(t.into_signed(sig)) } FoundryTypedTx::Eip4844(mut t) => { let sig = signer.sign_transaction_sync(&mut t)?; FoundryTxEnvelope::Eip4844(t.into_signed(sig)) } FoundryTypedTx::Deposit(_) => { unreachable!("op deposit txs should not be signed") } FoundryTypedTx::Tempo(mut t) => { let sig = signer.sign_transaction_sync(&mut t)?; FoundryTxEnvelope::Tempo(t.into_signed(sig.into())) } }; Ok(envelope) } } /// Builds a TxEnvelope from UnsignedTx with a zeroed signature. /// /// Used for impersonated accounts, where transactions are accepted without a valid signature. pub fn build_impersonated(typed_tx: FoundryTypedTx) -> FoundryTxEnvelope { let signature = Signature::new(Default::default(), Default::default(), false); match typed_tx { FoundryTypedTx::Legacy(tx) => FoundryTxEnvelope::Legacy(tx.into_signed(signature)), FoundryTypedTx::Eip2930(tx) => FoundryTxEnvelope::Eip2930(tx.into_signed(signature)), FoundryTypedTx::Eip1559(tx) => FoundryTxEnvelope::Eip1559(tx.into_signed(signature)), FoundryTypedTx::Eip7702(tx) => FoundryTxEnvelope::Eip7702(tx.into_signed(signature)), FoundryTypedTx::Eip4844(tx) => FoundryTxEnvelope::Eip4844(tx.into_signed(signature)), FoundryTypedTx::Deposit(tx) => FoundryTxEnvelope::Deposit(Sealed::new(tx)), FoundryTypedTx::Tempo(tx) => { let tempo_sig: TempoSignature = signature.into(); FoundryTxEnvelope::Tempo(tx.into_signed(tempo_sig)) } } } ================================================ FILE: crates/anvil/src/eth/util.rs ================================================ use alloy_primitives::hex; use itertools::Itertools; /// Formats values as hex strings, separated by commas. pub fn hex_fmt_many(i: I) -> String where I: IntoIterator, T: AsRef<[u8]>, { let items = i.into_iter().map(|item| hex::encode(item.as_ref())).format(", "); format!("{items}") } ================================================ FILE: crates/anvil/src/evm.rs ================================================ use alloy_evm::precompiles::DynPrecompile; use alloy_primitives::Address; use std::fmt::Debug; /// 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, DynPrecompile)>; } #[cfg(test)] mod tests { use std::convert::Infallible; use crate::PrecompileFactory; use alloy_evm::{ EthEvm, Evm, eth::EthEvmContext, precompiles::{DynPrecompile, PrecompilesMap}, }; use alloy_op_evm::OpEvm; use alloy_primitives::{Address, Bytes, TxKind, U256, address}; use foundry_evm::core::either_evm::EitherEvm; use itertools::Itertools; use op_revm::{L1BlockInfo, OpContext, OpSpecId, OpTransaction, precompiles::OpPrecompiles}; use revm::{ Journal, context::{BlockEnv, CfgEnv, Evm as RevmEvm, JournalTr, LocalContext, TxEnv}, database::{EmptyDB, EmptyDBTyped}, handler::{EthPrecompiles, instructions::EthInstructions}, inspector::NoOpInspector, interpreter::interpreter::EthInterpreter, precompile::{PrecompileOutput, PrecompileSpecId, Precompiles}, primitives::hardfork::SpecId, }; // A precompile activated in the `Prague` spec (BLS12-381 G2 map). const ETH_PRAGUE_PRECOMPILE: Address = address!("0x0000000000000000000000000000000000000011"); // A precompile activated in the `Osaka` spec (EIP-7951). const ETH_OSAKA_PRECOMPILE: Address = address!("0x0000000000000000000000000000000000000100"); // 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; impl PrecompileFactory for CustomPrecompileFactory { fn precompiles(&self) -> Vec<(Address, DynPrecompile)> { use alloy_evm::precompiles::PrecompileInput; vec![( PRECOMPILE_ADDR, DynPrecompile::from(|input: PrecompileInput<'_>| { Ok(PrecompileOutput { bytes: Bytes::copy_from_slice(input.data), gas_used: 0, gas_refunded: 0, reverted: false, }) }), )] } } /// Creates a new EVM instance with the custom precompile factory. fn create_eth_evm( spec: SpecId, ) -> (TxEnv, EitherEvm, NoOpInspector, PrecompilesMap>) { let tx_env = TxEnv { kind: TxKind::Call(PRECOMPILE_ADDR), data: PAYLOAD.into(), ..Default::default() }; let eth_evm_context = EthEvmContext { journaled_state: Journal::new(EmptyDB::default()), block: BlockEnv::default(), cfg: CfgEnv::new_with_spec(spec), tx: tx_env.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, )); (tx_env, eth_evm) } /// Creates a new OP EVM instance with the custom precompile factory. fn create_op_evm( spec: SpecId, op_spec: OpSpecId, ) -> (OpTransaction, EitherEvm, NoOpInspector, PrecompilesMap>) { let tx = OpTransaction:: { base: TxEnv { kind: TxKind::Call(PRECOMPILE_ADDR), data: PAYLOAD.into(), ..Default::default() }, ..Default::default() }; let mut chain = L1BlockInfo::default(); if op_spec == OpSpecId::ISTHMUS { chain.operator_fee_constant = Some(U256::from(0)); chain.operator_fee_scalar = Some(U256::from(0)); } let op_cfg: CfgEnv = CfgEnv::new_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(spec); journal }, block: BlockEnv::default(), cfg: op_cfg.clone(), tx: 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, )); (tx, op_evm) } #[test] fn build_eth_evm_with_extra_precompiles_osaka_spec() { let (tx_env, mut evm) = create_eth_evm(SpecId::OSAKA); // Check that the Osaka precompile IS present when using the Osaka spec. assert!(evm.precompiles().addresses().contains(Ð_OSAKA_PRECOMPILE)); // Check that the Prague precompile IS present when using the Osaka spec. assert!(evm.precompiles().addresses().contains(Ð_PRAGUE_PRECOMPILE)); assert!(!evm.precompiles().addresses().contains(&PRECOMPILE_ADDR)); evm.precompiles_mut().extend_precompiles(CustomPrecompileFactory.precompiles()); assert!(evm.precompiles().addresses().contains(&PRECOMPILE_ADDR)); let result = match &mut evm { EitherEvm::Eth(eth_evm) => eth_evm.transact(tx_env).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 (tx_env, mut evm) = create_eth_evm(SpecId::LONDON); // Check that the Osaka precompile IS NOT present when using the London spec. assert!(!evm.precompiles().addresses().contains(Ð_OSAKA_PRECOMPILE)); // 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)); evm.precompiles_mut().extend_precompiles(CustomPrecompileFactory.precompiles()); assert!(evm.precompiles().addresses().contains(&PRECOMPILE_ADDR)); let result = match &mut evm { EitherEvm::Eth(eth_evm) => eth_evm.transact(tx_env).unwrap(), _ => unreachable!(), }; assert!(result.result.is_success()); assert_eq!(result.result.output(), Some(&PAYLOAD.into())); } #[test] fn build_eth_evm_with_extra_precompiles_prague_spec() { let (tx_env, mut evm) = create_eth_evm(SpecId::PRAGUE); // Check that the Osaka precompile IS NOT present when using the Prague spec. assert!(!evm.precompiles().addresses().contains(Ð_OSAKA_PRECOMPILE)); // Check that the Prague precompile IS present when using the Prague spec. assert!(evm.precompiles().addresses().contains(Ð_PRAGUE_PRECOMPILE)); assert!(!evm.precompiles().addresses().contains(&PRECOMPILE_ADDR)); evm.precompiles_mut().extend_precompiles(CustomPrecompileFactory.precompiles()); assert!(evm.precompiles().addresses().contains(&PRECOMPILE_ADDR)); let result = match &mut evm { EitherEvm::Eth(eth_evm) => eth_evm.transact(tx_env).unwrap(), _ => unreachable!(), }; assert!(result.result.is_success()); assert_eq!(result.result.output(), Some(&PAYLOAD.into())); } #[test] fn build_op_evm_with_extra_precompiles_isthmus_spec() { let (tx, mut evm) = create_op_evm(SpecId::OSAKA, OpSpecId::ISTHMUS); // Check that the Isthmus precompile IS present when using the Isthmus spec. assert!(evm.precompiles().addresses().contains(&OP_ISTHMUS_PRECOMPILE)); // Check that the Prague precompile IS present when using the Isthmus spec. assert!(evm.precompiles().addresses().contains(Ð_PRAGUE_PRECOMPILE)); assert!(!evm.precompiles().addresses().contains(&PRECOMPILE_ADDR)); evm.precompiles_mut().extend_precompiles(CustomPrecompileFactory.precompiles()); assert!(evm.precompiles().addresses().contains(&PRECOMPILE_ADDR)); let result = match &mut evm { EitherEvm::Op(op_evm) => op_evm.transact(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 (tx, mut evm) = create_op_evm(SpecId::OSAKA, 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)); evm.precompiles_mut().extend_precompiles(CustomPrecompileFactory.precompiles()); assert!(evm.precompiles().addresses().contains(&PRECOMPILE_ADDR)); let result = match &mut evm { EitherEvm::Op(op_evm) => op_evm.transact(tx).unwrap(), _ => unreachable!(), }; assert!(result.result.is_success()); assert_eq!(result.result.output(), Some(&PAYLOAD.into())); } } ================================================ FILE: crates/anvil/src/filter.rs ================================================ //! Support for polling based filters use crate::{ StorageInfo, eth::{backend::notifications::NewBlockNotifications, error::ToRpcResponseResult}, pubsub::filter_logs, }; use alloy_consensus::TxReceipt; use alloy_network::Network; use alloy_primitives::{TxHash, map::HashMap}; use alloy_rpc_types::{Filter, FilteredParams, Log}; use anvil_core::eth::subscription::SubscriptionId; use anvil_rpc::{ error::{ErrorCode, RpcError}, response::ResponseResult, }; use futures::{Stream, StreamExt, channel::mpsc::Receiver}; use std::{ pin::Pin, sync::Arc, task::{Context, Poll}, time::{Duration, Instant}, }; use tokio::sync::Mutex; /// Type alias for filters identified by their id and their expiration timestamp type FilterMap = Arc, Instant)>>>; /// timeout after which to remove an active filter if it wasn't polled since then pub const ACTIVE_FILTER_TIMEOUT_SECS: u64 = 60 * 5; /// Contains all registered filters pub struct Filters { /// all currently active filters active_filters: FilterMap, /// How long we keep a live the filter after the last poll keepalive: Duration, } impl Clone for Filters { fn clone(&self) -> Self { Self { active_filters: self.active_filters.clone(), keepalive: self.keepalive } } } impl std::fmt::Debug for Filters { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("Filters").field("keepalive", &self.keepalive).finish_non_exhaustive() } } impl Filters { /// Adds a new `EthFilter` to the set pub async fn add_filter(&self, filter: EthFilter) -> String { let id = new_id(); trace!(target: "node::filter", "Adding new filter id {}", id); let mut filters = self.active_filters.lock().await; filters.insert(id.clone(), (filter, self.next_deadline())); id } /// Returns the original `Filter` of an `eth_newFilter` pub async fn get_log_filter(&self, id: &str) -> Option { let filters = self.active_filters.lock().await; if let Some((EthFilter::Logs(log), _)) = filters.get(id) { return log.filter.filter.clone(); } None } /// Removes the filter identified with the `id` pub async fn uninstall_filter(&self, id: &str) -> Option> { trace!(target: "node::filter", "Uninstalling filter id {}", id); self.active_filters.lock().await.remove(id).map(|(f, _)| f) } /// The duration how long to keep alive stale filters pub fn keep_alive(&self) -> Duration { self.keepalive } /// Returns the timestamp after which a filter should expire fn next_deadline(&self) -> Instant { Instant::now() + self.keep_alive() } /// Evict all filters that weren't updated and reached there deadline pub async fn evict(&self) { trace!(target: "node::filter", "Evicting stale filters"); let now = Instant::now(); let mut active_filters = self.active_filters.lock().await; active_filters.retain(|id, (_, deadline)| { if now > *deadline { trace!(target: "node::filter",?id, "Evicting stale filter"); return false; } true }); } } impl Filters where N::ReceiptEnvelope: TxReceipt + Clone, { pub async fn get_filter_changes(&self, id: &str) -> ResponseResult { { let mut filters = self.active_filters.lock().await; if let Some((filter, deadline)) = filters.get_mut(id) { let resp = filter .next() .await .unwrap_or_else(|| ResponseResult::success(Vec::<()>::new())); *deadline = self.next_deadline(); return resp; } } warn!(target: "node::filter", "No filter found for {}", id); ResponseResult::error(RpcError { code: ErrorCode::ServerError(-32000), message: "filter not found".into(), data: None, }) } } impl Default for Filters { fn default() -> Self { Self { active_filters: Arc::new(Default::default()), keepalive: Duration::from_secs(ACTIVE_FILTER_TIMEOUT_SECS), } } } /// returns a new random hex id fn new_id() -> String { SubscriptionId::random_hex().to_string() } /// Represents a poll based filter pub enum EthFilter { Logs(Box>), Blocks(NewBlockNotifications), PendingTransactions(Receiver), } impl std::fmt::Debug for EthFilter { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Self::Logs(_) => f.debug_tuple("Logs").finish(), Self::Blocks(_) => f.debug_tuple("Blocks").finish(), Self::PendingTransactions(_) => f.debug_tuple("PendingTransactions").finish(), } } } impl Stream for EthFilter where N::ReceiptEnvelope: TxReceipt + Clone, { type Item = ResponseResult; fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { let pin = self.get_mut(); match pin { Self::Logs(logs) => Poll::Ready(Some(Ok(logs.poll(cx)).to_rpc_result())), Self::Blocks(blocks) => { let mut new_blocks = Vec::new(); while let Poll::Ready(Some(block)) = blocks.poll_next_unpin(cx) { new_blocks.push(block.hash); } Poll::Ready(Some(Ok(new_blocks).to_rpc_result())) } Self::PendingTransactions(tx) => { let mut new_txs = Vec::new(); while let Poll::Ready(Some(tx_hash)) = tx.poll_next_unpin(cx) { new_txs.push(tx_hash); } Poll::Ready(Some(Ok(new_txs).to_rpc_result())) } } } } /// Listens for new blocks and matching logs emitted in that block pub struct LogsFilter { /// listener for new blocks pub blocks: NewBlockNotifications, /// accessor for block storage pub storage: StorageInfo, /// matcher with all provided filter params pub filter: FilteredParams, /// existing logs that matched the filter when the listener was installed /// /// They'll be returned on the first poll pub historic: Option>, } impl std::fmt::Debug for LogsFilter { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("LogsFilter").field("filter", &self.filter).finish_non_exhaustive() } } impl LogsFilter where N::ReceiptEnvelope: TxReceipt + Clone, { /// Returns all the logs since the last time this filter was polled pub fn poll(&mut self, cx: &mut Context<'_>) -> Vec { let mut logs = self.historic.take().unwrap_or_default(); while let Poll::Ready(Some(block)) = self.blocks.poll_next_unpin(cx) { let b = self.storage.block(block.hash); let receipts = self.storage.receipts(block.hash); if let (Some(receipts), Some(block)) = (receipts, b) { logs.extend(filter_logs(block, receipts, &self.filter)) } } logs } } ================================================ FILE: crates/anvil/src/lib.rs ================================================ //! Anvil is a fast local Ethereum development node. #![cfg_attr(not(test), warn(unused_crate_dependencies))] #![cfg_attr(docsrs, feature(doc_cfg))] use crate::{ error::{NodeError, NodeResult}, eth::{ EthApi, backend::{info::StorageInfo, mem}, fees::{FeeHistoryService, FeeManager}, miner::{Miner, MiningMode}, pool::Pool, sign::{DevSigner, Signer as EthSigner}, }, filter::Filters, logging::{LoggingManager, NodeLogLayer}, service::NodeService, 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, WrapErr}; use foundry_common::provider::{ProviderBuilder, RetryProvider}; pub use foundry_evm::hardfork::EthereumHardfork; use foundry_primitives::FoundryNetwork; use futures::{FutureExt, TryFutureExt}; use parking_lot::Mutex; use revm::primitives::hardfork::SpecId; use server::try_spawn_ipc; use std::{ net::SocketAddr, pin::Pin, sync::Arc, task::{Context, Poll}, }; use tokio::{ runtime::Handle, task::{JoinError, JoinHandle}, }; use tracing_subscriber::EnvFilter; /// contains the background service that drives the node mod service; mod config; pub use config::{ AccountGenerator, CHAIN_ID, DEFAULT_GAS_LIMIT, ForkChoice, NodeConfig, VERSION_MESSAGE, }; mod error; /// ethereum related implementations pub mod eth; /// Evm related abstractions mod evm; pub use evm::PrecompileFactory; /// support for polling filters pub mod filter; /// commandline output pub mod logging; /// types for subscriptions pub mod pubsub; /// axum RPC server implementations pub mod server; /// Futures for shutdown signal mod shutdown; /// additional task management mod tasks; /// contains cli command #[cfg(feature = "cmd")] pub mod cmd; #[cfg(feature = "cmd")] pub mod args; #[cfg(feature = "cmd")] pub mod opts; #[macro_use] extern crate foundry_common; #[macro_use] extern crate tracing; /// Creates the node and runs the server. /// /// Returns the [EthApi] that can be used to interact with the node and the [JoinHandle] of the /// task. /// /// # Panics /// /// Panics if any error occurs. For a non-panicking version, use [`try_spawn`]. /// /// /// # Examples /// /// ```no_run /// # use anvil::NodeConfig; /// # async fn spawn() -> eyre::Result<()> { /// let config = NodeConfig::default(); /// let (api, handle) = anvil::spawn(config).await; /// /// // use api /// /// // wait forever /// handle.await.unwrap().unwrap(); /// # Ok(()) /// # } /// ``` pub async fn spawn(config: NodeConfig) -> (EthApi, NodeHandle) { try_spawn(config).await.expect("failed to spawn node") } /// Creates the node and runs the server /// /// Returns the [EthApi] that can be used to interact with the node and the [JoinHandle] of the /// task. /// /// # Examples /// /// ```no_run /// # use anvil::NodeConfig; /// # async fn spawn() -> eyre::Result<()> { /// let config = NodeConfig::default(); /// let (api, handle) = anvil::try_spawn(config).await?; /// /// // use api /// /// // wait forever /// handle.await??; /// # Ok(()) /// # } /// ``` pub async fn try_spawn(mut config: NodeConfig) -> Result<(EthApi, NodeHandle)> { let logger = if config.enable_tracing { init_tracing() } else { Default::default() }; logger.set_enabled(!config.silent); let backend = config.setup::().await?; if let Some(state) = config.init_state.clone() { backend.load_state(state).await.wrap_err("failed to load init state")?; } let backend = Arc::new(backend); if config.enable_auto_impersonate { backend.auto_impersonate_account(true); } let fork = backend.get_fork(); let NodeConfig { signer_accounts, block_time, port, max_transactions, server_config, no_mining, transaction_order, genesis, mixed_mining, .. } = config.clone(); let pool = Arc::new(Pool::default()); let mode = if let Some(block_time) = block_time { if mixed_mining { let listener = pool.add_ready_listener(); MiningMode::mixed(max_transactions, listener, block_time) } else { MiningMode::interval(block_time) } } else if no_mining { MiningMode::None } else { // get a listener for ready transactions let listener = pool.add_ready_listener(); MiningMode::instant(max_transactions, listener) }; let miner = match &fork { Some(fork) => { Miner::new(mode).with_forced_transactions(fork.config.read().force_transactions.clone()) } _ => Miner::new(mode), }; let dev_signer: Box> = Box::new(DevSigner::new(signer_accounts)); let mut signers = vec![dev_signer]; if let Some(genesis) = genesis { let genesis_signers = genesis .alloc .values() .filter_map(|acc| acc.private_key) .flat_map(|k| PrivateKeySigner::from_bytes(&k)) .collect::>(); if !genesis_signers.is_empty() { signers.push(Box::new(DevSigner::new(genesis_signers))); } } 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)), ); // create an entry for the best block if let Some(header) = backend.get_block(backend.best_number()).map(|block| block.header) { fee_history_service.insert_cache_entry_for_block(header.hash_slow(), &header); } let filters = Filters::default(); // create the cloneable api wrapper let api = EthApi::new( Arc::clone(&pool), Arc::clone(&backend), Arc::new(signers), fee_history_cache, fee_history_service.fee_history_limit(), miner.clone(), logger, filters.clone(), transaction_order, ); // spawn the node service let node_service = tokio::task::spawn(NodeService::new(pool, backend, miner, fee_history_service, filters)); let mut servers = Vec::with_capacity(config.host.len()); let mut addresses = Vec::with_capacity(config.host.len()); for addr in &config.host { let sock_addr = SocketAddr::new(*addr, port); // Create a TCP listener. let tcp_listener = tokio::net::TcpListener::bind(sock_addr).await?; addresses.push(tcp_listener.local_addr()?); // Spawn the server future on a new task. let srv = server::serve_on(tcp_listener, api.clone(), server_config.clone()); servers.push(tokio::task::spawn(srv.map_err(Into::into))); } let tokio_handle = Handle::current(); let (signal, on_shutdown) = shutdown::signal(); let task_manager = TaskManager::new(tokio_handle, on_shutdown); let ipc_task = config.get_ipc_path().map(|path| try_spawn_ipc(api.clone(), path)).transpose()?; let handle = NodeHandle { config, node_service, servers, ipc_task, addresses, _signal: Some(signal), task_manager, }; handle.print(fork.as_ref())?; Ok((api, handle)) } type IpcTask = JoinHandle<()>; /// A handle to the spawned node and server tasks. /// /// This future will resolve if either the node or server task resolve/fail. pub struct NodeHandle { config: NodeConfig, /// The address of the running rpc server. addresses: Vec, /// Join handle for the Node Service. pub node_service: JoinHandle>, /// Join handles (one per socket) for the Anvil server. pub servers: Vec>>, /// The future that joins the ipc server, if any. ipc_task: Option, /// A signal that fires the shutdown, fired on drop. _signal: Option, /// A task manager that can be used to spawn additional tasks. task_manager: TaskManager, } impl Drop for NodeHandle { fn drop(&mut self) { // Fire shutdown signal to make sure anvil instance is terminated. if let Some(signal) = self._signal.take() { let _ = signal.fire(); } } } impl NodeHandle { /// The [NodeConfig] the node was launched with. pub fn config(&self) -> &NodeConfig { &self.config } /// Prints the launch info. pub(crate) fn print(&self, fork: Option<&ClientFork>) -> Result<()> { self.config.print(fork)?; if !self.config.silent { if let Some(ipc_path) = self.ipc_path() { sh_println!("IPC path: {ipc_path}")?; } sh_println!( "Listening on {}", self.addresses .iter() .map(|addr| { addr.to_string() }) .collect::>() .join(", ") )?; } Ok(()) } /// The address of the launched server. /// /// **N.B.** this may not necessarily be the same `host + port` as configured in the /// `NodeConfig`, if port was set to 0, then the OS auto picks an available port. pub fn socket_address(&self) -> &SocketAddr { &self.addresses[0] } /// Returns the http endpoint. pub fn http_endpoint(&self) -> String { format!("http://{}", self.socket_address()) } /// Returns the websocket endpoint. pub fn ws_endpoint(&self) -> String { format!("ws://{}", self.socket_address()) } /// Returns the path of the launched ipc server, if any. pub fn ipc_path(&self) -> Option { self.config.get_ipc_path() } /// Constructs a [`RetryProvider`] for this handle's HTTP endpoint. pub fn http_provider(&self) -> RetryProvider { ProviderBuilder::new(&self.http_endpoint()).build().expect("failed to build HTTP provider") } /// Constructs a [`RetryProvider`] for this handle's WS endpoint. pub fn ws_provider(&self) -> RetryProvider { ProviderBuilder::new(&self.ws_endpoint()).build().expect("failed to build WS provider") } /// Constructs a [`RetryProvider`] for this handle's IPC endpoint, if any. pub fn ipc_provider(&self) -> Option { ProviderBuilder::new(&self.config.get_ipc_path()?).build().ok() } /// Signer accounts that can sign messages/transactions from the EVM node. pub fn dev_accounts(&self) -> impl Iterator + '_ { self.config.signer_accounts.iter().map(|wallet| wallet.address()) } /// Signer accounts that can sign messages/transactions from the EVM node. pub fn dev_wallets(&self) -> impl Iterator + '_ { self.config.signer_accounts.iter().cloned() } /// Accounts that will be initialised with `genesis_balance` in the genesis block. pub fn genesis_accounts(&self) -> impl Iterator + '_ { self.config.genesis_accounts.iter().map(|w| w.address()) } /// Native token balance of every genesis account in the genesis block. pub fn genesis_balance(&self) -> U256 { self.config.genesis_balance } /// Default gas price for all txs. pub fn gas_price(&self) -> u128 { self.config.get_gas_price() } /// Returns the shutdown signal. pub fn shutdown_signal(&self) -> &Option { &self._signal } /// Returns mutable access to the shutdown signal. /// /// This can be used to extract the Signal. pub fn shutdown_signal_mut(&mut self) -> &mut Option { &mut self._signal } /// Returns the task manager that can be used to spawn new tasks. /// /// ``` /// use anvil::NodeHandle; /// # fn t(handle: NodeHandle) { /// let task_manager = handle.task_manager(); /// let on_shutdown = task_manager.on_shutdown(); /// /// task_manager.spawn(async move { /// on_shutdown.await; /// // do something /// }); /// /// # } /// ``` pub fn task_manager(&self) -> &TaskManager { &self.task_manager } } impl Future for NodeHandle { type Output = Result, JoinError>; fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { let pin = self.get_mut(); // poll the ipc task if let Some(mut ipc) = pin.ipc_task.take() { if let Poll::Ready(res) = ipc.poll_unpin(cx) { return Poll::Ready(res.map(|()| Ok(()))); } else { pin.ipc_task = Some(ipc); } } // poll the node service task if let Poll::Ready(res) = pin.node_service.poll_unpin(cx) { return Poll::Ready(res); } // poll the axum server handles for server in &mut pin.servers { if let Poll::Ready(res) = server.poll_unpin(cx) { return Poll::Ready(res); } } Poll::Pending } } #[doc(hidden)] pub fn init_tracing() -> LoggingManager { use tracing_subscriber::prelude::*; let manager = LoggingManager::default(); let _ = if let Ok(rust_log_val) = std::env::var("RUST_LOG") && !rust_log_val.contains("=") { // Mutate the given filter to include `node` logs if it is not already present. // This prevents the unexpected behaviour of not seeing any node logs if a RUST_LOG // is already present that doesn't set it. let rust_log_val = if !rust_log_val.contains("node") { format!("{rust_log_val},node=info") } else { rust_log_val }; let env_filter: EnvFilter = rust_log_val.parse().expect("failed to parse modified RUST_LOG"); tracing_subscriber::registry() .with(env_filter) .with(tracing_subscriber::fmt::layer()) .try_init() } else { tracing_subscriber::Registry::default() .with(NodeLogLayer::new(manager.clone())) .with( tracing_subscriber::fmt::layer() .without_time() .with_target(false) .with_level(false), ) .try_init() }; manager } ================================================ FILE: crates/anvil/src/logging.rs ================================================ //! User facing Logger use parking_lot::RwLock; use std::sync::Arc; use tracing::{Metadata, subscriber::Interest}; use tracing_subscriber::{Layer, layer::Context}; /// The target that identifies the events intended to be logged to stdout pub(crate) const NODE_USER_LOG_TARGET: &str = "node::user"; /// The target that identifies the events coming from the `console.log` invocations. pub(crate) const EVM_CONSOLE_LOG_TARGET: &str = "node::console"; /// A logger that listens for node related events and displays them. /// /// This layer is intended to be used as filter for `NODE_USER_LOG_TARGET` events that will /// eventually be logged to stdout #[derive(Clone, Debug, Default)] pub struct NodeLogLayer { state: LoggingManager, } impl NodeLogLayer { /// Returns a new instance of this layer pub fn new(state: LoggingManager) -> Self { Self { state } } } // use `Layer`'s filter function to globally enable/disable `NODE_USER_LOG_TARGET` events impl Layer for NodeLogLayer where S: tracing::Subscriber, { fn register_callsite(&self, metadata: &'static Metadata<'static>) -> Interest { if metadata.target() == NODE_USER_LOG_TARGET || metadata.target() == EVM_CONSOLE_LOG_TARGET { Interest::sometimes() } else { Interest::never() } } fn enabled(&self, metadata: &Metadata<'_>, _ctx: Context<'_, S>) -> bool { self.state.is_enabled() && (metadata.target() == NODE_USER_LOG_TARGET || metadata.target() == EVM_CONSOLE_LOG_TARGET) } } /// Contains the configuration of the logger #[derive(Clone, Debug)] pub struct LoggingManager { /// Whether the logger is currently enabled pub enabled: Arc>, } impl LoggingManager { /// Returns true if logging is currently enabled pub fn is_enabled(&self) -> bool { *self.enabled.read() } /// Updates the `enabled` state pub fn set_enabled(&self, enabled: bool) { let mut current = self.enabled.write(); *current = enabled; } } impl Default for LoggingManager { fn default() -> Self { Self { enabled: Arc::new(RwLock::new(true)) } } } ================================================ FILE: crates/anvil/src/opts.rs ================================================ use crate::cmd::NodeArgs; use clap::{Parser, Subcommand}; use foundry_cli::opts::GlobalArgs; use foundry_common::version::{LONG_VERSION, SHORT_VERSION}; /// A fast local Ethereum development node. #[derive(Parser)] #[command(name = "anvil", version = SHORT_VERSION, long_version = LONG_VERSION, next_display_order = None)] pub struct Anvil { /// Include the global arguments. #[command(flatten)] pub global: GlobalArgs, #[command(flatten)] pub node: NodeArgs, #[command(subcommand)] pub cmd: Option, } #[derive(Subcommand)] pub enum AnvilSubcommand { /// Generate shell completions script. #[command(visible_alias = "com")] Completions { #[arg(value_enum)] shell: foundry_cli::clap::Shell, }, } ================================================ FILE: crates/anvil/src/pubsub.rs ================================================ use crate::{ StorageInfo, eth::{backend::notifications::NewBlockNotifications, error::to_rpc_result}, }; use alloy_consensus::{BlockHeader, TxReceipt}; use alloy_network::{AnyRpcTransaction, Network}; use alloy_primitives::{B256, TxHash}; use alloy_rpc_types::{FilteredParams, Log, Transaction, pubsub::SubscriptionResult}; use anvil_core::eth::{block::Block, subscription::SubscriptionId}; use anvil_rpc::{request::Version, response::ResponseResult}; use futures::{Stream, StreamExt, channel::mpsc::Receiver, ready}; use serde::Serialize; use std::{ collections::VecDeque, pin::Pin, task::{Context, Poll}, }; use tokio::sync::mpsc::UnboundedReceiver; /// Listens for new blocks and matching logs emitted in that block pub struct LogsSubscription { pub blocks: NewBlockNotifications, pub storage: StorageInfo, pub filter: FilteredParams, pub queued: VecDeque, pub id: SubscriptionId, } impl std::fmt::Debug for LogsSubscription { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("LogsSubscription") .field("filter", &self.filter) .field("id", &self.id) .finish_non_exhaustive() } } impl LogsSubscription where N::ReceiptEnvelope: TxReceipt + Clone, { fn poll(&mut self, cx: &mut Context<'_>) -> Poll> { loop { if let Some(log) = self.queued.pop_front() { let params = EthSubscriptionParams { subscription: self.id.clone(), result: to_rpc_result(log), }; return Poll::Ready(Some(EthSubscriptionResponse::new(params))); } if let Some(block) = ready!(self.blocks.poll_next_unpin(cx)) { let b = self.storage.block(block.hash); let receipts = self.storage.receipts(block.hash); if let (Some(receipts), Some(block)) = (receipts, b) { let logs = filter_logs(block, receipts, &self.filter); if logs.is_empty() { // this ensures we poll the receiver until it is pending, in which case the // underlying `UnboundedReceiver` will register the new waker, see // [`futures::channel::mpsc::UnboundedReceiver::poll_next()`] continue; } self.queued.extend(logs) } } else { return Poll::Ready(None); } if self.queued.is_empty() { return Poll::Pending; } } } } #[derive(Clone, Debug, PartialEq, Eq, Serialize)] pub struct EthSubscriptionResponse { jsonrpc: Version, method: &'static str, params: EthSubscriptionParams, } impl EthSubscriptionResponse { pub fn new(params: EthSubscriptionParams) -> Self { Self { jsonrpc: Version::V2, method: "eth_subscription", params } } } /// Represents the `params` field of an `eth_subscription` event #[derive(Clone, Debug, PartialEq, Eq, Serialize)] pub struct EthSubscriptionParams { subscription: SubscriptionId, #[serde(flatten)] result: ResponseResult, } /// Represents an ethereum Websocket subscription pub enum EthSubscription { Logs(Box>), Header(NewBlockNotifications, StorageInfo, SubscriptionId), PendingTransactions(Receiver, SubscriptionId), FullPendingTransactions(UnboundedReceiver, SubscriptionId), } impl std::fmt::Debug for EthSubscription { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Self::Logs(_) => f.debug_tuple("Logs").finish(), Self::Header(..) => f.debug_tuple("Header").finish(), Self::PendingTransactions(..) => f.debug_tuple("PendingTransactions").finish(), Self::FullPendingTransactions(..) => f.debug_tuple("FullPendingTransactions").finish(), } } } impl EthSubscription where N::ReceiptEnvelope: TxReceipt + Clone, { fn poll_response(&mut self, cx: &mut Context<'_>) -> Poll> { match self { Self::Logs(listener) => listener.poll(cx), Self::Header(blocks, storage, id) => { // this loop ensures we poll the receiver until it is pending, in which case the // underlying `UnboundedReceiver` will register the new waker, see // [`futures::channel::mpsc::UnboundedReceiver::poll_next()`] loop { if let Some(block) = ready!(blocks.poll_next_unpin(cx)) { if let Some(block) = storage.eth_block(block.hash) { let params = EthSubscriptionParams { subscription: id.clone(), result: to_rpc_result(block), }; return Poll::Ready(Some(EthSubscriptionResponse::new(params))); } } else { return Poll::Ready(None); } } } Self::PendingTransactions(tx, id) => { let res = ready!(tx.poll_next_unpin(cx)) .map(SubscriptionResult::::TransactionHash) .map(to_rpc_result) .map(|result| { let params = EthSubscriptionParams { subscription: id.clone(), result }; EthSubscriptionResponse::new(params) }); 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) } } } } impl Stream for EthSubscription where N::ReceiptEnvelope: TxReceipt + Clone, { type Item = serde_json::Value; fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { let pin = self.get_mut(); match ready!(pin.poll_response(cx)) { None => Poll::Ready(None), Some(res) => Poll::Ready(Some(serde_json::to_value(res).expect("can't fail;"))), } } } /// Returns all the logs that match the given filter pub fn filter_logs(block: Block, receipts: Vec, filter: &FilteredParams) -> Vec where R: TxReceipt, { /// Determines whether to add this log fn add_log( block_hash: B256, l: &alloy_primitives::Log, block: &Block, params: &FilteredParams, ) -> bool { if params.filter.is_some() { let block_number = block.header.number(); if !params.filter_block_range(block_number) || !params.filter_block_hash(block_hash) || !params.filter_address(&l.address) || !params.filter_topics(l.topics()) { return false; } } true } let block_hash = block.header.hash_slow(); let mut logs = vec![]; let mut log_index: u32 = 0; for (receipt_index, receipt) in receipts.into_iter().enumerate() { let transaction_hash = block.body.transactions[receipt_index].hash(); for log in receipt.logs() { if add_log(block_hash, log, &block, filter) { logs.push(Log { inner: log.clone(), block_hash: Some(block_hash), block_number: Some(block.header.number()), transaction_hash: Some(transaction_hash), transaction_index: Some(receipt_index as u64), log_index: Some(log_index as u64), removed: false, block_timestamp: Some(block.header.timestamp()), }); } log_index += 1; } } logs } ================================================ FILE: crates/anvil/src/server/beacon/error.rs ================================================ //! Beacon API error types use axum::{ Json, http::StatusCode, response::{IntoResponse, Response}, }; use serde::{Deserialize, Serialize}; use std::{ borrow::Cow, fmt::{self, Display}, }; /// Represents a Beacon API error response #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] pub struct BeaconError { /// HTTP status code #[serde(skip)] pub status_code: u16, /// Error code pub code: BeaconErrorCode, /// Error message pub message: Cow<'static, str>, } impl BeaconError { /// Creates a new beacon error with the given code pub fn new(code: BeaconErrorCode, message: impl Into>) -> Self { let status_code = code.status_code(); Self { status_code, code, message: message.into() } } /// Helper function to create a 400 Bad Request error for invalid block ID pub fn invalid_block_id(block_id: impl Display) -> Self { Self::new(BeaconErrorCode::BadRequest, format!("Invalid block ID: {block_id}")) } /// Helper function to create a 404 Not Found error for block not found pub fn block_not_found() -> Self { Self::new(BeaconErrorCode::NotFound, "Block not found") } /// Helper function to create a 500 Internal Server Error pub fn internal_error() -> Self { Self::new(BeaconErrorCode::InternalError, "Internal server error") } /// Helper function to create a 410 Gone error for deprecated endpoints pub fn deprecated_endpoint_with_hint(hint: impl Display) -> Self { Self::new(BeaconErrorCode::Gone, format!("This endpoint is deprecated. {hint}")) } /// Converts to an Axum response pub fn into_response(self) -> Response { let status = StatusCode::from_u16(self.status_code).unwrap_or(StatusCode::INTERNAL_SERVER_ERROR); ( status, Json(serde_json::json!({ "code": self.code as u16, "message": self.message, })), ) .into_response() } } impl fmt::Display for BeaconError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}: {}", self.code.as_str(), self.message) } } impl std::error::Error for BeaconError {} impl IntoResponse for BeaconError { fn into_response(self) -> Response { Self::into_response(self) } } /// Beacon API error codes following the beacon chain specification #[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] #[repr(u16)] pub enum BeaconErrorCode { BadRequest = 400, NotFound = 404, Gone = 410, InternalError = 500, } impl BeaconErrorCode { /// Returns the HTTP status code for this error pub const fn status_code(&self) -> u16 { *self as u16 } /// Returns a string representation of the error code pub const fn as_str(&self) -> &'static str { match self { Self::BadRequest => "Bad Request", Self::NotFound => "Not Found", Self::Gone => "Gone", Self::InternalError => "Internal Server Error", } } } impl fmt::Display for BeaconErrorCode { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", self.as_str()) } } #[cfg(test)] mod tests { use super::*; #[test] fn test_beacon_error_codes() { assert_eq!(BeaconErrorCode::BadRequest.status_code(), 400); assert_eq!(BeaconErrorCode::NotFound.status_code(), 404); assert_eq!(BeaconErrorCode::InternalError.status_code(), 500); } #[test] fn test_beacon_error_display() { let err = BeaconError::invalid_block_id("current"); assert_eq!(err.to_string(), "Bad Request: Invalid block ID: current"); } } ================================================ FILE: crates/anvil/src/server/beacon/handlers.rs ================================================ use super::{error::BeaconError, utils::must_be_ssz}; use crate::eth::EthApi; use alloy_eips::BlockId; use alloy_primitives::{B256, aliases::B32}; use alloy_rpc_types_beacon::{ genesis::{GenesisData, GenesisResponse}, sidecar::GetBlobsResponse, }; use axum::{ Json, extract::{Path, Query, State}, http::HeaderMap, response::{IntoResponse, Response}, }; use foundry_primitives::FoundryNetwork; use ssz::Encode; use std::{collections::HashMap, str::FromStr as _}; /// Handles incoming Beacon API requests for blob sidecars /// /// This endpoint is deprecated. Use `GET /eth/v1/beacon/blobs/{block_id}` instead. /// /// GET /eth/v1/beacon/blob_sidecars/{block_id} pub async fn handle_get_blob_sidecars( State(_api): State>, Path(_block_id): Path, Query(_params): Query>, ) -> Response { BeaconError::deprecated_endpoint_with_hint("Use `GET /eth/v1/beacon/blobs/{block_id}` instead.") .into_response() } /// Handles incoming Beacon API requests for blobs /// /// GET /eth/v1/beacon/blobs/{block_id} pub async fn handle_get_blobs( headers: HeaderMap, State(api): State>, Path(block_id): Path, Query(versioned_hashes): Query>, ) -> Response { // Parse block_id from path parameter let Ok(block_id) = BlockId::from_str(&block_id) else { return BeaconError::invalid_block_id(block_id).into_response(); }; // Parse versioned hashes from query parameters // Supports comma-separated format: ?versioned_hashes=0x...,0x... let versioned_hashes: Vec = match versioned_hashes.get("versioned_hashes") { Some(s) => { let mut hashes = Vec::new(); for hash in s.split(',') { let hash = hash.trim(); if hash.is_empty() { continue; } match B256::from_str(hash) { Ok(h) => hashes.push(h), Err(_) => { return BeaconError::new( super::error::BeaconErrorCode::BadRequest, format!("Invalid versioned hash: {hash}"), ) .into_response(); } } } hashes } None => Vec::new(), }; // Get the blob sidecars using existing EthApi logic match api.anvil_get_blobs_by_block_id(block_id, versioned_hashes) { Ok(Some(blobs)) => { if must_be_ssz(&headers) { blobs.as_ssz_bytes().into_response() } else { Json(GetBlobsResponse { execution_optimistic: false, finalized: false, data: blobs, }) .into_response() } } Ok(None) => BeaconError::block_not_found().into_response(), Err(_) => BeaconError::internal_error().into_response(), } } /// Handles incoming Beacon API requests for genesis details /// /// Only returns the `genesis_time`, other fields are set to zero. /// /// GET /eth/v1/beacon/genesis pub async fn handle_get_genesis(State(api): State>) -> Response { match api.anvil_get_genesis_time() { Ok(genesis_time) => Json(GenesisResponse { data: GenesisData { genesis_time, genesis_validators_root: B256::ZERO, genesis_fork_version: B32::ZERO, }, }) .into_response(), Err(_) => BeaconError::internal_error().into_response(), } } #[cfg(test)] mod tests { use super::*; use axum::http::HeaderValue; fn header_map_with_accept(accept: &str) -> HeaderMap { let mut headers = HeaderMap::new(); headers.insert(axum::http::header::ACCEPT, HeaderValue::from_str(accept).unwrap()); headers } #[test] fn test_must_be_ssz() { let test_cases = vec![ (None, false, "no Accept header"), (Some("application/json"), false, "JSON only"), (Some("application/octet-stream"), true, "octet-stream only"), (Some("application/octet-stream;q=1.0,application/json;q=0.9"), true, "SSZ preferred"), ( Some("application/json;q=1.0,application/octet-stream;q=0.9"), false, "JSON preferred", ), (Some("application/octet-stream;q=0.5,application/json;q=0.5"), false, "equal quality"), ( Some("text/html;q=0.9, application/octet-stream;q=1.0, application/json;q=0.8"), true, "multiple types", ), ( Some("application/octet-stream ; q=1.0 , application/json ; q=0.9"), true, "whitespace handling", ), (Some("application/octet-stream, application/json;q=0.9"), true, "default quality"), ]; for (accept_header, expected, description) in test_cases { let headers = match accept_header { None => HeaderMap::new(), Some(header) => header_map_with_accept(header), }; assert_eq!( must_be_ssz(&headers), expected, "Test case '{}' failed: expected {}, got {}", description, expected, !expected ); } } } ================================================ FILE: crates/anvil/src/server/beacon/mod.rs ================================================ //! Beacon Node REST API implementation for Anvil. use axum::{Router, routing::get}; use crate::eth::EthApi; use foundry_primitives::FoundryNetwork; mod error; mod handlers; mod utils; /// Configures an [`axum::Router`] that handles Beacon REST API calls. pub fn router(api: EthApi) -> Router { Router::new() .route("/eth/v1/beacon/blob_sidecars/{block_id}", get(handlers::handle_get_blob_sidecars)) .route("/eth/v1/beacon/blobs/{block_id}", get(handlers::handle_get_blobs)) .route("/eth/v1/beacon/genesis", get(handlers::handle_get_genesis)) .with_state(api) } ================================================ FILE: crates/anvil/src/server/beacon/utils.rs ================================================ use hyper::HeaderMap; /// Helper function to determine if the Accept header indicates a preference for SSZ (octet-stream) /// over JSON. pub fn must_be_ssz(headers: &HeaderMap) -> bool { headers .get(axum::http::header::ACCEPT) .and_then(|v| v.to_str().ok()) .map(|accept_str| { let mut octet_stream_q = 0.0; let mut json_q = 0.0; // Parse each media type in the Accept header for media_type in accept_str.split(',') { let media_type = media_type.trim(); let quality = media_type .split(';') .find_map(|param| { let param = param.trim(); if let Some(q) = param.strip_prefix("q=") { q.parse::().ok() } else { None } }) .unwrap_or(1.0); // Default quality factor is 1.0 if media_type.starts_with("application/octet-stream") { octet_stream_q = quality; } else if media_type.starts_with("application/json") { json_q = quality; } } // Prefer octet-stream if it has higher quality factor octet_stream_q > json_q }) .unwrap_or(false) } ================================================ FILE: crates/anvil/src/server/mod.rs ================================================ //! This module provides the infrastructure to launch an Ethereum JSON-RPC server //! (via HTTP, WebSocket, and IPC) and Beacon Node REST API. use crate::{EthApi, IpcTask}; use anvil_server::{ServerConfig, ipc::IpcEndpoint}; use axum::Router; use foundry_primitives::FoundryNetwork; use futures::StreamExt; use rpc_handlers::{HttpEthRpcHandler, PubSubEthRpcHandler}; use std::{io, net::SocketAddr, pin::pin}; use tokio::net::TcpListener; mod beacon; mod rpc_handlers; /// Configures a server that handles [`EthApi`] related JSON-RPC calls via HTTP and WS. /// /// The returned future creates a new server, binding it to the given address, which returns another /// future that runs it. pub async fn serve( addr: SocketAddr, api: EthApi, config: ServerConfig, ) -> io::Result>> { let tcp_listener = TcpListener::bind(addr).await?; Ok(serve_on(tcp_listener, api, config)) } /// Configures a server that handles [`EthApi`] related JSON-RPC calls via HTTP and WS. pub async fn serve_on( tcp_listener: TcpListener, api: EthApi, config: ServerConfig, ) -> io::Result<()> { axum::serve(tcp_listener, router(api, config).into_make_service()).await } /// Configures an [`axum::Router`] that handles [`EthApi`] related JSON-RPC calls via HTTP and WS, /// and Beacon REST API calls. pub fn router(api: EthApi, config: ServerConfig) -> Router { let http = HttpEthRpcHandler::new(api.clone()); let ws = PubSubEthRpcHandler::new(api.clone()); // JSON-RPC router let rpc_router = anvil_server::http_ws_router(config, http, ws); // Beacon REST API router let beacon_router = beacon::router(api); // Merge the routers rpc_router.merge(beacon_router) } /// Launches an ipc server at the given path in a new task /// /// # Panics /// /// Panics if setting up the IPC connection was unsuccessful. #[track_caller] pub fn spawn_ipc(api: EthApi, path: String) -> IpcTask { try_spawn_ipc(api, path).expect("failed to establish ipc connection") } /// Launches an ipc server at the given path in a new task. pub fn try_spawn_ipc(api: EthApi, path: String) -> io::Result { let handler = PubSubEthRpcHandler::new(api); let ipc = IpcEndpoint::new(handler, path); let incoming = ipc.incoming()?; let task = tokio::task::spawn(async move { let mut incoming = pin!(incoming); while let Some(stream) = incoming.next().await { trace!(target: "ipc", "new ipc connection"); tokio::task::spawn(stream); } }); Ok(task) } ================================================ FILE: crates/anvil/src/server/rpc_handlers.rs ================================================ //! Contains RPC handlers use crate::{ EthApi, eth::error::to_rpc_result, pubsub::{EthSubscription, LogsSubscription}, }; use alloy_rpc_types::{ FilteredParams, pubsub::{Params, SubscriptionKind}, }; use anvil_core::eth::{EthPubSub, EthRequest, EthRpcCall, subscription::SubscriptionId}; use anvil_rpc::{error::RpcError, response::ResponseResult}; use anvil_server::{PubSubContext, PubSubRpcHandler, RpcHandler}; use foundry_primitives::FoundryNetwork; /// A `RpcHandler` that expects `EthRequest` rpc calls via http #[derive(Clone)] pub struct HttpEthRpcHandler { /// Access to the node api: EthApi, } impl HttpEthRpcHandler { /// Creates a new instance of the handler using the given `EthApi` pub fn new(api: EthApi) -> Self { Self { api } } } #[async_trait::async_trait] impl RpcHandler for HttpEthRpcHandler { type Request = EthRequest; async fn on_request(&self, request: Self::Request) -> ResponseResult { self.api.execute(request).await } } /// A `RpcHandler` that expects `EthRequest` rpc calls and `EthPubSub` via pubsub connection #[derive(Clone)] pub struct PubSubEthRpcHandler { /// Access to the node api: EthApi, } impl PubSubEthRpcHandler { /// Creates a new instance of the handler using the given `EthApi` pub fn new(api: EthApi) -> Self { Self { api } } /// Invoked for an ethereum pubsub rpc call async fn on_pub_sub(&self, pubsub: EthPubSub, cx: PubSubContext) -> ResponseResult { let id = SubscriptionId::random_hex(); trace!(target: "rpc::ws", "received pubsub request {:?}", pubsub); match pubsub { EthPubSub::EthUnSubscribe(id) => { trace!(target: "rpc::ws", "canceling subscription {:?}", id); let canceled = cx.remove_subscription(&id).is_some(); ResponseResult::Success(canceled.into()) } EthPubSub::EthSubscribe(kind, raw_params) => { let filter = match &*raw_params { Params::None => None, Params::Logs(filter) => Some(filter.clone()), Params::Bool(_) => None, }; 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(); EthSubscription::Logs(Box::new(LogsSubscription { blocks, storage, filter: params, queued: Default::default(), id: id.clone(), })) } SubscriptionKind::NewHeads => { trace!(target: "rpc::ws", "received header subscription"); let blocks = self.api.new_block_notifications(); let storage = self.api.storage_info(); EthSubscription::Header(blocks, storage, id.clone()) } SubscriptionKind::NewPendingTransactions => { trace!(target: "rpc::ws", "received pending transactions subscription"); 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(); } }; cx.add_subscription(id.clone(), subscription); trace!(target: "rpc::ws", "created new subscription: {:?}", id); to_rpc_result(id) } } } } #[async_trait::async_trait] impl PubSubRpcHandler for PubSubEthRpcHandler { type Request = EthRpcCall; type SubscriptionId = SubscriptionId; type Subscription = EthSubscription; async fn on_request(&self, request: Self::Request, cx: PubSubContext) -> ResponseResult { trace!(target: "rpc", "received pubsub request {:?}", request); match request { EthRpcCall::Request(request) => self.api.execute(*request).await, EthRpcCall::PubSub(pubsub) => self.on_pub_sub(pubsub, cx).await, } } } ================================================ FILE: crates/anvil/src/service.rs ================================================ //! background service use crate::{ NodeResult, eth::{ backend::validate::TransactionValidator, fees::FeeHistoryService, miner::Miner, pool::{Pool, transactions::PoolTransaction}, }, filter::Filters, mem::{Backend, storage::MinedBlockOutcome}, }; use alloy_network::Network; use foundry_primitives::{FoundryReceiptEnvelope, FoundryTxEnvelope}; use futures::{FutureExt, Stream, StreamExt}; use std::{ collections::VecDeque, pin::Pin, sync::Arc, task::{Context, Poll}, }; use tokio::{task::JoinHandle, time::Interval}; /// The type that drives the blockchain's state /// /// This service is basically an endless future that continuously polls the miner which returns /// transactions for the next block, then those transactions are handed off to the backend to /// construct a new block, if all transactions were successfully included in a new block they get /// purged from the `Pool`. pub struct NodeService { /// The pool that holds all transactions. pool: Arc>, /// Creates new blocks. block_producer: BlockProducer, /// The miner responsible to select transactions from the `pool`. miner: Miner, /// Maintenance task for fee history related tasks. fee_history: FeeHistoryService, /// Tracks all active filters filters: Filters, /// The interval at which to check for filters that need to be evicted filter_eviction_interval: Interval, } impl NodeService where Backend: TransactionValidator, N: Network, { pub fn new( pool: Arc>, backend: Arc>, miner: Miner, fee_history: FeeHistoryService, filters: Filters, ) -> Self { let start = tokio::time::Instant::now() + filters.keep_alive(); let filter_eviction_interval = tokio::time::interval_at(start, filters.keep_alive()); Self { pool, block_producer: BlockProducer::new(backend), miner, fee_history, filter_eviction_interval, filters, } } } impl Future for NodeService where Backend: TransactionValidator, N: Network, { type Output = NodeResult<()>; fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { let pin = self.get_mut(); // this drives block production and feeds new sets of ready transactions to the block // producer loop { // advance block production until pending while let Poll::Ready(Some(outcome)) = pin.block_producer.poll_next_unpin(cx) { trace!(target: "node", "mined block {}", outcome.block_number); // prune the transactions from the pool pin.pool.on_mined_block(outcome); } if let Poll::Ready(transactions) = pin.miner.poll(&pin.pool, cx) { // miner returned a set of transaction that we feed to the producer pin.block_producer.queued.push_back(transactions); } else { // no progress made break; } } // poll the fee history task let _ = pin.fee_history.poll_unpin(cx); if pin.filter_eviction_interval.poll_tick(cx).is_ready() { let filters = pin.filters.clone(); // evict filters that timed out tokio::task::spawn(async move { filters.evict().await }); } Poll::Pending } } type MiningResult = (MinedBlockOutcome<::TxEnvelope>, Arc>); /// A type that exclusively mines one block at a time #[must_use = "streams do nothing unless polled"] struct BlockProducer { /// Holds the backend if no block is being mined idle_backend: Option>>, /// Single active future that mines a new block block_mining: Option>>, /// backlog of sets of transactions ready to be mined queued: VecDeque>>>, } impl BlockProducer where Backend: TransactionValidator, N: Network, { fn new(backend: Arc>) -> Self { Self { idle_backend: Some(backend), block_mining: None, queued: Default::default() } } } impl Stream for BlockProducer where Backend: TransactionValidator + Send + Sync + 'static, N: Network + 'static, { type Item = MinedBlockOutcome; fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { let pin = self.get_mut(); if !pin.queued.is_empty() { // only spawn a building task if there's none in progress already if let Some(backend) = pin.idle_backend.take() { let transactions = pin.queued.pop_front().expect("not empty; qed"); // we spawn this on as blocking task because this can be blocking for a while in // forking mode, because of all the rpc calls to fetch the required state let handle = tokio::runtime::Handle::current(); let mining = tokio::task::spawn_blocking(move || { handle.block_on(async move { trace!(target: "miner", "creating new block"); let block = backend.mine_block(transactions).await; trace!(target: "miner", "created new block: {}", block.block_number); (block, backend) }) }); pin.block_mining = Some(mining); } } if let Some(mut mining) = pin.block_mining.take() { if let Poll::Ready(res) = mining.poll_unpin(cx) { return match res { Ok((outcome, backend)) => { pin.idle_backend = Some(backend); Poll::Ready(Some(outcome)) } Err(err) => { panic!("miner task failed: {err}"); } }; } else { pin.block_mining = Some(mining) } } Poll::Pending } } ================================================ FILE: crates/anvil/src/shutdown.rs ================================================ //! Helper for shutdown signals use futures::{ FutureExt, channel::oneshot, future::{FusedFuture, Shared}, }; use std::{ pin::Pin, task::{Context, Poll}, }; /// Future that resolves when the shutdown event has fired #[derive(Clone)] pub struct Shutdown(Shared>); impl Future for Shutdown { type Output = (); fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { let pin = self.get_mut(); if pin.0.is_terminated() || pin.0.poll_unpin(cx).is_ready() { Poll::Ready(()) } else { Poll::Pending } } } /// Shutdown signal that fires either manually or on drop by closing the channel pub struct Signal(oneshot::Sender<()>); impl Signal { /// Fire the signal manually. pub fn fire(self) -> Result<(), ()> { self.0.send(()) } } /// Create a channel pair that's used to propagate shutdown event pub fn signal() -> (Signal, Shutdown) { let (sender, receiver) = oneshot::channel(); (Signal(sender), Shutdown(receiver.shared())) } ================================================ FILE: crates/anvil/src/tasks/block_listener.rs ================================================ //! A task that listens for new blocks use crate::shutdown::Shutdown; use futures::{FutureExt, Stream, StreamExt}; use std::{ pin::Pin, task::{Context, Poll}, }; /// A Future that will execute a given `task` for each new block that arrives on the stream. pub struct BlockListener { stream: St, task_factory: F, task: Option>>, on_shutdown: Shutdown, } impl BlockListener where St: Stream, F: Fn(::Item) -> Fut, { pub fn new(on_shutdown: Shutdown, block_stream: St, task_factory: F) -> Self { Self { stream: block_stream, task_factory, task: None, on_shutdown } } } impl Future for BlockListener where St: Stream + Unpin, F: Fn(::Item) -> Fut + Unpin + Send + Sync + 'static, Fut: Future + Send, { type Output = (); fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { let pin = self.get_mut(); if pin.on_shutdown.poll_unpin(cx).is_ready() { return Poll::Ready(()); } let mut block = None; // drain the stream while let Poll::Ready(maybe_block) = pin.stream.poll_next_unpin(cx) { if maybe_block.is_none() { // stream complete return Poll::Ready(()); } block = maybe_block; } if let Some(block) = block { pin.task = Some(Box::pin((pin.task_factory)(block))); } if let Some(mut task) = pin.task.take() && task.poll_unpin(cx).is_pending() { pin.task = Some(task); } Poll::Pending } } ================================================ FILE: crates/anvil/src/tasks/mod.rs ================================================ //! Task management support #![allow(rustdoc::private_doc_tests)] use crate::{EthApi, shutdown::Shutdown, tasks::block_listener::BlockListener}; use alloy_consensus::BlockHeader; use alloy_network::{BlockResponse, Network}; use alloy_primitives::B256; use alloy_provider::Provider; use alloy_rpc_types::anvil::Forking; use foundry_primitives::FoundryNetwork; use futures::StreamExt; use std::fmt; use tokio::{runtime::Handle, task::JoinHandle}; pub mod block_listener; /// A helper struct for managing additional tokio tasks. #[derive(Clone)] pub struct TaskManager { /// Tokio runtime handle that's used to spawn futures, See [tokio::runtime::Handle]. tokio_handle: Handle, /// A receiver for the shutdown signal on_shutdown: Shutdown, } impl TaskManager { /// Creates a new instance of the task manager pub fn new(tokio_handle: Handle, on_shutdown: Shutdown) -> Self { Self { tokio_handle, on_shutdown } } /// Returns a receiver for the shutdown event pub fn on_shutdown(&self) -> Shutdown { self.on_shutdown.clone() } /// Spawns the given task. pub fn spawn(&self, task: impl Future + Send + 'static) -> JoinHandle<()> { self.tokio_handle.spawn(task) } /// Spawns the blocking task and returns a handle to it. /// /// Returning the `JoinHandle` allows callers to cancel the task or await its completion. pub fn spawn_blocking( &self, task: impl Future + Send + 'static, ) -> JoinHandle<()> { let handle = self.tokio_handle.clone(); self.tokio_handle.spawn_blocking(move || { handle.block_on(task); }) } /// Spawns a new task that listens for new blocks and resets the forked provider for every new /// block /// /// ``` /// use alloy_network::Ethereum; /// use alloy_provider::RootProvider; /// use anvil::{NodeConfig, spawn}; /// /// # async fn t() { /// let endpoint = "http://...."; /// let (api, handle) = spawn(NodeConfig::default().with_eth_rpc_url(Some(endpoint))).await; /// /// let provider = RootProvider::connect(endpoint).await.unwrap(); /// /// handle.task_manager().spawn_reset_on_new_polled_blocks::(provider, api); /// # } /// ``` pub fn spawn_reset_on_new_polled_blocks(&self, provider: P, api: EthApi) where N: Network, P: Provider + Clone + Unpin + 'static, { self.spawn_block_poll_listener(provider.clone(), move |hash| { let provider = provider.clone(); let api = api.clone(); async move { if let Ok(Some(block)) = provider.get_block(hash.into()).await { let _ = api .anvil_reset(Some(Forking { json_rpc_url: None, block_number: Some(block.header().number()), })) .await; } } }) } /// Spawns a new [`BlockListener`] task that listens for new blocks (poll-based) See also /// [`Provider::watch_blocks`] and executes the future the `task_factory` returns for the new /// block hash pub fn spawn_block_poll_listener(&self, provider: P, task_factory: F) where N: Network, P: Provider + 'static, F: Fn(B256) -> Fut + Unpin + Send + Sync + 'static, Fut: Future + Send, { let shutdown = self.on_shutdown.clone(); self.spawn(async move { let blocks = provider .watch_blocks() .await .unwrap() .into_stream() .flat_map(futures::stream::iter); BlockListener::new(shutdown, blocks, task_factory).await; }); } /// Spawns a new task that listens for new blocks and resets the forked provider for every new /// block /// /// ``` /// use alloy_network::Ethereum; /// use alloy_provider::RootProvider; /// use anvil::{NodeConfig, spawn}; /// /// # async fn t() { /// let (api, handle) = spawn(NodeConfig::default().with_eth_rpc_url(Some("http://...."))).await; /// /// let provider = RootProvider::connect("ws://...").await.unwrap(); /// /// handle.task_manager().spawn_reset_on_subscribed_blocks::(provider, api); /// /// # } /// ``` pub fn spawn_reset_on_subscribed_blocks(&self, provider: P, api: EthApi) where N: Network, P: Provider + 'static, { self.spawn_block_subscription(provider, move |header: N::HeaderResponse| { let api = api.clone(); async move { let _ = api .anvil_reset(Some(Forking { json_rpc_url: None, block_number: Some(header.number()), })) .await; } }) } /// Spawns a new [`BlockListener`] task that listens for new blocks (via subscription) See also /// [`Provider::subscribe_blocks()`] and executes the future the `task_factory` returns for the /// new block hash pub fn spawn_block_subscription(&self, provider: P, task_factory: F) where N: Network, P: Provider + 'static, F: Fn(N::HeaderResponse) -> Fut + Unpin + Send + Sync + 'static, Fut: Future + Send, { let shutdown = self.on_shutdown.clone(); self.spawn(async move { let blocks = provider.subscribe_blocks().await.unwrap().into_stream(); BlockListener::new(shutdown, blocks, task_factory).await; }); } } impl fmt::Debug for TaskManager { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("TaskManager").finish_non_exhaustive() } } ================================================ FILE: crates/anvil/test-data/SimpleStorage.json ================================================ {"abi":[{"inputs":[{"internalType":"string","name":"value","type":"string"}],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"author","type":"address"},{"indexed":true,"internalType":"address","name":"oldAuthor","type":"address"},{"indexed":false,"internalType":"string","name":"oldValue","type":"string"},{"indexed":false,"internalType":"string","name":"newValue","type":"string"}],"name":"ValueChanged","type":"event"},{"inputs":[],"name":"_hashPuzzle","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getValue","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"lastSender","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"value","type":"string"}],"name":"setValue","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"value","type":"string"},{"internalType":"string","name":"value2","type":"string"}],"name":"setValues","outputs":[],"stateMutability":"nonpayable","type":"function"}],"bin":"60806040523480156200001157600080fd5b5060405162000d6a38038062000d6a83398181016040528101906200003791906200030f565b600073ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff167f999b6d464c4e3383c341bdd3a22b02dda8a7e1d69c069d252e35cb2ee2f4a3c36001846040516200009a929190620004c3565b60405180910390a38060019080519060200190620000ba929190620000c2565b5050620004fe565b828054620000d0906200038f565b90600052602060002090601f016020900481019282620000f4576000855562000140565b82601f106200010f57805160ff191683800117855562000140565b8280016001018555821562000140579182015b828111156200013f57825182559160200191906001019062000122565b5b5090506200014f919062000153565b5090565b5b808211156200016e57600081600090555060010162000154565b5090565b6000604051905090565b600080fd5b600080fd5b600080fd5b600080fd5b6000601f19601f8301169050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b620001db8262000190565b810181811067ffffffffffffffff82111715620001fd57620001fc620001a1565b5b80604052505050565b60006200021262000172565b9050620002208282620001d0565b919050565b600067ffffffffffffffff821115620002435762000242620001a1565b5b6200024e8262000190565b9050602081019050919050565b60005b838110156200027b5780820151818401526020810190506200025e565b838111156200028b576000848401525b50505050565b6000620002a8620002a28462000225565b62000206565b905082815260208101848484011115620002c757620002c66200018b565b5b620002d48482856200025b565b509392505050565b600082601f830112620002f457620002f362000186565b5b81516200030684826020860162000291565b91505092915050565b6000602082840312156200032857620003276200017c565b5b600082015167ffffffffffffffff81111562000349576200034862000181565b5b6200035784828501620002dc565b91505092915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b60006002820490506001821680620003a857607f821691505b60208210811415620003bf57620003be62000360565b5b50919050565b600082825260208201905092915050565b60008190508160005260206000209050919050565b60008154620003fa816200038f565b620004068186620003c5565b9450600182166000811462000424576001811462000437576200046e565b60ff19831686526020860193506200046e565b6200044285620003d6565b60005b83811015620004665781548189015260018201915060208101905062000445565b808801955050505b50505092915050565b600081519050919050565b60006200048f8262000477565b6200049b8185620003c5565b9350620004ad8185602086016200025b565b620004b88162000190565b840191505092915050565b60006040820190508181036000830152620004df8185620003eb565b90508181036020830152620004f5818462000482565b90509392505050565b61085c806200050e6000396000f3fe608060405234801561001057600080fd5b50600436106100575760003560e01c8063018ba9911461005c578063209652551461007a578063256fec88146100985780637ffaa4b6146100b657806393a09352146100d2575b600080fd5b6100646100ee565b60405161007191906103bd565b60405180910390f35b6100826100f7565b60405161008f9190610471565b60405180910390f35b6100a0610189565b6040516100ad91906104d4565b60405180910390f35b6100d060048036038101906100cb9190610638565b6101ad565b005b6100ec60048036038101906100e791906106b0565b61021f565b005b60006064905090565b60606001805461010690610728565b80601f016020809104026020016040519081016040528092919081815260200182805461013290610728565b801561017f5780601f106101545761010080835404028352916020019161017f565b820191906000526020600020905b81548152906001019060200180831161016257829003601f168201915b5050505050905090565b60008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b81600190805190602001906101c3929190610301565b5080600290805190602001906101da929190610301565b50336000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055505050565b60008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff167f999b6d464c4e3383c341bdd3a22b02dda8a7e1d69c069d252e35cb2ee2f4a3c360018460405161029f9291906107ef565b60405180910390a380600190805190602001906102bd929190610301565b50336000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555050565b82805461030d90610728565b90600052602060002090601f01602090048101928261032f5760008555610376565b82601f1061034857805160ff1916838001178555610376565b82800160010185558215610376579182015b8281111561037557825182559160200191906001019061035a565b5b5090506103839190610387565b5090565b5b808211156103a0576000816000905550600101610388565b5090565b6000819050919050565b6103b7816103a4565b82525050565b60006020820190506103d260008301846103ae565b92915050565b600081519050919050565b600082825260208201905092915050565b60005b838110156104125780820151818401526020810190506103f7565b83811115610421576000848401525b50505050565b6000601f19601f8301169050919050565b6000610443826103d8565b61044d81856103e3565b935061045d8185602086016103f4565b61046681610427565b840191505092915050565b6000602082019050818103600083015261048b8184610438565b905092915050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b60006104be82610493565b9050919050565b6104ce816104b3565b82525050565b60006020820190506104e960008301846104c5565b92915050565b6000604051905090565b600080fd5b600080fd5b600080fd5b600080fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b61054582610427565b810181811067ffffffffffffffff821117156105645761056361050d565b5b80604052505050565b60006105776104ef565b9050610583828261053c565b919050565b600067ffffffffffffffff8211156105a3576105a261050d565b5b6105ac82610427565b9050602081019050919050565b82818337600083830152505050565b60006105db6105d684610588565b61056d565b9050828152602081018484840111156105f7576105f6610508565b5b6106028482856105b9565b509392505050565b600082601f83011261061f5761061e610503565b5b813561062f8482602086016105c8565b91505092915050565b6000806040838503121561064f5761064e6104f9565b5b600083013567ffffffffffffffff81111561066d5761066c6104fe565b5b6106798582860161060a565b925050602083013567ffffffffffffffff81111561069a576106996104fe565b5b6106a68582860161060a565b9150509250929050565b6000602082840312156106c6576106c56104f9565b5b600082013567ffffffffffffffff8111156106e4576106e36104fe565b5b6106f08482850161060a565b91505092915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b6000600282049050600182168061074057607f821691505b60208210811415610754576107536106f9565b5b50919050565b60008190508160005260206000209050919050565b6000815461077c81610728565b61078681866103e3565b945060018216600081146107a157600181146107b3576107e6565b60ff19831686526020860193506107e6565b6107bc8561075a565b60005b838110156107de578154818901526001820191506020810190506107bf565b808801955050505b50505092915050565b60006040820190508181036000830152610809818561076f565b9050818103602083015261081d8184610438565b9050939250505056fea2646970667358221220e37ed4b56859ad0b3a3773f57ff7b4e7a99406933fc4ff9f8ae053c52cdf3e3264736f6c63430008090033"} ================================================ FILE: crates/anvil/test-data/SimpleStorage.sol ================================================ pragma solidity >=0.4.24; contract SimpleStorage { event ValueChanged(address indexed author, address indexed oldAuthor, string oldValue, string newValue); address public lastSender; string _value; string _otherValue; constructor(string memory value) public { emit ValueChanged(msg.sender, address(0), _value, value); _value = value; } function getValue() view public returns (string memory) { return _value; } function setValue(string memory value) public { emit ValueChanged(msg.sender, lastSender, _value, value); _value = value; lastSender = msg.sender; } function setValues(string memory value, string memory value2) public { _value = value; _otherValue = value2; lastSender = msg.sender; } function _hashPuzzle() public view returns (uint256) { return 100; } } ================================================ FILE: crates/anvil/test-data/emit_logs.json ================================================ {"abi":[{"inputs":[{"internalType":"string","name":"value","type":"string"}],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"author","type":"address"},{"indexed":false,"internalType":"string","name":"oldValue","type":"string"},{"indexed":false,"internalType":"string","name":"newValue","type":"string"}],"name":"ValueChanged","type":"event"},{"inputs":[],"name":"getValue","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"value","type":"string"}],"name":"setValue","outputs":[],"stateMutability":"nonpayable","type":"function"}],"bin":"608060405234801561001057600080fd5b506040516105a63803806105a68339818101604052602081101561003357600080fd5b810190808051604051939291908464010000000082111561005357600080fd5b90830190602082018581111561006857600080fd5b825164010000000081118282018810171561008257600080fd5b82525081516020918201929091019080838360005b838110156100af578181015183820152602001610097565b50505050905090810190601f1680156100dc5780820380516001836020036101000a031916815260200191505b50604052505081516100f6915060009060208401906100fd565b505061019e565b828054600181600116156101000203166002900490600052602060002090601f0160209004810192826101335760008555610179565b82601f1061014c57805160ff1916838001178555610179565b82800160010185558215610179579182015b8281111561017957825182559160200191906001019061015e565b50610185929150610189565b5090565b5b80821115610185576000815560010161018a565b6103f9806101ad6000396000f3fe608060405234801561001057600080fd5b50600436106100365760003560e01c8063209652551461003b57806393a09352146100b8575b600080fd5b610043610160565b6040805160208082528351818301528351919283929083019185019080838360005b8381101561007d578181015183820152602001610065565b50505050905090810190601f1680156100aa5780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b61015e600480360360208110156100ce57600080fd5b8101906020810181356401000000008111156100e957600080fd5b8201836020820111156100fb57600080fd5b8035906020019184600183028401116401000000008311171561011d57600080fd5b91908080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152509295506101f6945050505050565b005b60008054604080516020601f60026000196101006001881615020190951694909404938401819004810282018101909252828152606093909290918301828280156101ec5780601f106101c1576101008083540402835291602001916101ec565b820191906000526020600020905b8154815290600101906020018083116101cf57829003601f168201915b5050505050905090565b60408051818152600080546002600019610100600184161502019091160492820183905233927fe826f71647b8486f2bae59832124c70792fba044036720a54ec8dacdd5df4fcb9285918190602082019060608301908690801561029b5780601f106102705761010080835404028352916020019161029b565b820191906000526020600020905b81548152906001019060200180831161027e57829003601f168201915b5050838103825284518152845160209182019186019080838360005b838110156102cf5781810151838201526020016102b7565b50505050905090810190601f1680156102fc5780820380516001836020036101000a031916815260200191505b5094505050505060405180910390a2805161031e906000906020840190610322565b5050565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282610358576000855561039e565b82601f1061037157805160ff191683800117855561039e565b8280016001018555821561039e579182015b8281111561039e578251825591602001919060010190610383565b506103aa9291506103ae565b5090565b5b808211156103aa57600081556001016103af56fea2646970667358221220c1367a0db85dfe60814cdfc5141a8fe8b95c9d051a6824343085c3ba9697244a64736f6c63430007060033"} ================================================ FILE: crates/anvil/test-data/emit_logs.sol ================================================ pragma solidity >=0.4.24; contract SimpleStorage { event ValueChanged(address indexed author, string oldValue, string newValue); string _value; constructor(string memory value) public { _value = value; } function getValue() view public returns (string memory) { return _value; } function setValue(string memory value) public { emit ValueChanged(msg.sender, _value, value); _value = value; } } ================================================ FILE: crates/anvil/test-data/greeter.json ================================================ {"bytecode":{"object":"608060405234801561001057600080fd5b506040516104913803806104918339818101604052602081101561003357600080fd5b810190808051604051939291908464010000000082111561005357600080fd5b90830190602082018581111561006857600080fd5b825164010000000081118282018810171561008257600080fd5b82525081516020918201929091019080838360005b838110156100af578181015183820152602001610097565b50505050905090810190601f1680156100dc5780820380516001836020036101000a031916815260200191505b50604052505081516100f6915060009060208401906100fd565b505061019e565b828054600181600116156101000203166002900490600052602060002090601f0160209004810192826101335760008555610179565b82601f1061014c57805160ff1916838001178555610179565b82800160010185558215610179579182015b8281111561017957825182559160200191906001019061015e565b50610185929150610189565b5090565b5b80821115610185576000815560010161018a565b6102e4806101ad6000396000f3fe608060405234801561001057600080fd5b50600436106100365760003560e01c8063a41368621461003b578063cfae3217146100e3575b600080fd5b6100e16004803603602081101561005157600080fd5b81019060208101813564010000000081111561006c57600080fd5b82018360208201111561007e57600080fd5b803590602001918460018302840111640100000000831117156100a057600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250929550610160945050505050565b005b6100eb610177565b6040805160208082528351818301528351919283929083019185019080838360005b8381101561012557818101518382015260200161010d565b50505050905090810190601f1680156101525780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b805161017390600090602084019061020d565b5050565b60008054604080516020601f60026000196101006001881615020190951694909404938401819004810282018101909252828152606093909290918301828280156102035780601f106101d857610100808354040283529160200191610203565b820191906000526020600020905b8154815290600101906020018083116101e657829003601f168201915b5050505050905090565b828054600181600116156101000203166002900490600052602060002090601f0160209004810192826102435760008555610289565b82601f1061025c57805160ff1916838001178555610289565b82800160010185558215610289579182015b8281111561028957825182559160200191906001019061026e565b50610295929150610299565b5090565b5b80821115610295576000815560010161029a56fea26469706673582212208b9161dfd195d53618942a72a3b481d61a7b142de919925a0b34f9c986e5707e64736f6c63430007060033"},"abi":[{"inputs":[{"internalType":"string","name":"_greeting","type":"string"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"greet","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"_greeting","type":"string"}],"name":"setGreeting","outputs":[],"stateMutability":"nonpayable","type":"function"}]} ================================================ FILE: crates/anvil/test-data/multicall.json ================================================ {"abi":[{"inputs":[{"components":[{"internalType":"address","name":"target","type":"address"},{"internalType":"bytes","name":"callData","type":"bytes"}],"internalType":"struct Multicall.Call[]","name":"calls","type":"tuple[]"}],"name":"aggregate","outputs":[{"internalType":"uint256","name":"blockNumber","type":"uint256"},{"internalType":"bytes[]","name":"returnData","type":"bytes[]"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"blockNumber","type":"uint256"}],"name":"getBlockHash","outputs":[{"internalType":"bytes32","name":"blockHash","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getCurrentBlockCoinbase","outputs":[{"internalType":"address","name":"coinbase","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getCurrentBlockDifficulty","outputs":[{"internalType":"uint256","name":"difficulty","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getCurrentBlockGasLimit","outputs":[{"internalType":"uint256","name":"gaslimit","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getCurrentBlockTimestamp","outputs":[{"internalType":"uint256","name":"timestamp","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"addr","type":"address"}],"name":"getEthBalance","outputs":[{"internalType":"uint256","name":"balance","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getLastBlockHash","outputs":[{"internalType":"bytes32","name":"blockHash","type":"bytes32"}],"stateMutability":"view","type":"function"}],"bin":"608060405234801561001057600080fd5b50610abb806100206000396000f3fe608060405234801561001057600080fd5b50600436106100885760003560e01c806372425d9d1161005b57806372425d9d1461012a57806386d516e814610148578063a8b0574e14610166578063ee82ac5e1461018457610088565b80630f28c97d1461008d578063252dba42146100ab57806327e86d6e146100dc5780634d2301cc146100fa575b600080fd5b6100956101b4565b6040516100a29190610381565b60405180910390f35b6100c560048036038101906100c091906106b0565b6101bc565b6040516100d3929190610843565b60405180910390f35b6100e461030f565b6040516100f1919061088c565b60405180910390f35b610114600480360381019061010f91906108a7565b610324565b6040516101219190610381565b60405180910390f35b610132610345565b60405161013f9190610381565b60405180910390f35b61015061034d565b60405161015d9190610381565b60405180910390f35b61016e610355565b60405161017b91906108e3565b60405180910390f35b61019e6004803603810190610199919061092a565b61035d565b6040516101ab919061088c565b60405180910390f35b600042905090565b60006060439150825167ffffffffffffffff8111156101de576101dd6103c6565b5b60405190808252806020026020018201604052801561021157816020015b60608152602001906001900390816101fc5790505b50905060005b83518110156103095760008085838151811061023657610235610957565b5b60200260200101516000015173ffffffffffffffffffffffffffffffffffffffff1686848151811061026b5761026a610957565b5b60200260200101516020015160405161028491906109c2565b6000604051808303816000865af19150503d80600081146102c1576040519150601f19603f3d011682016040523d82523d6000602084013e6102c6565b606091505b5091509150816102d557600080fd5b808484815181106102e9576102e8610957565b5b60200260200101819052505050808061030190610a08565b915050610217565b50915091565b600060014361031e9190610a51565b40905090565b60008173ffffffffffffffffffffffffffffffffffffffff16319050919050565b600044905090565b600045905090565b600041905090565b600081409050919050565b6000819050919050565b61037b81610368565b82525050565b60006020820190506103966000830184610372565b92915050565b6000604051905090565b600080fd5b600080fd5b600080fd5b6000601f19601f8301169050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b6103fe826103b5565b810181811067ffffffffffffffff8211171561041d5761041c6103c6565b5b80604052505050565b600061043061039c565b905061043c82826103f5565b919050565b600067ffffffffffffffff82111561045c5761045b6103c6565b5b602082029050602081019050919050565b600080fd5b600080fd5b600080fd5b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b60006104a78261047c565b9050919050565b6104b78161049c565b81146104c257600080fd5b50565b6000813590506104d4816104ae565b92915050565b600080fd5b600067ffffffffffffffff8211156104fa576104f96103c6565b5b610503826103b5565b9050602081019050919050565b82818337600083830152505050565b600061053261052d846104df565b610426565b90508281526020810184848401111561054e5761054d6104da565b5b610559848285610510565b509392505050565b600082601f830112610576576105756103b0565b5b813561058684826020860161051f565b91505092915050565b6000604082840312156105a5576105a4610472565b5b6105af6040610426565b905060006105bf848285016104c5565b600083015250602082013567ffffffffffffffff8111156105e3576105e2610477565b5b6105ef84828501610561565b60208301525092915050565b600061060e61060984610441565b610426565b905080838252602082019050602084028301858111156106315761063061046d565b5b835b8181101561067857803567ffffffffffffffff811115610656576106556103b0565b5b808601610663898261058f565b85526020850194505050602081019050610633565b5050509392505050565b600082601f830112610697576106966103b0565b5b81356106a78482602086016105fb565b91505092915050565b6000602082840312156106c6576106c56103a6565b5b600082013567ffffffffffffffff8111156106e4576106e36103ab565b5b6106f084828501610682565b91505092915050565b600081519050919050565b600082825260208201905092915050565b6000819050602082019050919050565b600081519050919050565b600082825260208201905092915050565b60005b8381101561075f578082015181840152602081019050610744565b8381111561076e576000848401525b50505050565b600061077f82610725565b6107898185610730565b9350610799818560208601610741565b6107a2816103b5565b840191505092915050565b60006107b98383610774565b905092915050565b6000602082019050919050565b60006107d9826106f9565b6107e38185610704565b9350836020820285016107f585610715565b8060005b85811015610831578484038952815161081285826107ad565b945061081d836107c1565b925060208a019950506001810190506107f9565b50829750879550505050505092915050565b60006040820190506108586000830185610372565b818103602083015261086a81846107ce565b90509392505050565b6000819050919050565b61088681610873565b82525050565b60006020820190506108a1600083018461087d565b92915050565b6000602082840312156108bd576108bc6103a6565b5b60006108cb848285016104c5565b91505092915050565b6108dd8161049c565b82525050565b60006020820190506108f860008301846108d4565b92915050565b61090781610368565b811461091257600080fd5b50565b600081359050610924816108fe565b92915050565b6000602082840312156109405761093f6103a6565b5b600061094e84828501610915565b91505092915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b600081905092915050565b600061099c82610725565b6109a68185610986565b93506109b6818560208601610741565b80840191505092915050565b60006109ce8284610991565b915081905092915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b6000610a1382610368565b91507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff821415610a4657610a456109d9565b5b600182019050919050565b6000610a5c82610368565b9150610a6783610368565b925082821015610a7a57610a796109d9565b5b82820390509291505056fea2646970667358221220e5023d90063e0939116a41565414721ba1350cd3e98b12e7b65983a039644df964736f6c634300080a0033"} ================================================ FILE: crates/anvil/test-data/multicall.sol ================================================ /** *Submitted for verification at Etherscan.io on 2019-06-10 */ pragma solidity >=0.5.0; pragma experimental ABIEncoderV2; /// @title Multicall - Aggregate results from multiple read-only function calls /// @author Michael Elliot /// @author Joshua Levine /// @author Nick Johnson contract Multicall { struct Call { address target; bytes callData; } function aggregate(Call[] memory calls) public returns (uint256 blockNumber, bytes[] memory returnData) { blockNumber = block.number; returnData = new bytes[](calls.length); for(uint256 i = 0; i < calls.length; i++) { (bool success, bytes memory ret) = calls[i].target.call(calls[i].callData); require(success); returnData[i] = ret; } } // Helper functions function getEthBalance(address addr) public view returns (uint256 balance) { balance = addr.balance; } function getBlockHash(uint256 blockNumber) public view returns (bytes32 blockHash) { blockHash = blockhash(blockNumber); } function getLastBlockHash() public view returns (bytes32 blockHash) { blockHash = blockhash(block.number - 1); } function getCurrentBlockTimestamp() public view returns (uint256 timestamp) { timestamp = block.timestamp; } function getCurrentBlockDifficulty() public view returns (uint256 difficulty) { difficulty = block.difficulty; } function getCurrentBlockGasLimit() public view returns (uint256 gaslimit) { gaslimit = block.gaslimit; } function getCurrentBlockCoinbase() public view returns (address coinbase) { coinbase = block.coinbase; } } ================================================ FILE: crates/anvil/test-data/state-dump-legacy-stress.json ================================================ {"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":[{"chainId":"0x343a","nonce":"0x0","gas":"0x3ea90d","maxFeePerGas":"0x83215600","maxPriorityFeePerGas":"0x3b9aca00","value":"0x0","accessList":[],"input":"0x608060405234801561001057600080fd5b5061494f806100206000396000f3fe608060405234801561001057600080fd5b50600436106100575760003560e01c80632a952b2d1461005c57806350c946fe14610085578063625ca21c146100a5578063daa250be146100c6578063deba1b98146100d9575b600080fd5b61006f61006a366004613a63565b6100ec565b60405161007c9190613a7c565b60405180910390f35b610098610093366004613a63565b61011c565b60405161007c9190613b21565b6100b86100b3366004613c92565b610276565b60405190815260200161007c565b61006f6100d4366004613d5f565b6102bb565b6100b86100e7366004613c92565b6102d6565b6100f46139e4565b6040805160008082526020820190815281830190925261011691849190610310565b92915050565b6101416040805160608101909152806000815260200160608152602001606081525090565b61014a82610ab6565b60408051606081019091528154909190829060ff16600981111561017057610170613aa7565b600981111561018157610181613aa7565b815260200160018201805461019590613dc2565b80601f01602080910402602001604051908101604052809291908181526020018280546101c190613dc2565b801561020e5780601f106101e35761010080835404028352916020019161020e565b820191906000526020600020905b8154815290600101906020018083116101f157829003601f168201915b505050505081526020016002820180548060200260200160405190810160405280929190818152602001828054801561026657602002820191906000526020600020905b815481526020019060010190808311610252575b5050505050815250509050919050565b600080604051806060016040528086600981111561029657610296613aa7565b81526020018581526020018481525090506102b081610ac1565b9150505b9392505050565b6102c36139e4565b6102ce848484610310565b949350505050565b60008060405180606001604052808660098111156102f6576102f6613aa7565b81526020018581526020018481525090506102b081610acc565b6103186139e4565b81518351146103a05760408051634bab873760e11b81526004810191909152600d60448201526c72756e74696d6556616c75657360981b606482015260806024820152602260848201527f6d7573742062652073616d65206c656e6774682061732072756e74696d654b6560a482015261797360f01b60c482015260e4015b60405180910390fd5b60006103ab85610c26565b805490915060ff1660018160098111156103c7576103c7613aa7565b036104755761046c6103da838787610c84565b8360010180546103e990613dc2565b80601f016020809104026020016040519081016040528092919081815260200182805461041590613dc2565b80156104625780601f1061043757610100808354040283529160200191610462565b820191906000526020600020905b81548152906001019060200180831161044557829003601f168201915b5050505050610d46565b925050506102b4565b600281600981111561048957610489613aa7565b036105305761046c61049c838787610c84565b8360010180546104ab90613dc2565b80601f01602080910402602001604051908101604052809291908181526020018280546104d790613dc2565b80156105245780601f106104f957610100808354040283529160200191610524565b820191906000526020600020905b81548152906001019060200180831161050757829003601f168201915b50505050508787610ebb565b600381600981111561054457610544613aa7565b036105de5761046c82600101805461055b90613dc2565b80601f016020809104026020016040519081016040528092919081815260200182805461058790613dc2565b80156105d45780601f106105a9576101008083540402835291602001916105d4565b820191906000526020600020905b8154815290600101906020018083116105b757829003601f168201915b5050505050610f59565b60048160098111156105f2576105f2613aa7565b0361068c5761046c82600101805461060990613dc2565b80601f016020809104026020016040519081016040528092919081815260200182805461063590613dc2565b80156106825780601f1061065757610100808354040283529160200191610682565b820191906000526020600020905b81548152906001019060200180831161066557829003601f168201915b5050505050611087565b60058160098111156106a0576106a0613aa7565b0361073a5761046c8260010180546106b790613dc2565b80601f01602080910402602001604051908101604052809291908181526020018280546106e390613dc2565b80156107305780601f1061070557610100808354040283529160200191610730565b820191906000526020600020905b81548152906001019060200180831161071357829003601f168201915b505050505061131e565b600981600981111561074e5761074e613aa7565b036107ea5761046c82600101805461076590613dc2565b80601f016020809104026020016040519081016040528092919081815260200182805461079190613dc2565b80156107de5780601f106107b3576101008083540402835291602001916107de565b820191906000526020600020905b8154815290600101906020018083116107c157829003601f168201915b505050505086866114b5565b60068160098111156107fe576107fe613aa7565b036108a35761046c610811838787610c84565b83600101805461082090613dc2565b80601f016020809104026020016040519081016040528092919081815260200182805461084c90613dc2565b80156108995780601f1061086e57610100808354040283529160200191610899565b820191906000526020600020905b81548152906001019060200180831161087c57829003601f168201915b50505050506115c7565b60078160098111156108b7576108b7613aa7565b036109ec576040805160608101909152825461046c91908490829060ff1660098111156108e6576108e6613aa7565b60098111156108f7576108f7613aa7565b815260200160018201805461090b90613dc2565b80601f016020809104026020016040519081016040528092919081815260200182805461093790613dc2565b80156109845780601f1061095957610100808354040283529160200191610984565b820191906000526020600020905b81548152906001019060200180831161096757829003601f168201915b50505050508152602001600282018054806020026020016040519081016040528092919081815260200182805480156109dc57602002820191906000526020600020905b8154815260200190600101908083116109c8575b5050505050815250508686611728565b6008816009811115610a0057610a00613aa7565b03610a9a5761046c826001018054610a1790613dc2565b80601f0160208091040260200160405190810160405280929190818152602001828054610a4390613dc2565b8015610a905780601f10610a6557610100808354040283529160200191610a90565b820191906000526020600020905b815481529060010190602001808311610a7357829003601f168201915b50505050506118a5565b6040516323a9bbc960e01b815260048101879052602401610397565b600061011682610c26565b6000610116826118ea565b6000610ad782610ac1565b9050610ae28161192a565b15610b35577fcb64985827770858ec421ad26da7e558c757541643036ce44d6b4eb9e8e5dc5e81836000015184602001518560400151604051610b289493929190613e32565b60405180910390a1919050565b610b3e82611a8c565b610b5d578160405163382bbbc960e11b81526004016103979190613b21565b60005b826040015151811015610bd957610b9383604001518281518110610b8657610b86613e6a565b602002602001015161192a565b610bd15782604001518181518110610bad57610bad613e6a565b6020026020010151604051632f19f96160e11b815260040161039791815260200190565b600101610b60565b50610be382611c31565b8351602085015160408087015190519395507fcb64985827770858ec421ad26da7e558c757541643036ce44d6b4eb9e8e5dc5e9450610b28938693929190613e32565b604080516020808201839052606082018190527f696f2e73796e7468657469782e6f7261636c652d6d616e616765722e4e6f6465608080840191909152828401949094528251808303909401845260a0909101909152815191012090565b600283015460609067ffffffffffffffff811115610ca457610ca4613b9a565b604051908082528060200260200182016040528015610cdd57816020015b610cca6139e4565b815260200190600190039081610cc25790505b50905060005b6002850154811015610d3e57610d19856002018281548110610d0757610d07613e6a565b90600052602060002001548585610310565b828281518110610d2b57610d2b613e6a565b6020908102919091010152600101610ce3565b509392505050565b610d4e6139e4565b600082806020019051810190610d649190613e80565b90506000816008811115610d7a57610d7a613aa7565b03610d9057610d8884611ca5565b915050610116565b6001816008811115610da457610da4613aa7565b03610db257610d8884611d0d565b6002816008811115610dc657610dc6613aa7565b03610dd457610d8884611d90565b6003816008811115610de857610de8613aa7565b03610df657610d8884611e13565b6004816008811115610e0a57610e0a613aa7565b03610e1857610d8884611ec9565b6005816008811115610e2c57610e2c613aa7565b03610e3a57610d8884612009565b6006816008811115610e4e57610e4e613aa7565b03610e5c57610d88846120e4565b6007816008811115610e7057610e70613aa7565b03610e7e57610d888461220c565b6008816008811115610e9257610e92613aa7565b03610ea057610d88846122ce565b80604051631be413d360e11b81526004016103979190613ea1565b610ec36139e4565b600084806020019051810190610ed99190613ed3565b604051631ecba7c360e31b81529091506001600160a01b0382169063f65d3e1890610f0e908990899089908990600401613ef0565b608060405180830381865afa158015610f2b573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610f4f9190613f91565b9695505050505050565b610f616139e4565b600080600084806020019051810190610f7a9190613fe8565b92509250925060008390506000806000836001600160a01b031663feaf968c6040518163ffffffff1660e01b815260040160a060405180830381865afa158015610fc8573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610fec9190614041565b509350509250925060008660001461100f5761100a8585858a6123c7565b611011565b825b905060128660ff161161103b5761103661102f60ff881660126140a7565b82906124c2565b611053565b61105361104c601260ff89166140a7565b82906124dc565b9050604051806080016040528082815260200183815260200160008152602001600081525098505050505050505050919050565b61108f6139e4565b600080600080600080878060200190518101906110ac91906140ba565b604080516002808252606082018352979d50959b50939950919750955093506000929060208301908036833701905050905081816000815181106110f2576110f2613e6a565b602002602001019063ffffffff16908163ffffffff168152505060008160018151811061112157611121613e6a565b63ffffffff9092166020928302919091019091015260405163883bdbfd60e01b81526000906001600160a01b0385169063883bdbfd90611165908590600401614143565b600060405180830381865afa158015611182573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f191682016040526111aa91908101906141f5565b5090506000816000815181106111c2576111c2613e6a565b6020026020010151826001815181106111dd576111dd613e6a565b60200260200101516111ef91906142c1565b9050600061121761120563ffffffff87166124f6565b61120f9084614304565b60060b61252d565b905060008260060b12801561124c575061123b63ffffffff8616612569565b612569565b8260060b6112499190614342565b15155b1561125f578061125b81614356565b9150505b600061126d6012600a61445d565b9050600061128061123684848f8f612593565b905060006112908a60ff16612569565b61129c8c60ff16612569565b6112a6919061446c565b905060008082136112d1576112cc6112c56112c084614493565b612686565b84906124dc565b6112e4565b6112e46112dd83612686565b84906124c2565b905060405180608001604052808281526020014281526020016000815260200160008152509e505050505050505050505050505050919050565b6113266139e4565b60008060008480602001905181019061133f91906144bf565b91945092509050826000826113bc576040516396834ad360e01b8152600481018590526001600160a01b038316906396834ad390602401608060405180830381865afa158015611393573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906113b791906144f5565b611425565b604051639474f45b60e01b8152600481018590526001600160a01b03831690639474f45b90602401608060405180830381865afa158015611401573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061142591906144f5565b90506000816040015160030b601261143d919061456f565b90506000808213611467576114626114576112c084614493565b845160070b906124dc565b61147e565b61147e61147383612686565b845160070b906124c2565b9050604051806080016040528082815260200184606001518152602001600081526020016000815250975050505050505050919050565b6114bd6139e4565b6000806000868060200190518101906114d69190614597565b92509250925060005b8651811015611545578681815181106114fa576114fa613e6a565b6020026020010151717374616c656e657373546f6c6572616e636560701b0361153d5785818151811061152f5761152f613e6a565b602002602001015160001c91505b6001016114df565b5060408051600180825281830190925260009160208083019080368337019050509050828160008151811061157c5761157c613e6a565b602002602001018181525050836001838360405160200161159f939291906145ce565b60408051601f198184030181529082905263cf2cabdf60e01b82526103979291600401614603565b6115cf6139e4565b6000828060200190518101906115e59190614627565b90506000846000815181106115fc576115fc613e6a565b602002602001015160000151905060008560018151811061161f5761161f613e6a565b6020026020010151600001519050808214611702576000611653601261164d611648858761446c565b6126a9565b906124c2565b905082158061167b5750611666836126a9565b6116709082614640565b61167985612569565b125b15611700576002875111156116b0578660028151811061169d5761169d613e6a565b6020026020010151945050505050610116565b826000036116d15760405163014cc07160e01b815260040160405180910390fd5b6116da836126a9565b6116e49082614640565b60405163dcac091960e01b815260040161039791815260200190565b505b8560008151811061171557611715613e6a565b6020026020010151935050505092915050565b6117306139e4565b6000846020015180602001905181019061174a9190614627565b905060005b84518110156117bc5784818151811061176a5761176a613e6a565b6020026020010151717374616c656e657373546f6c6572616e636560701b036117b4576117ad8482815181106117a2576117a2613e6a565b602002602001015190565b91506117bc565b60010161174f565b50600085604001516000815181106117d6576117d6613e6a565b6020026020010151905060006117ed828787610310565b60208101519091506117ff84426140a7565b1161180e5792506102b4915050565b86604001515160010361187157866040015160008151811061183257611832613e6a565b602002602001015181600001518260200151604051631808066560e21b8152600401610397939291909283526020830191909152604082015260600190565b61189a876040015160018151811061188b5761188b613e6a565b60200260200101518787610310565b979650505050505050565b6118ad6139e4565b6040518060800160405280838060200190518101906118cc9190614627565b81526020014281526020016000815260200160008152509050919050565b600081600001518260200151836040015160405160200161190d9392919061466e565b604051602081830303815290604052805190602001209050919050565b60008061193683610c26565b60408051606081019091528154909190829060ff16600981111561195c5761195c613aa7565b600981111561196d5761196d613aa7565b815260200160018201805461198190613dc2565b80601f01602080910402602001604051908101604052809291908181526020018280546119ad90613dc2565b80156119fa5780601f106119cf576101008083540402835291602001916119fa565b820191906000526020600020905b8154815290600101906020018083116119dd57829003601f168201915b5050505050815260200160028201805480602002602001604051908101604052809291908181526020018280548015611a5257602002820191906000526020600020905b815481526020019060010190808311611a3e575b505050505081525050905060006009811115611a7057611a70613aa7565b81516009811115611a8357611a83613aa7565b14159392505050565b6000600182516009811115611aa357611aa3613aa7565b1480611ac15750600682516009811115611abf57611abf613aa7565b145b80611ade5750600782516009811115611adc57611adc613aa7565b145b15611aee57611aec826126c1565b505b600182516009811115611b0357611b03613aa7565b03611b11576101168261284a565b600282516009811115611b2657611b26613aa7565b03611b3457610116826128a5565b600382516009811115611b4957611b49613aa7565b03611b575761011682612973565b600482516009811115611b6c57611b6c613aa7565b03611b7a5761011682612aae565b600582516009811115611b8f57611b8f613aa7565b03611b9d5761011682612e92565b600982516009811115611bb257611bb2613aa7565b03611bc05761011682612fcb565b600682516009811115611bd557611bd5613aa7565b03611be3576101168261300e565b600782516009811115611bf857611bf8613aa7565b03611c065761011682613052565b600882516009811115611c1b57611c1b613aa7565b03611c295761011682613078565b506000919050565b600080611c3d836118ea565b9050611c4881610c26565b8351815491935090839060ff19166001836009811115611c6a57611c6a613aa7565b021790555060208301516001830190611c8390826146ed565b5060408301518051611c9f916002850191602090910190613a0c565b50915091565b611cad6139e4565b60005b8251811015611d07578160200151838281518110611cd057611cd0613e6a565b6020026020010151602001511115611cff57828181518110611cf457611cf4613e6a565b602002602001015191505b600101611cb0565b50919050565b611d156139e4565b81600081518110611d2857611d28613e6a565b602002602001015190506000600190505b8251811015611d07578160000151838281518110611d5957611d59613e6a565b6020026020010151600001511215611d8857828181518110611d7d57611d7d613e6a565b602002602001015191505b600101611d39565b611d986139e4565b81600081518110611dab57611dab613e6a565b602002602001015190506000600190505b8251811015611d07578160000151838281518110611ddc57611ddc613e6a565b6020026020010151600001511315611e0b57828181518110611e0057611e00613e6a565b602002602001015191505b600101611dbc565b611e1b6139e4565b60005b8251811015611e9557828181518110611e3957611e39613e6a565b60200260200101516000015182600001818151611e56919061456f565b9052508251839082908110611e6d57611e6d613e6a565b60200260200101516020015182602001818151611e8a91906147ad565b905250600101611e1e565b50611ea08251612569565b8151611eac9190614640565b815281516020820151611ebf91906147c0565b6020820152919050565b611ed16139e4565b611eed826000611ee86001865161123691906140a7565b6130a4565b60028251611efb91906147d4565b600003611fd65760408051600280825260608201909252600091816020015b611f226139e4565b815260200190600190039081611f1a57905050905082600160028551611f4891906147c0565b611f5291906140a7565b81518110611f6257611f62613e6a565b602002602001015181600081518110611f7d57611f7d613e6a565b60200260200101819052508260028451611f9791906147c0565b81518110611fa757611fa7613e6a565b602002602001015181600181518110611fc257611fc2613e6a565b60200260200101819052506102b481611e13565b8160028351611fe591906147c0565b81518110611ff557611ff5613e6a565b60200260200101519050919050565b919050565b6120116139e4565b8160008151811061202457612024613e6a565b60209081029190910101515181528151829060009061204557612045613e6a565b6020908102919091018101518101519082015260015b82518110156120d25782818151811061207657612076613e6a565b6020026020010151600001518260000181815161209391906147e8565b90525082518390829081106120aa576120aa613e6a565b602002602001015160200151826020018181516120c791906147ad565b90525060010161205b565b5081518160200151611ebf91906147c0565b6120ec6139e4565b816000815181106120ff576120ff613e6a565b60209081029190910101515181528151829060009061212057612120613e6a565b6020908102919091018101518101519082015260015b82518110156120d25782818151811061215157612151613e6a565b60200260200101516000015160000361219e5782818151811061217657612176613e6a565b6020026020010151600001516040516338ee04a760e01b815260040161039791815260200190565b8281815181106121b0576121b0613e6a565b602002602001015160000151826000018181516121cd9190614640565b90525082518390829081106121e4576121e4613e6a565b6020026020010151602001518260200181815161220191906147ad565b905250600101612136565b6122146139e4565b8160008151811061222757612227613e6a565b60209081029190910101515181528151829060009061224857612248613e6a565b6020908102919091018101518101519082015260015b82518110156120d25761229083828151811061227c5761227c613e6a565b602090810291909101015151835190613264565b825282518390829081106122a6576122a6613e6a565b602002602001015160200151826020018181516122c391906147ad565b90525060010161225e565b6122d66139e4565b816000815181106122e9576122e9613e6a565b60209081029190910101515181528151829060009061230a5761230a613e6a565b6020908102919091018101518101519082015260015b82518110156120d25782818151811061233b5761233b613e6a565b6020026020010151600001516000036123605782818151811061217657612176613e6a565b61238983828151811061237557612375613e6a565b602090810291909101015151835190613283565b8252825183908290811061239f5761239f613e6a565b602002602001015160200151826020018181516123bc91906147ad565b905250600101612320565b6000826001826123d785426140a7565b90505b69ffffffffffffffffffff8716156124a3576001600160a01b038816639a6fc8f561240489614818565b6040516001600160e01b031960e084901b16815269ffffffffffffffffffff8216600482015290995060240160a060405180830381865afa925050508015612469575060408051601f3d908101601f1916820190925261246691810190614041565b60015b156124a357858210156124805750505050506124a3565b61248a848961456f565b97508661249681614834565b97505050505050506123da565b6124ac82612569565b6124b69084614640565b98975050505050505050565b60006124d261123683600a61484d565b6102b490846147e8565b60006124ec61123683600a61484d565b6102b49084614640565b6000667fffffffffffff66ffffffffffffff83161115612529576040516329d2678160e21b815260040160405180910390fd5b5090565b6000627fffff19600683900b128061254b5750627fffff600683900b135b1561252957604051630d962f7960e21b815260040160405180910390fd5b60006001600160ff1b038211156125295760405163677c430560e11b815260040160405180910390fd5b60008061259f86613298565b90506fffffffffffffffffffffffffffffffff6001600160a01b0382161161261c5760006125d66001600160a01b03831680614859565b9050836001600160a01b0316856001600160a01b03161061260557612600600160c01b87836136cd565b612614565b6126148187600160c01b6136cd565b92505061267d565b600061263b6001600160a01b03831680680100000000000000006136cd565b9050836001600160a01b0316856001600160a01b03161061266a57612665600160801b87836136cd565b612679565b6126798187600160801b6136cd565b9250505b50949350505050565b6000808212156125295760405163029f024d60e31b815260040160405180910390fd5b600080821215612529576126bc82614493565b610116565b6000805b8260400151518110156128415760006126fa846040015183815181106126ed576126ed613e6a565b6020026020010151610ab6565b60408051606081019091528154909190829060ff16600981111561272057612720613aa7565b600981111561273157612731613aa7565b815260200160018201805461274590613dc2565b80601f016020809104026020016040519081016040528092919081815260200182805461277190613dc2565b80156127be5780601f10612793576101008083540402835291602001916127be565b820191906000526020600020905b8154815290600101906020018083116127a157829003601f168201915b505050505081526020016002820180548060200260200160405190810160405280929190818152602001828054801561281657602002820191906000526020600020905b815481526020019060010190808311612802575b505050505081525050905061282a81611a8c565b612838575060009392505050565b506001016126c5565b50600192915050565b60006002826040015151101561286257506000919050565b81602001515160201461287757506000919050565b600082602001518060200190518101906128919190614627565b905060088111156128415750600092915050565b6000602082602001515110156128bd57506000919050565b600082602001518060200190518101906128d79190613ed3565b90506128ea816306e7ea3960e21b6138e2565b6128f75750600092915050565b604051633b70a5bf60e21b81526001600160a01b0382169063edc296fc90612923908690600401613b21565b6020604051808303816000875af1158015612942573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906129669190614870565b6128415750600092915050565b6040810151516000901561298957506000919050565b81602001515160601461299e57506000919050565b60008083602001518060200190518101906129b99190613fe8565b92505091506000829050806001600160a01b031663feaf968c6040518163ffffffff1660e01b815260040160a060405180830381865afa158015612a01573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612a259190614041565b5050505050806001600160a01b031663313ce5676040518163ffffffff1660e01b8152600401602060405180830381865afa158015612a68573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612a8c919061488b565b60ff168260ff1614612aa357506000949350505050565b506001949350505050565b60408101515160009015612ac457506000919050565b81602001515160c014612ad957506000919050565b6000806000806000808760200151806020019051810190612afa91906140ba565b9550955095509550955095508360ff16866001600160a01b031663313ce5676040518163ffffffff1660e01b8152600401602060405180830381865afa158015612b48573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612b6c919061488b565b60ff1614612b8257506000979650505050505050565b8260ff16856001600160a01b031663313ce5676040518163ffffffff1660e01b8152600401602060405180830381865afa158015612bc4573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612be8919061488b565b60ff1614612bfe57506000979650505050505050565b6000826001600160a01b0316630dfe16816040518163ffffffff1660e01b8152600401602060405180830381865afa158015612c3e573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612c629190613ed3565b90506000836001600160a01b031663d21220a76040518163ffffffff1660e01b8152600401602060405180830381865afa158015612ca4573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612cc89190613ed3565b9050876001600160a01b0316826001600160a01b0316148015612cfc5750866001600160a01b0316816001600160a01b0316145b158015612d385750866001600160a01b0316826001600160a01b0316148015612d365750876001600160a01b0316816001600160a01b0316145b155b15612d4d575060009998505050505050505050565b60128660ff161180612d62575060128560ff16115b15612d77575060009998505050505050505050565b8263ffffffff16600003612d95575060009998505050505050505050565b6040805160028082526060820183526000926020830190803683370190505090508381600081518110612dca57612dca613e6a565b602002602001019063ffffffff16908163ffffffff1681525050600081600181518110612df957612df9613e6a565b63ffffffff9092166020928302919091019091015260405163883bdbfd60e01b81526001600160a01b0386169063883bdbfd90612e3a908490600401614143565b600060405180830381865afa158015612e57573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f19168201604052612e7f91908101906141f5565b5060019c9b505050505050505050505050565b60408101515160009015612ea857506000919050565b816020015151606014612ebd57506000919050565b60008060008460200151806020019051810190612eda91906144bf565b919450925090508281612f55576040516396834ad360e01b8152600481018490526001600160a01b038216906396834ad390602401608060405180830381865afa158015612f2c573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612f5091906144f5565b612fbe565b604051639474f45b60e01b8152600481018490526001600160a01b03821690639474f45b90602401608060405180830381865afa158015612f9a573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612fbe91906144f5565b5060019695505050505050565b60408101515160009015612fe157506000919050565b816020015151606014612ff657506000919050565b8160200151806020019051810190612aa39190614597565b60008160400151516002148061302957508160400151516003145b61303557506000919050565b81602001515160201461304a57506000919050565b506001919050565b600081604001515160011480613029575081604001515160021461303557506000919050565b6040810151516000901561308e57506000919050565b6020826020015151101561304a57506000919050565b81818082036130b4575050505050565b6000856130da60026130c6888861446c565b6130d09190614640565b6112c0908861456f565b815181106130ea576130ea613e6a565b60200260200101516000015190505b818313613236575b808661310c85612686565b8151811061311c5761311c613e6a565b60200260200101516000015112156131405782613138816148a6565b935050613101565b8561314a83612686565b8151811061315a5761315a613e6a565b60200260200101516000015181121561317f5781613177816148be565b925050613140565b818313613231578561319083612686565b815181106131a0576131a0613e6a565b6020026020010151866131b285612686565b815181106131c2576131c2613e6a565b6020026020010151876131d486612686565b815181106131e4576131e4613e6a565b60200260200101886131f586612686565b8151811061320557613205613e6a565b602002602001018290528290525050828061321f906148a6565b935050818061322d906148be565b9250505b6130f9565b81851215613249576132498686846130a4565b8383121561325c5761325c8684866130a4565b505050505050565b6000670de0b6b3a764000061327983856147e8565b6102b49190614640565b600081613279670de0b6b3a7640000856147e8565b60008060008360020b126132b8576132b3600284900b612686565b6132c8565b6132c86112c0600285900b614493565b90506132e36112c06132dd620d89e7196148db565b60020b90565b8111156133165760405162461bcd60e51b81526020600482015260016024820152601560fa1b6044820152606401610397565b60008160011660000361332d57600160801b61333f565b6ffffcb933bd6fad37aa2d162d1a5940015b70ffffffffffffffffffffffffffffffffff169050600282161561337e576080613379826ffff97272373d413259a46990580e213a614859565b901c90505b60048216156133a85760806133a3826ffff2e50f5f656932ef12357cf3c7fdcc614859565b901c90505b60088216156133d25760806133cd826fffe5caca7e10e4e61c3624eaa0941cd0614859565b901c90505b60108216156133fc5760806133f7826fffcb9843d60f6159c9db58835c926644614859565b901c90505b6020821615613426576080613421826fff973b41fa98c081472e6896dfb254c0614859565b901c90505b604082161561345057608061344b826fff2ea16466c96a3843ec78b326b52861614859565b901c90505b608082161561347a576080613475826ffe5dee046a99a2a811c461f1969c3053614859565b901c90505b6101008216156134a55760806134a0826ffcbe86c7900a88aedcffc83b479aa3a4614859565b901c90505b6102008216156134d05760806134cb826ff987a7253ac413176f2b074cf7815e54614859565b901c90505b6104008216156134fb5760806134f6826ff3392b0822b70005940c7a398e4b70f3614859565b901c90505b610800821615613526576080613521826fe7159475a2c29b7443b29c7fa6e889d9614859565b901c90505b61100082161561355157608061354c826fd097f3bdfd2022b8845ad8f792aa5825614859565b901c90505b61200082161561357c576080613577826fa9f746462d870fdf8a65dc1f90e061e5614859565b901c90505b6140008216156135a75760806135a2826f70d869a156d2a1b890bb3df62baf32f7614859565b901c90505b6180008216156135d25760806135cd826f31be135f97d08fd981231505542fcfa6614859565b901c90505b620100008216156135fe5760806135f9826f09aa508b5b7a84e1c677de54f3e99bc9614859565b901c90505b62020000821615613629576080613624826e5d6af8dedb81196699c329225ee604614859565b901c90505b6204000082161561365357608061364e826d2216e584f5fa1ea926041bedfe98614859565b901c90505b6208000082161561367b576080613676826b048a170391f7dc42444e8fa2614859565b901c90505b60008460020b131561369657613693816000196147c0565b90505b6102ce6136a8640100000000836147d4565b156136b45760016136b7565b60005b6136c89060ff16602084901c6147ad565b6139ba565b6000808060001985870985870292508281108382030391505080600003613749576000841161373e5760405162461bcd60e51b815260206004820152601960248201527f48616e646c65206e6f6e2d6f766572666c6f77206361736573000000000000006044820152606401610397565b5082900490506102b4565b8084116137985760405162461bcd60e51b815260206004820152601960248201527f70726576656e74732064656e6f6d696e61746f72203d3d2030000000000000006044820152606401610397565b60008486880980840393811190920391905060006137d06137b887612569565b6137c188612569565b6137ca90614493565b16612686565b9586900495938490049360008190030460010190506137ef8184614859565b909317926000613800876003614859565b600218905061380f8188614859565b61381a9060026140a7565b6138249082614859565b90506138308188614859565b61383b9060026140a7565b6138459082614859565b90506138518188614859565b61385c9060026140a7565b6138669082614859565b90506138728188614859565b61387d9060026140a7565b6138879082614859565b90506138938188614859565b61389e9060026140a7565b6138a89082614859565b90506138b48188614859565b6138bf9060026140a7565b6138c99082614859565b90506138d58186614859565b9998505050505050505050565b604080516001600160e01b0319831660248083019190915282518083039091018152604490910182526020810180516001600160e01b03166301ffc9a760e01b1790529051600091829182916001600160a01b0387169161394391906148fd565b6000604051808303816000865af19150503d8060008114613980576040519150601f19603f3d011682016040523d82523d6000602084013e613985565b606091505b50915091508161399a57600092505050610116565b80516000036139ae57600092505050610116565b60200151949350505050565b60006001600160a01b038211156125295760405163dccde8ed60e01b815260040160405180910390fd5b6040518060800160405280600081526020016000815260200160008152602001600081525090565b828054828255906000526020600020908101928215613a47579160200282015b82811115613a47578251825591602001919060010190613a2c565b506125299291505b808211156125295760008155600101613a4f565b600060208284031215613a7557600080fd5b5035919050565b8151815260208083015190820152604080830151908201526060808301519082015260808101610116565b634e487b7160e01b600052602160045260246000fd5b600a8110613acd57613acd613aa7565b9052565b60005b83811015613aec578181015183820152602001613ad4565b50506000910152565b60008151808452613b0d816020860160208601613ad1565b601f01601f19169290920160200192915050565b60006020808352613b358184018551613abd565b8084015160606040850152613b4d6080850182613af5565b6040860151858203601f19016060870152805180835290840192506000918401905b80831015613b8f5783518252928401926001929092019190840190613b6f565b509695505050505050565b634e487b7160e01b600052604160045260246000fd5b6040516080810167ffffffffffffffff81118282101715613bd357613bd3613b9a565b60405290565b604051601f8201601f1916810167ffffffffffffffff81118282101715613c0257613c02613b9a565b604052919050565b600067ffffffffffffffff821115613c2457613c24613b9a565b5060051b60200190565b600082601f830112613c3f57600080fd5b81356020613c54613c4f83613c0a565b613bd9565b8083825260208201915060208460051b870101935086841115613c7657600080fd5b602086015b84811015613b8f5780358352918301918301613c7b565b600080600060608486031215613ca757600080fd5b8335600a8110613cb657600080fd5b925060208481013567ffffffffffffffff80821115613cd457600080fd5b818701915087601f830112613ce857600080fd5b813581811115613cfa57613cfa613b9a565b613d0c601f8201601f19168501613bd9565b8181528985838601011115613d2057600080fd5b818585018683013760009181019094015291935060408601359180831115613d4757600080fd5b5050613d5586828701613c2e565b9150509250925092565b600080600060608486031215613d7457600080fd5b83359250602084013567ffffffffffffffff80821115613d9357600080fd5b613d9f87838801613c2e565b93506040860135915080821115613db557600080fd5b50613d5586828701613c2e565b600181811c90821680613dd657607f821691505b602082108103611d0757634e487b7160e01b600052602260045260246000fd5b60008151808452602080850194506020840160005b83811015613e2757815187529582019590820190600101613e0b565b509495945050505050565b848152613e426020820185613abd565b608060408201526000613e586080830185613af5565b828103606084015261189a8185613df6565b634e487b7160e01b600052603260045260246000fd5b600060208284031215613e9257600080fd5b8151600981106102b457600080fd5b6020810160098310613eb557613eb5613aa7565b91905290565b6001600160a01b0381168114613ed057600080fd5b50565b600060208284031215613ee557600080fd5b81516102b481613ebb565b608080825285518282018190526000919060209060a0850190828a01855b82811015613f5257613f42848351805182526020810151602083015260408101516040830152606081015160608301525050565b9285019290840190600101613f0e565b5050508481036020860152613f678189613af5565b925050508281036040840152613f7d8186613df6565b9050828103606084015261189a8185613df6565b600060808284031215613fa357600080fd5b613fab613bb0565b825181526020830151602082015260408301516040820152606083015160608201528091505092915050565b805160ff8116811461200457600080fd5b600080600060608486031215613ffd57600080fd5b835161400881613ebb565b6020850151909350915061401e60408501613fd7565b90509250925092565b805169ffffffffffffffffffff8116811461200457600080fd5b600080600080600060a0868803121561405957600080fd5b61406286614027565b945060208601519350604086015192506060860151915061408560808701614027565b90509295509295909350565b634e487b7160e01b600052601160045260246000fd5b8181038181111561011657610116614091565b60008060008060008060c087890312156140d357600080fd5b86516140de81613ebb565b60208801519096506140ef81613ebb565b94506140fd60408801613fd7565b935061410b60608801613fd7565b9250608087015161411b81613ebb565b60a088015190925063ffffffff8116811461413557600080fd5b809150509295509295509295565b6020808252825182820181905260009190848201906040850190845b8181101561418157835163ffffffff168352928401929184019160010161415f565b50909695505050505050565b600082601f83011261419e57600080fd5b815160206141ae613c4f83613c0a565b8083825260208201915060208460051b8701019350868411156141d057600080fd5b602086015b84811015613b8f5780516141e881613ebb565b83529183019183016141d5565b6000806040838503121561420857600080fd5b825167ffffffffffffffff8082111561422057600080fd5b818501915085601f83011261423457600080fd5b81516020614244613c4f83613c0a565b82815260059290921b8401810191818101908984111561426357600080fd5b948201945b838610156142915785518060060b81146142825760008081fd5b82529482019490820190614268565b918801519196509093505050808211156142aa57600080fd5b506142b78582860161418d565b9150509250929050565b600682810b9082900b03667fffffffffffff198112667fffffffffffff8213171561011657610116614091565b634e487b7160e01b600052601260045260246000fd5b60008160060b8360060b8061431b5761431b6142ee565b667fffffffffffff1982146000198214161561433957614339614091565b90059392505050565b600082614351576143516142ee565b500790565b60008160020b627fffff19810361436f5761436f614091565b6000190192915050565b600181815b808511156143b457816000190482111561439a5761439a614091565b808516156143a757918102915b93841c939080029061437e565b509250929050565b6000826143cb57506001610116565b816143d857506000610116565b81600181146143ee57600281146143f857614414565b6001915050610116565b60ff84111561440957614409614091565b50506001821b610116565b5060208310610133831016604e8410600b8410161715614437575081810a610116565b6144418383614379565b806000190482111561445557614455614091565b029392505050565b60006102b460ff8416836143bc565b818103600083128015838313168383128216171561448c5761448c614091565b5092915050565b6000600160ff1b82016144a8576144a8614091565b5060000390565b8051801515811461200457600080fd5b6000806000606084860312156144d457600080fd5b83516144df81613ebb565b6020850151909350915061401e604085016144af565b60006080828403121561450757600080fd5b61450f613bb0565b82518060070b811461452057600080fd5b8152602083015167ffffffffffffffff8116811461453d57600080fd5b60208201526040830151600381900b811461455757600080fd5b60408201526060928301519281019290925250919050565b808201828112600083128015821682158216171561458f5761458f614091565b505092915050565b6000806000606084860312156145ac57600080fd5b83516145b781613ebb565b602085015160409095015190969495509392505050565b60ff8416815267ffffffffffffffff831660208201526060604082015260006145fa6060830184613df6565b95945050505050565b6001600160a01b03831681526040602082018190526000906102ce90830184613af5565b60006020828403121561463957600080fd5b5051919050565b60008261464f5761464f6142ee565b600160ff1b82146000198414161561466957614669614091565b500590565b6146788185613abd565b60606020820152600061468e6060830185613af5565b8281036040840152610f4f8185613df6565b601f8211156146e8576000816000526020600020601f850160051c810160208610156146c95750805b601f850160051c820191505b8181101561325c578281556001016146d5565b505050565b815167ffffffffffffffff81111561470757614707613b9a565b61471b816147158454613dc2565b846146a0565b602080601f83116001811461475057600084156147385750858301515b600019600386901b1c1916600185901b17855561325c565b600085815260208120601f198616915b8281101561477f57888601518255948401946001909101908401614760565b508582101561479d5787850151600019600388901b60f8161c191681555b5050505050600190811b01905550565b8082018082111561011657610116614091565b6000826147cf576147cf6142ee565b500490565b6000826147e3576147e36142ee565b500690565b80820260008212600160ff1b8414161561480457614804614091565b818105831482151761011657610116614091565b600069ffffffffffffffffffff82168061436f5761436f614091565b60006001820161484657614846614091565b5060010190565b60006102b483836143bc565b808202811582820484141761011657610116614091565b60006020828403121561488257600080fd5b6102b4826144af565b60006020828403121561489d57600080fd5b6102b482613fd7565b60006001600160ff1b01820161484657614846614091565b6000600160ff1b82016148d3576148d3614091565b506000190190565b60008160020b627fffff1981036148f4576148f4614091565b60000392915050565b6000825161490f818460208701613ad1565b919091019291505056fea264697066735822122074f32fef384fdc296b0859f1c1f941c8e736c6cb972aa9e2b894956ebd6a80b364736f6c63430008160033","r":"0x1","s":"0x1","yParity":"0x0","hash":"0xbc73db80bf4b8784ba10a8910a0b7ef85f6846d102b41dd990969ea205335354","type":"0x2"}],"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":[{"chainId":"0x343a","nonce":"0x0","gas":"0x3c0f6","maxFeePerGas":"0x5d4285cd","maxPriorityFeePerGas":"0x3b9aca00","value":"0x0","accessList":[],"input":"0x608060405234801561001057600080fd5b50610380806100206000396000f3fe6080604052600080357fffffffff0000000000000000000000000000000000000000000000000000000016905060008160e01c610251565b60006379ba509782101561015e5781631627540c811461009857632a952b2d81146100b457633659cfe681146100d0576350c946fe81146100ec576353a47bb781146101085763625ca21c81146101245763718fe928811461014057610158565b7347d08dad17ccb558b3ea74b1a0e73a9cc804a9dc9150610158565b738138ef7cf908021d117e542120b7a390650161079150610158565b7347d08dad17ccb558b3ea74b1a0e73a9cc804a9dc9150610158565b738138ef7cf908021d117e542120b7a390650161079150610158565b7347d08dad17ccb558b3ea74b1a0e73a9cc804a9dc9150610158565b738138ef7cf908021d117e542120b7a390650161079150610158565b7347d08dad17ccb558b3ea74b1a0e73a9cc804a9dc91505b5061024c565b816379ba509781146101a657638da5cb5b81146101c25763aaf10f4281146101de5763c7f62cda81146101fa5763daa250be81146102165763deba1b9881146102325761024a565b7347d08dad17ccb558b3ea74b1a0e73a9cc804a9dc915061024a565b7347d08dad17ccb558b3ea74b1a0e73a9cc804a9dc915061024a565b7347d08dad17ccb558b3ea74b1a0e73a9cc804a9dc915061024a565b7347d08dad17ccb558b3ea74b1a0e73a9cc804a9dc915061024a565b738138ef7cf908021d117e542120b7a39065016107915061024a565b738138ef7cf908021d117e542120b7a3906501610791505b505b919050565b61025a81610037565b915050600073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff16036102ce57816040517fc2a825f50000000000000000000000000000000000000000000000000000000081526004016102c5919061032f565b60405180910390fd5b3660008037600080366000845af43d6000803e80600081146102ef573d6000f35b3d6000fd5b60007fffffffff0000000000000000000000000000000000000000000000000000000082169050919050565b610329816102f4565b82525050565b60006020820190506103446000830184610320565b9291505056fea264697066735822122017a4b7fdaaab3897a7b47abaed8d2ee92d558883d3bb2a8454f9601b2ab2c3db64736f6c63430008150033","r":"0x1","s":"0x1","yParity":"0x0","hash":"0x2476e039803622aeb040f924f04c493f559aed3d6c9372ab405cb33c8c695328","type":"0x2"}],"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":[{"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","type":"0x2"}],"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":[{"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","type":"0x2"}],"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":[{"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","type":"0x2"}],"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":[]}]} ================================================ FILE: crates/anvil/test-data/state-dump-legacy.json ================================================ {"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":[{"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","type":"0x2"}],"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":[{"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","type":"0x2"}],"ommers":[]}]} ================================================ FILE: crates/anvil/test-data/state-dump.json ================================================ { "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": { "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", "type": "0x2" }, "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": { "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", "type": "0x2" }, "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 }, "gas_refund_counter": 0 }, "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 }, "gas_refund_counter": 0 }, "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 } ] } ================================================ FILE: crates/anvil/test-data/storage_sample.json ================================================ {"0x0000000000000000000000000000000000000000000000000000000000000022":"0xf5a5fd42d16a20302798ef6ed309979b43003d2320d9f0e8ea9831a92759fb4b","0x0000000000000000000000000000000000000000000000000000000000000023":"0xdb56114e00fdd4c1f85c892bf35ac9a89289aaecb1ebd0a96cde606a748b5d71","0x0000000000000000000000000000000000000000000000000000000000000024":"0xc78009fdf07fc56a11f122370658a353aaa542ed63e44c4bc15ff4cd105ab33c","0x0000000000000000000000000000000000000000000000000000000000000025":"0x536d98837f2dd165a55d5eeae91485954472d56f246df256bf3cae19352a123c","0x0000000000000000000000000000000000000000000000000000000000000026":"0x9efde052aa15429fae05bad4d0b1d7c64da64d03d7a1854a588c2cb8430c0d30","0x0000000000000000000000000000000000000000000000000000000000000027":"0xd88ddfeed400a8755596b21942c1497e114c302e6118290f91e6772976041fa1","0x0000000000000000000000000000000000000000000000000000000000000028":"0x87eb0ddba57e35f6d286673802a4af5975e22506c7cf4c64bb6be5ee11527f2c","0x0000000000000000000000000000000000000000000000000000000000000029":"0x26846476fd5fc54a5d43385167c95144f2643f533cc85bb9d16b782f8d7db193","0x000000000000000000000000000000000000000000000000000000000000002a":"0x506d86582d252405b840018792cad2bf1259f1ef5aa5f887e13cb2f0094f51e1","0x000000000000000000000000000000000000000000000000000000000000002b":"0xffff0ad7e659772f9534c195c815efc4014ef1e1daed4404c06385d11192e92b","0x000000000000000000000000000000000000000000000000000000000000002c":"0x6cf04127db05441cd833107a52be852868890e4317e6a02ab47683aa75964220","0x000000000000000000000000000000000000000000000000000000000000002d":"0xb7d05f875f140027ef5118a2247bbb84ce8f2f0f1123623085daf7960c329f5f","0x000000000000000000000000000000000000000000000000000000000000002e":"0xdf6af5f5bbdb6be9ef8aa618e4bf8073960867171e29676f8b284dea6a08a85e","0x000000000000000000000000000000000000000000000000000000000000002f":"0xb58d900f5e182e3c50ef74969ea16c7726c549757cc23523c369587da7293784","0x0000000000000000000000000000000000000000000000000000000000000030":"0xd49a7502ffcfb0340b1d7885688500ca308161a7f96b62df9d083b71fcc8f2bb","0x0000000000000000000000000000000000000000000000000000000000000031":"0x8fe6b1689256c0d385f42f5bbe2027a22c1996e110ba97c171d3e5948de92beb","0x0000000000000000000000000000000000000000000000000000000000000032":"0x8d0d63c39ebade8509e0ae3c9c3876fb5fa112be18f905ecacfecb92057603ab","0x0000000000000000000000000000000000000000000000000000000000000033":"0x95eec8b2e541cad4e91de38385f2e046619f54496c2382cb6cacd5b98c26f5a4","0x0000000000000000000000000000000000000000000000000000000000000034":"0xf893e908917775b62bff23294dbbe3a1cd8e6cc1c35b4801887b646a6f81f17f","0x0000000000000000000000000000000000000000000000000000000000000035":"0xcddba7b592e3133393c16194fac7431abf2f5485ed711db282183c819e08ebaa","0x0000000000000000000000000000000000000000000000000000000000000036":"0x8a8d7fe3af8caa085a7639a832001457dfb9128a8061142ad0335629ff23ff9c","0x0000000000000000000000000000000000000000000000000000000000000037":"0xfeb3c337d7a51a6fbf00b9e34c52e1c9195c969bd4e7a0bfd51d5c5bed9c1167","0x0000000000000000000000000000000000000000000000000000000000000038":"0xe71f0aa83cc32edfbefa9f4d3e0174ca85182eec9f3a09f6a6c0df6377a510d7","0x0000000000000000000000000000000000000000000000000000000000000039":"0x31206fa80a50bb6abe29085058f16212212a60eec8f049fecb92d8c8e0a84bc0","0x000000000000000000000000000000000000000000000000000000000000003a":"0x21352bfecbeddde993839f614c3dac0a3ee37543f9b412b16199dc158e23b544","0x000000000000000000000000000000000000000000000000000000000000003b":"0x619e312724bb6d7c3153ed9de791d764a366b389af13c58bf8a8d90481a46765","0x000000000000000000000000000000000000000000000000000000000000003c":"0x7cdd2986268250628d0c10e385c58c6191e6fbe05191bcc04f133f2cea72c1c4","0x000000000000000000000000000000000000000000000000000000000000003d":"0x848930bd7ba8cac54661072113fb278869e07bb8587f91392933374d017bcbe1","0x000000000000000000000000000000000000000000000000000000000000003e":"0x8869ff2c22b28cc10510d9853292803328be4fb0e80495e8bb8d271f5b889636","0x000000000000000000000000000000000000000000000000000000000000003f":"0xb5fe28e79f1b850f8658246ce9b6a1e7b49fc06db7143e8fe0b4f2b0c5523a5c","0x0000000000000000000000000000000000000000000000000000000000000040":"0x985e929f70af28d0bdd1a90a808f977f597c7c778c489e98d3bd8910d31ac0f7"} ================================================ FILE: crates/anvil/tests/it/abi.rs ================================================ //! commonly used sol generated types use alloy_sol_types::sol; sol!( #[sol(rpc)] Greeter, "test-data/greeter.json" ); sol!( #[derive(Debug)] #[sol(rpc)] SimpleStorage, "test-data/SimpleStorage.json" ); sol!( #[sol(rpc)] Multicall, "test-data/multicall.json" ); sol!( #[sol(rpc)] contract BUSD { function balanceOf(address) external view returns (uint256); } ); sol!( #[sol(rpc)] interface ERC721 { function balanceOf(address owner) public view virtual returns (uint256); function ownerOf(uint256 tokenId) public view virtual returns (address); function name() public view virtual returns (string memory); function symbol() public view virtual returns (string memory); function tokenURI(uint256 tokenId) public view virtual returns (string memory); function getApproved(uint256 tokenId) public view virtual returns (address); function setApprovalForAll(address operator, bool approved) public virtual; function isApprovedForAll(address owner, address operator) public view virtual returns (bool); function transferFrom(address from, address to, uint256 tokenId) public virtual; function safeTransferFrom(address from, address to, uint256 tokenId) public; function safeTransferFrom(address from, address to, uint256 tokenId, bytes memory data) public virtual; function _mint(address to, uint256 tokenId) internal; function _safeMint(address to, uint256 tokenId, bytes memory data) internal virtual; function _burn(uint256 tokenId) internal; function _transfer(address from, address to, uint256 tokenId) internal; function _approve(address to, uint256 tokenId, address auth) internal; } ); // https://docs.soliditylang.org/en/latest/control-structures.html#revert sol!( // SPDX-License-Identifier: GPL-3.0 pragma solidity ^0.8.4; #[sol(rpc, bytecode = "6080806040523460155761011e908161001a8239f35b5f80fdfe60808060405260043610156011575f80fd5b5f3560e01c9081633ccfd60b146094575063d96a094a14602f575f80fd5b6020366003190112609057671bc16d674ec80000340460043511604e57005b60405162461bcd60e51b815260206004820152601a6024820152792737ba1032b737bab3b41022ba3432b910383937bb34b232b21760311b6044820152606490fd5b5f80fd5b346090575f3660031901126090575f546001600160a01b0316330360da575f8080804781811560d2575b3390f11560c757005b6040513d5f823e3d90fd5b506108fc60be565b6282b42960e81b8152600490fdfea2646970667358221220c143fcbf0da5cee61ae3fcc385d9f7c4d6a7fb2ea42530d70d6049478db0b8a964736f6c63430008190033")] contract VendingMachine { address owner; error Unauthorized(); #[derive(Debug)] function buy(uint amount) public payable { if (amount > msg.value / 2 ether) revert("Not enough Ether provided."); // Alternative way to do it: require( amount <= msg.value / 2 ether, "Not enough Ether provided." ); // Perform the purchase. } function withdraw() public { if (msg.sender != owner) revert Unauthorized(); payable(msg.sender).transfer(address(this).balance); } } ); ================================================ FILE: crates/anvil/tests/it/anvil.rs ================================================ //! tests for anvil specific logic use alloy_consensus::EMPTY_ROOT_HASH; use alloy_eips::BlockNumberOrTag; use alloy_network::{ReceiptResponse, TransactionBuilder}; use alloy_primitives::{Address, B256, U256, bytes, hex}; use alloy_provider::Provider; use alloy_rpc_types::TransactionRequest; use alloy_sol_types::SolCall; use anvil::{NodeConfig, spawn}; use foundry_evm::hardfork::EthereumHardfork; #[tokio::test(flavor = "multi_thread")] async fn test_can_change_mining_mode() { let (api, handle) = spawn(NodeConfig::test()).await; let provider = handle.http_provider(); assert!(api.anvil_get_auto_mine().unwrap()); assert!(api.anvil_get_interval_mining().unwrap().is_none()); let num = provider.get_block_number().await.unwrap(); assert_eq!(num, 0); api.anvil_set_interval_mining(1).unwrap(); assert!(!api.anvil_get_auto_mine().unwrap()); assert!(matches!(api.anvil_get_interval_mining().unwrap(), Some(1))); // changing the mining mode will instantly mine a new block tokio::time::sleep(std::time::Duration::from_millis(500)).await; let num = provider.get_block_number().await.unwrap(); assert_eq!(num, 0); tokio::time::sleep(std::time::Duration::from_millis(700)).await; let num = provider.get_block_number().await.unwrap(); assert_eq!(num, 1); // assert that no block is mined when the interval is set to 0 api.anvil_set_interval_mining(0).unwrap(); assert!(!api.anvil_get_auto_mine().unwrap()); assert!(api.anvil_get_interval_mining().unwrap().is_none()); tokio::time::sleep(std::time::Duration::from_millis(1000)).await; let num = provider.get_block_number().await.unwrap(); assert_eq!(num, 1); } #[tokio::test(flavor = "multi_thread")] async fn can_get_default_dev_keys() { let (_api, handle) = spawn(NodeConfig::test()).await; let provider = handle.http_provider(); let dev_accounts = handle.dev_accounts().collect::>(); let accounts = provider.get_accounts().await.unwrap(); assert_eq!(dev_accounts, accounts); } #[tokio::test(flavor = "multi_thread")] async fn can_set_empty_code() { let (api, _handle) = spawn(NodeConfig::test()).await; let addr = Address::random(); api.anvil_set_code(addr, Vec::new().into()).await.unwrap(); let code = api.get_code(addr, None).await.unwrap(); assert!(code.as_ref().is_empty()); } #[tokio::test(flavor = "multi_thread")] async fn test_can_set_genesis_timestamp() { let genesis_timestamp = 1000u64; let (_api, handle) = spawn(NodeConfig::test().with_genesis_timestamp(genesis_timestamp.into())).await; let provider = handle.http_provider(); assert_eq!( genesis_timestamp, provider.get_block(0.into()).await.unwrap().unwrap().header.timestamp ); } #[tokio::test(flavor = "multi_thread")] async fn test_can_use_default_genesis_timestamp() { let (_api, handle) = spawn(NodeConfig::test()).await; let provider = handle.http_provider(); assert_ne!(0u64, provider.get_block(0.into()).await.unwrap().unwrap().header.timestamp); } #[tokio::test(flavor = "multi_thread")] async fn test_can_handle_large_timestamp() { let (api, _handle) = spawn(NodeConfig::test()).await; let num = 317071597274; api.evm_set_next_block_timestamp(num).unwrap(); api.mine_one().await; let block = api.block_by_number(BlockNumberOrTag::Latest).await.unwrap().unwrap(); assert_eq!(block.header.timestamp, num); } #[tokio::test(flavor = "multi_thread")] async fn test_shanghai_fields() { let (api, _handle) = spawn(NodeConfig::test().with_hardfork(Some(EthereumHardfork::Shanghai.into()))).await; api.mine_one().await; let block = api.block_by_number(BlockNumberOrTag::Latest).await.unwrap().unwrap(); assert_eq!(block.header.withdrawals_root, Some(EMPTY_ROOT_HASH)); assert_eq!(block.withdrawals, Some(Default::default())); assert!(block.header.blob_gas_used.is_none()); assert!(block.header.excess_blob_gas.is_none()); } #[tokio::test(flavor = "multi_thread")] async fn test_cancun_fields() { let (api, _handle) = spawn(NodeConfig::test().with_hardfork(Some(EthereumHardfork::Cancun.into()))).await; api.mine_one().await; let block = api.block_by_number(BlockNumberOrTag::Latest).await.unwrap().unwrap(); assert_eq!(block.header.withdrawals_root, Some(EMPTY_ROOT_HASH)); assert_eq!(block.withdrawals, Some(Default::default())); assert!(block.header.blob_gas_used.is_some()); assert!(block.header.excess_blob_gas.is_some()); } #[tokio::test(flavor = "multi_thread")] async fn test_can_set_genesis_block_number() { let (_api, handle) = spawn(NodeConfig::test().with_genesis_block_number(Some(1337u64))).await; let provider = handle.http_provider(); let block_number = provider.get_block_number().await.unwrap(); assert_eq!(block_number, 1337u64); assert_eq!(1337, provider.get_block(1337.into()).await.unwrap().unwrap().header.number); } #[tokio::test(flavor = "multi_thread")] async fn test_can_use_default_genesis_block_number() { let (_api, handle) = spawn(NodeConfig::test()).await; let provider = handle.http_provider(); assert_eq!(0, provider.get_block(0.into()).await.unwrap().unwrap().header.number); } /// Verify that genesis block number affects both RPC and EVM execution layer. #[tokio::test(flavor = "multi_thread")] async fn test_number_opcode_reflects_genesis_block_number() { let genesis_number: u64 = 4242; let (api, handle) = spawn(NodeConfig::test().with_genesis_block_number(Some(genesis_number))).await; let provider = handle.http_provider(); // RPC layer should return configured genesis number let bn = provider.get_block_number().await.unwrap(); assert_eq!(bn, genesis_number); // Deploy bytecode that returns block.number // 0x43 (NUMBER) 0x5f (PUSH0) 0x52 (MSTORE) 0x60 0x20 (PUSH1 0x20) 0x5f (PUSH0) 0xf3 (RETURN) let target = Address::random(); api.anvil_set_code(target, bytes!("435f5260205ff3")).await.unwrap(); // EVM execution should reflect genesis number (+ 1 for pending block) let tx = alloy_rpc_types::TransactionRequest::default().with_to(target); let out = provider.call(tx.into()).await.unwrap(); let returned = U256::from_be_slice(out.as_ref()); assert_eq!(returned, U256::from(genesis_number + 1)); } #[tokio::test(flavor = "multi_thread")] async fn test_anvil_recover_signature() { let (api, handle) = spawn(NodeConfig::test()).await; let provider = handle.http_provider(); alloy_sol_types::sol! { #[sol(rpc)] contract TestRecover { function testRecover(bytes32 hash, uint8 v, bytes32 r, bytes32 s, address expected) external pure { address recovered = ecrecover(hash, v, r, s); require(recovered == expected, "ecrecover failed: address mismatch"); } } } let bytecode = hex::decode( "0x60808060405234601557610125908161001a8239f35b5f80fdfe60808060405260043610156011575f80fd5b5f3560e01c63bff0b743146023575f80fd5b3460eb5760a036600319011260eb5760243560ff811680910360eb576084356001600160a01b038116929083900360eb5760805f916020936004358252848201526044356040820152606435606082015282805260015afa1560e0575f516001600160a01b031603609057005b60405162461bcd60e51b815260206004820152602260248201527f65637265636f766572206661696c65643a2061646472657373206d69736d61746044820152610c6d60f31b6064820152608490fd5b6040513d5f823e3d90fd5b5f80fdfea264697066735822122006368b42bca31c97f2c409a1cc5186dc899d4255ecc28db7bbb0ad285dc82ae464736f6c634300081c0033", ).unwrap(); let tx = TransactionRequest::default().with_deploy_code(bytecode); let receipt = provider.send_transaction(tx.into()).await.unwrap().get_receipt().await.unwrap(); let contract_address = receipt.contract_address().unwrap(); let contract = TestRecover::new(contract_address, &provider); let sig = alloy_primitives::hex::decode("11".repeat(65)).unwrap(); let r = B256::from_slice(&sig[0..32]); let s = B256::from_slice(&sig[32..64]); let v = sig[64]; let fake_hash = B256::random(); let expected = alloy_primitives::address!("0x1234567890123456789012345678901234567890"); api.anvil_impersonate_signature(sig.clone().into(), expected).await.unwrap(); let result = contract.testRecover(fake_hash, v, r, s, expected).call().await; assert!(result.is_ok(), "ecrecover failed: {:?}", result.err()); } #[tokio::test(flavor = "multi_thread")] async fn test_fake_signature_transaction() { let (api, handle) = spawn(NodeConfig::test()).await; let provider = handle.http_provider(); alloy_sol_types::sol! { #[sol(rpc)] contract TestRecover { function testRecover(bytes32 hash, uint8 v, bytes32 r, bytes32 s, address expected) external pure { address recovered = ecrecover(hash, v, r, s); require(recovered == expected, "ecrecover failed: address mismatch"); } } } let bytecode = hex::decode( "0x60808060405234601557610125908161001a8239f35b5f80fdfe60808060405260043610156011575f80fd5b5f3560e01c63bff0b743146023575f80fd5b3460eb5760a036600319011260eb5760243560ff811680910360eb576084356001600160a01b038116929083900360eb5760805f916020936004358252848201526044356040820152606435606082015282805260015afa1560e0575f516001600160a01b031603609057005b60405162461bcd60e51b815260206004820152602260248201527f65637265636f766572206661696c65643a2061646472657373206d69736d61746044820152610c6d60f31b6064820152608490fd5b6040513d5f823e3d90fd5b5f80fdfea264697066735822122006368b42bca31c97f2c409a1cc5186dc899d4255ecc28db7bbb0ad285dc82ae464736f6c634300081c0033", ).unwrap(); let tx = TransactionRequest::default().with_deploy_code(bytecode); let _receipt = provider.send_transaction(tx.into()).await.unwrap().get_receipt().await.unwrap(); let sig = alloy_primitives::hex::decode("11".repeat(65)).unwrap(); let r = B256::from_slice(&sig[0..32]); let s = B256::from_slice(&sig[32..64]); let v = sig[64]; let fake_hash = B256::random(); let expected = alloy_primitives::address!("0x1234567890123456789012345678901234567890"); api.anvil_impersonate_signature(sig.clone().into(), expected).await.unwrap(); let calldata = TestRecover::testRecoverCall { hash: fake_hash, v, r, s, expected }.abi_encode(); let tx = TransactionRequest::default().with_input(calldata); let pending = provider.send_transaction(tx.into()).await.unwrap(); let result = pending.get_receipt().await; assert!(result.is_ok(), "ecrecover failed: {:?}", result.err()); } ================================================ FILE: crates/anvil/tests/it/anvil_api.rs ================================================ //! tests for custom anvil endpoints use crate::{ abi::{self, BUSD, Greeter, Multicall}, fork::fork_config, utils::http_provider_with_signer, }; use alloy_consensus::{SignableTransaction, TxEip1559}; use alloy_network::{EthereumWallet, TransactionBuilder, TxSignerSync}; use alloy_primitives::{Address, Bytes, TxKind, U256, address, fixed_bytes}; use alloy_provider::{Provider, ext::TxPoolApi}; use alloy_rpc_types::{ BlockId, BlockNumberOrTag, TransactionRequest, anvil::{ ForkedNetwork, Forking, Metadata, MineOptions, NodeEnvironment, NodeForkConfig, NodeInfo, }, }; use alloy_serde::WithOtherFields; use anvil::{NodeConfig, eth::api::CLIENT_VERSION, spawn}; use anvil_core::{ eth::EthRequest, types::{ReorgOptions, TransactionData}, }; use foundry_evm::hardfork::EthereumHardfork; use revm::primitives::hardfork::SpecId; use std::{ str::FromStr, time::{Duration, SystemTime}, }; #[tokio::test(flavor = "multi_thread")] async fn can_set_gas_price() { let (api, handle) = spawn(NodeConfig::test().with_hardfork(Some(EthereumHardfork::Berlin.into()))).await; let provider = handle.http_provider(); let gas_price = U256::from(1337); api.anvil_set_min_gas_price(gas_price).await.unwrap(); assert_eq!(gas_price.to::(), provider.get_gas_price().await.unwrap()); } #[tokio::test(flavor = "multi_thread")] async fn can_set_block_gas_limit() { let (api, _) = spawn(NodeConfig::test().with_hardfork(Some(EthereumHardfork::Berlin.into()))).await; let block_gas_limit = U256::from(1337); assert!(api.evm_set_block_gas_limit(block_gas_limit).unwrap()); // Mine a new block, and check the new block gas limit api.mine_one().await; let latest_block = api.block_by_number(BlockNumberOrTag::Latest).await.unwrap().unwrap(); assert_eq!(block_gas_limit.to::(), latest_block.header.gas_limit); } // Ref #[tokio::test(flavor = "multi_thread")] async fn can_set_storage() { let (api, _handle) = spawn(NodeConfig::test()).await; let s = r#"{"jsonrpc": "2.0", "method": "hardhat_setStorageAt", "id": 1, "params": ["0xe9e7CEA3DedcA5984780Bafc599bD69ADd087D56", "0xa6eef7e35abe7026729641147f7915573c7e97b47efa546f5f6e3230263bcb49", "0x0000000000000000000000000000000000000000000000000000000000003039"]}"#; let req = serde_json::from_str::(s).unwrap(); let (addr, slot, val) = match req.clone() { EthRequest::SetStorageAt(addr, slot, val) => (addr, slot, val), _ => unreachable!(), }; api.execute(req).await; let storage_value = api.storage_at(addr, slot, None).await.unwrap(); assert_eq!(val, storage_value); } #[tokio::test(flavor = "multi_thread")] async fn can_impersonate_account() { let (api, handle) = spawn(NodeConfig::test()).await; let provider = handle.http_provider(); let impersonate = Address::random(); let to = Address::random(); let val = U256::from(1337); let funding = U256::from(1e18 as u64); // fund the impersonated account api.anvil_set_balance(impersonate, funding).await.unwrap(); let balance = api.balance(impersonate, None).await.unwrap(); assert_eq!(balance, funding); let tx = TransactionRequest::default().with_from(impersonate).with_to(to).with_value(val); let tx = WithOtherFields::new(tx); let res = provider.send_transaction(tx.clone()).await; res.unwrap_err(); api.anvil_impersonate_account(impersonate).await.unwrap(); assert!(api.accounts().unwrap().contains(&impersonate)); let res = provider.send_transaction(tx.clone()).await.unwrap().get_receipt().await.unwrap(); assert_eq!(res.from, impersonate); let nonce = provider.get_transaction_count(impersonate).await.unwrap(); assert_eq!(nonce, 1); let balance = provider.get_balance(to).await.unwrap(); assert_eq!(balance, val); api.anvil_stop_impersonating_account(impersonate).await.unwrap(); let res = provider.send_transaction(tx).await; res.unwrap_err(); } #[tokio::test(flavor = "multi_thread")] async fn can_auto_impersonate_account() { let (api, handle) = spawn(NodeConfig::test()).await; let provider = handle.http_provider(); let impersonate = Address::random(); let to = Address::random(); let val = U256::from(1337); let funding = U256::from(1e18 as u64); // fund the impersonated account api.anvil_set_balance(impersonate, funding).await.unwrap(); let balance = api.balance(impersonate, None).await.unwrap(); assert_eq!(balance, funding); let tx = TransactionRequest::default().with_from(impersonate).with_to(to).with_value(val); let tx = WithOtherFields::new(tx); let res = provider.send_transaction(tx.clone()).await; res.unwrap_err(); api.anvil_auto_impersonate_account(true).await.unwrap(); let res = provider.send_transaction(tx.clone()).await.unwrap().get_receipt().await.unwrap(); assert_eq!(res.from, impersonate); let nonce = provider.get_transaction_count(impersonate).await.unwrap(); assert_eq!(nonce, 1); let balance = provider.get_balance(to).await.unwrap(); assert_eq!(balance, val); api.anvil_auto_impersonate_account(false).await.unwrap(); let res = provider.send_transaction(tx).await; res.unwrap_err(); // explicitly impersonated accounts get returned by `eth_accounts` api.anvil_impersonate_account(impersonate).await.unwrap(); assert!(api.accounts().unwrap().contains(&impersonate)); } #[tokio::test(flavor = "multi_thread")] async fn can_impersonate_contract() { let (api, handle) = spawn(NodeConfig::test()).await; let provider = handle.http_provider(); let greeter_contract = Greeter::deploy(&provider, "Hello World!".to_string()).await.unwrap(); let impersonate = greeter_contract.address().to_owned(); let to = Address::random(); let val = U256::from(1337); // // fund the impersonated account api.anvil_set_balance(impersonate, U256::from(1e18 as u64)).await.unwrap(); let tx = TransactionRequest::default().with_from(impersonate).to(to).with_value(val); let tx = WithOtherFields::new(tx); let res = provider.send_transaction(tx.clone()).await; res.unwrap_err(); let greeting = greeter_contract.greet().call().await.unwrap(); assert_eq!("Hello World!", greeting); api.anvil_impersonate_account(impersonate).await.unwrap(); let res = provider.send_transaction(tx.clone()).await.unwrap().get_receipt().await.unwrap(); assert_eq!(res.from, impersonate); let balance = provider.get_balance(to).await.unwrap(); assert_eq!(balance, val); api.anvil_stop_impersonating_account(impersonate).await.unwrap(); let res = provider.send_transaction(tx).await; res.unwrap_err(); let greeting = greeter_contract.greet().call().await.unwrap(); assert_eq!("Hello World!", greeting); } #[tokio::test(flavor = "multi_thread")] async fn can_impersonate_gnosis_safe() { let (api, handle) = spawn(fork_config()).await; let provider = handle.http_provider(); // let safe = address!("0xA063Cb7CFd8E57c30c788A0572CBbf2129ae56B6"); let code = provider.get_code_at(safe).await.unwrap(); assert!(!code.is_empty()); api.anvil_impersonate_account(safe).await.unwrap(); let code = provider.get_code_at(safe).await.unwrap(); assert!(!code.is_empty()); let balance = U256::from(1e18 as u64); // fund the impersonated account api.anvil_set_balance(safe, balance).await.unwrap(); let on_chain_balance = provider.get_balance(safe).await.unwrap(); assert_eq!(on_chain_balance, balance); api.anvil_stop_impersonating_account(safe).await.unwrap(); let code = provider.get_code_at(safe).await.unwrap(); // code is added back after stop impersonating assert!(!code.is_empty()); } #[tokio::test(flavor = "multi_thread")] async fn can_impersonate_multiple_accounts() { let (api, handle) = spawn(NodeConfig::test()).await; let provider = handle.http_provider(); let impersonate0 = Address::random(); let impersonate1 = Address::random(); let to = Address::random(); let val = U256::from(1337); let funding = U256::from(1e18 as u64); // fund the impersonated accounts api.anvil_set_balance(impersonate0, funding).await.unwrap(); api.anvil_set_balance(impersonate1, funding).await.unwrap(); let tx = TransactionRequest::default().with_from(impersonate0).to(to).with_value(val); let tx = WithOtherFields::new(tx); api.anvil_impersonate_account(impersonate0).await.unwrap(); api.anvil_impersonate_account(impersonate1).await.unwrap(); let res0 = provider.send_transaction(tx.clone()).await.unwrap().get_receipt().await.unwrap(); assert_eq!(res0.from, impersonate0); let nonce = provider.get_transaction_count(impersonate0).await.unwrap(); assert_eq!(nonce, 1); let receipt = provider.get_transaction_receipt(res0.transaction_hash).await.unwrap().unwrap(); assert_eq!(res0.inner, receipt.inner); let res1 = provider .send_transaction(tx.with_from(impersonate1)) .await .unwrap() .get_receipt() .await .unwrap(); assert_eq!(res1.from, impersonate1); let nonce = provider.get_transaction_count(impersonate1).await.unwrap(); assert_eq!(nonce, 1); let receipt = provider.get_transaction_receipt(res1.transaction_hash).await.unwrap().unwrap(); assert_eq!(res1.inner, receipt.inner); assert_ne!(res0.inner, res1.inner); } #[tokio::test(flavor = "multi_thread")] async fn can_mine_manually() { let (api, handle) = spawn(NodeConfig::test()).await; let provider = handle.http_provider(); let start_num = provider.get_block_number().await.unwrap(); for (idx, _) in std::iter::repeat_n((), 10).enumerate() { api.evm_mine(None).await.unwrap(); let num = provider.get_block_number().await.unwrap(); assert_eq!(num, start_num + idx as u64 + 1); } } #[tokio::test(flavor = "multi_thread")] async fn test_set_next_timestamp() { let (api, handle) = spawn(NodeConfig::test()).await; let provider = handle.http_provider(); let now = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap(); let next_timestamp = now + Duration::from_secs(60); // mock timestamp api.evm_set_next_block_timestamp(next_timestamp.as_secs()).unwrap(); api.evm_mine(None).await.unwrap(); let block = provider.get_block(BlockId::default()).await.unwrap().unwrap(); assert_eq!(block.header.number, 1); assert_eq!(block.header.timestamp, next_timestamp.as_secs()); api.evm_mine(None).await.unwrap(); let next = provider.get_block(BlockId::default()).await.unwrap().unwrap(); assert_eq!(next.header.number, 2); assert!(next.header.timestamp >= block.header.timestamp); } #[tokio::test(flavor = "multi_thread")] async fn test_evm_set_time() { let (api, handle) = spawn(NodeConfig::test()).await; let provider = handle.http_provider(); let now = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap(); let timestamp = now + Duration::from_secs(120); // mock timestamp api.evm_set_time(timestamp.as_secs()).unwrap(); // mine a block api.evm_mine(None).await.unwrap(); let block = provider.get_block(BlockId::default()).await.unwrap().unwrap(); assert!(block.header.timestamp >= timestamp.as_secs()); api.evm_mine(None).await.unwrap(); let next = provider.get_block(BlockId::default()).await.unwrap().unwrap(); assert!(next.header.timestamp >= block.header.timestamp); } #[tokio::test(flavor = "multi_thread")] async fn test_evm_set_time_in_past() { let (api, handle) = spawn(NodeConfig::test()).await; let provider = handle.http_provider(); let now = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap(); let timestamp = now - Duration::from_secs(120); // mock timestamp api.evm_set_time(timestamp.as_secs()).unwrap(); // mine a block api.evm_mine(None).await.unwrap(); let block = provider.get_block(BlockId::default()).await.unwrap().unwrap(); assert!(block.header.timestamp >= timestamp.as_secs()); assert!(block.header.timestamp < now.as_secs()); } #[tokio::test(flavor = "multi_thread")] async fn test_timestamp_interval() { let (api, handle) = spawn(NodeConfig::test()).await; let provider = handle.http_provider(); api.evm_mine(None).await.unwrap(); let interval = 10; for _ in 0..5 { let block = provider.get_block(BlockId::default()).await.unwrap().unwrap(); // mock timestamp api.evm_set_block_timestamp_interval(interval).unwrap(); api.evm_mine(None).await.unwrap(); let new_block = provider.get_block(BlockId::default()).await.unwrap().unwrap(); assert_eq!(new_block.header.timestamp, block.header.timestamp + interval); } let block = provider.get_block(BlockId::default()).await.unwrap().unwrap(); let next_timestamp = block.header.timestamp + 50; api.evm_set_next_block_timestamp(next_timestamp).unwrap(); api.evm_mine(None).await.unwrap(); let block = provider.get_block(BlockId::default()).await.unwrap().unwrap(); assert_eq!(block.header.timestamp, next_timestamp); api.evm_mine(None).await.unwrap(); let block = provider.get_block(BlockId::default()).await.unwrap().unwrap(); // interval also works after setting the next timestamp manually assert_eq!(block.header.timestamp, next_timestamp + interval); assert!(api.evm_remove_block_timestamp_interval().unwrap()); api.evm_mine(None).await.unwrap(); let new_block = provider.get_block(BlockId::default()).await.unwrap().unwrap(); // offset is applied correctly after resetting the interval assert!(new_block.header.timestamp > block.header.timestamp); api.evm_mine(None).await.unwrap(); let another_block = provider.get_block(BlockId::default()).await.unwrap().unwrap(); // check interval is disabled assert!(another_block.header.timestamp - new_block.header.timestamp < interval); } // #[tokio::test(flavor = "multi_thread")] async fn test_can_set_storage_bsc_fork() { let (api, handle) = spawn(NodeConfig::test().with_eth_rpc_url(Some("https://bsc-dataseed.binance.org/"))).await; let provider = handle.http_provider(); let busd_addr = address!("0xe9e7CEA3DedcA5984780Bafc599bD69ADd087D56"); let idx = U256::from_str("0xa6eef7e35abe7026729641147f7915573c7e97b47efa546f5f6e3230263bcb49") .unwrap(); let value = fixed_bytes!("0000000000000000000000000000000000000000000000000000000000003039"); api.anvil_set_storage_at(busd_addr, idx, value).await.unwrap(); let storage = api.storage_at(busd_addr, idx, None).await.unwrap(); assert_eq!(storage, value); let busd_contract = BUSD::new(busd_addr, &provider); let balance = busd_contract .balanceOf(address!("0x0000000000000000000000000000000000000000")) .call() .await .unwrap(); assert_eq!(balance, U256::from(12345u64)); } #[tokio::test(flavor = "multi_thread")] async fn can_get_node_info() { let (api, handle) = spawn(NodeConfig::test()).await; let node_info = api.anvil_node_info().await.unwrap(); let provider = handle.http_provider(); 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::OSAKA.into(); let expected_node_info = NodeInfo { current_block_number: 0_u64, current_block_timestamp: 1, current_block_hash: block.header.hash, hard_fork: hard_fork.to_string(), transaction_order: "fees".to_owned(), environment: NodeEnvironment { base_fee: U256::from_str("0x3b9aca00").unwrap().to(), chain_id: 0x7a69, gas_limit: U256::from_str("0x1c9c380").unwrap().to(), gas_price: U256::from_str("0x77359400").unwrap().to(), }, fork_config: NodeForkConfig { fork_url: None, fork_block_number: None, fork_retry_backoff: None, }, }; assert_eq!(node_info, expected_node_info); } #[tokio::test(flavor = "multi_thread")] async fn can_get_metadata() { let (api, handle) = spawn(NodeConfig::test()).await; let metadata = api.anvil_metadata().await.unwrap(); let provider = handle.http_provider(); let block_number = provider.get_block_number().await.unwrap(); let chain_id = provider.get_chain_id().await.unwrap(); let block = provider.get_block(BlockId::from(block_number)).await.unwrap().unwrap(); let expected_metadata = Metadata { latest_block_hash: block.header.hash, latest_block_number: block_number, chain_id, client_version: CLIENT_VERSION.to_string(), instance_id: api.instance_id(), forked_network: None, snapshots: Default::default(), }; assert_eq!(metadata, expected_metadata); } #[tokio::test(flavor = "multi_thread")] async fn can_get_metadata_on_fork() { let (api, handle) = spawn(NodeConfig::test().with_eth_rpc_url(Some("https://bsc-dataseed.binance.org/"))).await; let provider = handle.http_provider(); let metadata = api.anvil_metadata().await.unwrap(); let block_number = provider.get_block_number().await.unwrap(); let chain_id = provider.get_chain_id().await.unwrap(); let block = provider.get_block(BlockId::from(block_number)).await.unwrap().unwrap(); let expected_metadata = Metadata { latest_block_hash: block.header.hash, latest_block_number: block_number, chain_id, client_version: CLIENT_VERSION.to_string(), instance_id: api.instance_id(), forked_network: Some(ForkedNetwork { chain_id, fork_block_number: block_number, fork_block_hash: block.header.hash, }), snapshots: Default::default(), }; assert_eq!(metadata, expected_metadata); } #[tokio::test(flavor = "multi_thread")] async fn metadata_changes_on_reset() { let (api, _) = spawn(NodeConfig::test().with_eth_rpc_url(Some("https://bsc-dataseed.binance.org/"))).await; let metadata = api.anvil_metadata().await.unwrap(); let instance_id = metadata.instance_id; api.anvil_reset(Some(Forking { json_rpc_url: None, block_number: None })).await.unwrap(); let new_metadata = api.anvil_metadata().await.unwrap(); let new_instance_id = new_metadata.instance_id; assert_ne!(instance_id, new_instance_id); } #[tokio::test(flavor = "multi_thread")] async fn test_get_transaction_receipt() { let (api, handle) = spawn(NodeConfig::test()).await; let provider = handle.http_provider(); // set the base fee let new_base_fee = U256::from(1000); api.anvil_set_next_block_base_fee_per_gas(new_base_fee).await.unwrap(); // send a EIP-1559 transaction let to = Address::random(); let val = U256::from(1337); let tx = TransactionRequest::default().with_to(to).with_value(val); let tx = WithOtherFields::new(tx); let receipt = provider.send_transaction(tx.clone()).await.unwrap().get_receipt().await.unwrap(); // the block should have the new base fee let block = provider.get_block(BlockId::default()).await.unwrap().unwrap(); assert_eq!(block.header.base_fee_per_gas.unwrap(), new_base_fee.to::()); // mine blocks api.evm_mine(None).await.unwrap(); // the transaction receipt should have the original effective gas price let new_receipt = provider.get_transaction_receipt(receipt.transaction_hash).await.unwrap(); assert_eq!(receipt.effective_gas_price, new_receipt.unwrap().effective_gas_price); } // test can set chain id #[tokio::test(flavor = "multi_thread")] async fn test_set_chain_id() { let (api, handle) = spawn(NodeConfig::test()).await; let provider = handle.http_provider(); let chain_id = provider.get_chain_id().await.unwrap(); assert_eq!(chain_id, 31337); let chain_id = 1234; api.anvil_set_chain_id(chain_id).await.unwrap(); let chain_id = provider.get_chain_id().await.unwrap(); assert_eq!(chain_id, 1234); } // #[tokio::test(flavor = "multi_thread")] async fn test_fork_revert_next_block_timestamp() { let (api, _handle) = spawn(fork_config()).await; // Mine a new block, and check the new block gas limit api.mine_one().await; let latest_block = api.block_by_number(BlockNumberOrTag::Latest).await.unwrap().unwrap(); let state_snapshot = api.evm_snapshot().await.unwrap(); api.mine_one().await; api.evm_revert(state_snapshot).await.unwrap(); let block = api.block_by_number(BlockNumberOrTag::Latest).await.unwrap().unwrap(); assert_eq!(block, latest_block); api.mine_one().await; let block = api.block_by_number(BlockNumberOrTag::Latest).await.unwrap().unwrap(); assert!(block.header.timestamp >= latest_block.header.timestamp); } // test that after a snapshot revert, the env block is reset // to its correct value (block number, etc.) #[tokio::test(flavor = "multi_thread")] async fn test_fork_revert_call_latest_block_timestamp() { let (api, handle) = spawn(fork_config()).await; let provider = handle.http_provider(); // Mine a new block, and check the new block gas limit api.mine_one().await; let latest_block = api.block_by_number(BlockNumberOrTag::Latest).await.unwrap().unwrap(); let state_snapshot = api.evm_snapshot().await.unwrap(); api.mine_one().await; api.evm_revert(state_snapshot).await.unwrap(); let multicall_contract = Multicall::new(address!("0xeefba1e63905ef1d7acba5a8513c70307c1ce441"), &provider); let timestamp = multicall_contract.getCurrentBlockTimestamp().call().await.unwrap(); assert_eq!(timestamp, U256::from(latest_block.header.timestamp)); let difficulty = multicall_contract.getCurrentBlockDifficulty().call().await.unwrap(); assert_eq!(difficulty, U256::from(latest_block.header.difficulty)); let gaslimit = multicall_contract.getCurrentBlockGasLimit().call().await.unwrap(); assert_eq!(gaslimit, U256::from(latest_block.header.gas_limit)); let coinbase = multicall_contract.getCurrentBlockCoinbase().call().await.unwrap(); assert_eq!(coinbase, latest_block.header.beneficiary); } #[tokio::test(flavor = "multi_thread")] async fn can_remove_pool_transactions() { let (api, handle) = spawn(NodeConfig::test().with_blocktime(Some(Duration::from_secs(5)))).await; let wallet = handle.dev_wallets().next().unwrap(); let signer: EthereumWallet = wallet.clone().into(); let from = wallet.address(); let provider = http_provider_with_signer(&handle.http_endpoint(), signer); let sender = Address::random(); let to = Address::random(); let val = U256::from(1337); let tx = TransactionRequest::default().with_from(sender).with_to(to).with_value(val); let tx = WithOtherFields::new(tx); provider.send_transaction(tx.with_from(from)).await.unwrap().register().await.unwrap(); let initial_txs = provider.txpool_inspect().await.unwrap(); assert_eq!(initial_txs.pending.len(), 1); api.anvil_remove_pool_transactions(wallet.address()).await.unwrap(); let final_txs = provider.txpool_inspect().await.unwrap(); assert_eq!(final_txs.pending.len(), 0); } #[tokio::test(flavor = "multi_thread")] async fn flaky_test_reorg() { let (api, handle) = spawn(NodeConfig::test()).await; let provider = handle.http_provider(); let accounts = handle.dev_wallets().collect::>(); // Test calls // Populate chain for i in 0..10 { let tx = TransactionRequest::default() .to(accounts[0].address()) .value(U256::from(i)) .from(accounts[1].address()); let tx = WithOtherFields::new(tx); api.send_transaction(tx).await.unwrap(); let tx = TransactionRequest::default() .to(accounts[1].address()) .value(U256::from(i)) .from(accounts[2].address()); let tx = WithOtherFields::new(tx); api.send_transaction(tx).await.unwrap(); } // Define transactions let mut txs = vec![]; for i in 0..3 { let from = accounts[i].address(); let to = accounts[i + 1].address(); for j in 0..5 { let tx = TransactionRequest::default().from(from).to(to).value(U256::from(j)); txs.push((TransactionData::JSON(tx), i as u64)); } } let prev_height = provider.get_block_number().await.unwrap(); api.anvil_reorg(ReorgOptions { depth: 7, tx_block_pairs: txs }).await.unwrap(); let reorged_height = provider.get_block_number().await.unwrap(); assert_eq!(reorged_height, prev_height); // The first 3 reorged blocks should have 5 transactions each for num in 14..17 { let block = provider.get_block_by_number(num.into()).full().await.unwrap(); let block = block.unwrap(); assert_eq!(block.transactions.len(), 5); } // Verify that historic blocks are still accessible for num in (0..14).rev() { let block = provider.get_block_by_number(num.into()).full().await.unwrap(); assert!(block.is_some(), "Historic block {num} should be accessible after reorg"); } // Send a few more transaction to verify the chain can still progress for i in 0..3 { let tx = TransactionRequest::default() .to(accounts[0].address()) .value(U256::from(i)) .from(accounts[1].address()); let tx = WithOtherFields::new(tx); api.send_transaction(tx).await.unwrap(); } // Test reverting code let greeter = abi::Greeter::deploy(provider.clone(), "Reorg".to_string()).await.unwrap(); api.anvil_reorg(ReorgOptions { depth: 5, tx_block_pairs: vec![] }).await.unwrap(); let code = api.get_code(*greeter.address(), Some(BlockId::latest())).await.unwrap(); assert_eq!(code, Bytes::default()); // Test reverting contract storage let storage = abi::SimpleStorage::deploy(provider.clone(), "initial value".to_string()).await.unwrap(); api.evm_mine(Some(MineOptions::Options { timestamp: None, blocks: Some(5) })).await.unwrap(); let _ = storage .setValue("ReorgMe".to_string()) .from(accounts[0].address()) .send() .await .unwrap() .get_receipt() .await .unwrap(); api.anvil_reorg(ReorgOptions { depth: 3, tx_block_pairs: vec![] }).await.unwrap(); let value = storage.getValue().call().await.unwrap(); assert_eq!("initial value".to_string(), value); api.mine_one().await; api.mine_one().await; // Test raw transaction data let mut tx = TxEip1559 { chain_id: api.chain_id(), to: TxKind::Call(accounts[1].address()), value: U256::from(100), max_priority_fee_per_gas: 1000000000000, max_fee_per_gas: 10000000000000, gas_limit: 21000, ..Default::default() }; let signature = accounts[5].sign_transaction_sync(&mut tx).unwrap(); let tx = tx.into_signed(signature); let mut encoded = vec![]; tx.eip2718_encode(&mut encoded); let pre_bal = provider.get_balance(accounts[5].address()).await.unwrap(); api.anvil_reorg(ReorgOptions { depth: 1, tx_block_pairs: vec![(TransactionData::Raw(encoded.into()), 0)], }) .await .unwrap(); let post_bal = provider.get_balance(accounts[5].address()).await.unwrap(); assert_ne!(pre_bal, post_bal); // Test reorg depth exceeding current height let res = api.anvil_reorg(ReorgOptions { depth: 100, tx_block_pairs: vec![] }).await; assert!(res.is_err()); // Test reorg tx pairs exceeds chain length let res = api .anvil_reorg(ReorgOptions { depth: 1, tx_block_pairs: vec![(TransactionData::JSON(TransactionRequest::default()), 10)], }) .await; assert!(res.is_err()); } #[tokio::test(flavor = "multi_thread")] async fn test_reorg_blockhash_opcode_consistency() { let (api, handle) = spawn(NodeConfig::test()).await; let provider = handle.http_provider(); let multicall = Multicall::deploy(&provider).await.unwrap(); api.evm_mine(Some(MineOptions::Options { timestamp: None, blocks: Some(200) })).await.unwrap(); let tip_before_reorg = api.block_number().unwrap().to::(); let mut cached_hashes: Vec<(u64, alloy_primitives::B256, alloy_primitives::B256)> = Vec::new(); for i in 1..=10 { let block_num = tip_before_reorg - i; let rpc_hash = provider.get_block_by_number(block_num.into()).await.unwrap().unwrap().header.hash; let opcode_hash = multicall.getBlockHash(U256::from(block_num)).call().await.unwrap(); assert_eq!(rpc_hash, opcode_hash, "RPC and BLOCKHASH opcode should match before reorg"); cached_hashes.push((block_num, rpc_hash, opcode_hash)); } let tx = TransactionRequest::default(); api.anvil_reorg(ReorgOptions { depth: 5, tx_block_pairs: vec![(TransactionData::JSON(tx), 0)], }) .await .unwrap(); api.mine_one().await; for (block_num, rpc_before, opcode_before) in &cached_hashes { let rpc_after = provider.get_block_by_number((*block_num).into()).await.unwrap().unwrap().header.hash; let opcode_after = multicall.getBlockHash(U256::from(*block_num)).call().await.unwrap(); if *block_num <= tip_before_reorg.saturating_sub(5) { assert_eq!( rpc_after, *rpc_before, "Block {block_num}: hash should not change for non-reorged blocks" ); assert_eq!( opcode_after, *opcode_before, "Block {block_num}: BLOCKHASH should not change for non-reorged blocks" ); } assert_eq!( rpc_after, opcode_after, "Block {block_num}: RPC ({rpc_after}) and BLOCKHASH opcode ({opcode_after}) should match after reorg" ); } } #[tokio::test(flavor = "multi_thread")] async fn test_reorg_deep_blockhash_consistency() { let (api, handle) = spawn(NodeConfig::test()).await; let provider = handle.http_provider(); let multicall = Multicall::deploy(&provider).await.unwrap(); // Mine 300 blocks (more than BLOCKHASH limit of 256) api.evm_mine(Some(MineOptions::Options { timestamp: None, blocks: Some(300) })).await.unwrap(); let tip_before_reorg = api.block_number().unwrap().to::(); assert!(tip_before_reorg > 256, "Need more than 256 blocks for this test"); // Check blocks within the 256 window before reorg let mut cached_hashes: Vec<(u64, alloy_primitives::B256)> = Vec::new(); for i in [1, 10, 100, 200, 255] { let block_num = tip_before_reorg - i; let rpc_hash = provider.get_block_by_number(block_num.into()).await.unwrap().unwrap().header.hash; let opcode_hash = multicall.getBlockHash(U256::from(block_num)).call().await.unwrap(); assert_eq!(rpc_hash, opcode_hash, "RPC and BLOCKHASH opcode should match before reorg"); cached_hashes.push((block_num, rpc_hash)); } // Perform a deep reorg (50 blocks) let tx = TransactionRequest::default(); api.anvil_reorg(ReorgOptions { depth: 50, tx_block_pairs: vec![(TransactionData::JSON(tx), 0)], }) .await .unwrap(); api.mine_one().await; let tip_after_reorg = api.block_number().unwrap().to::(); // Verify blocks still in the 256 window have consistent hashes for (block_num, rpc_before) in &cached_hashes { // Skip blocks that were reorged if *block_num > tip_after_reorg - 50 { continue; } let rpc_after = provider.get_block_by_number((*block_num).into()).await.unwrap().unwrap().header.hash; let opcode_after = multicall.getBlockHash(U256::from(*block_num)).call().await.unwrap(); assert_eq!( rpc_after, *rpc_before, "Block {block_num}: hash should not change for non-reorged blocks" ); assert_eq!( rpc_after, opcode_after, "Block {block_num}: RPC and BLOCKHASH should match after deep reorg" ); } // Verify BLOCKHASH returns 0 for blocks outside the 256 window let old_block = tip_after_reorg.saturating_sub(257); if old_block > 0 { let opcode_hash = multicall.getBlockHash(U256::from(old_block)).call().await.unwrap(); assert_eq!( opcode_hash, alloy_primitives::B256::ZERO, "BLOCKHASH should return 0 for blocks outside 256 window" ); } } #[tokio::test(flavor = "multi_thread")] async fn test_rollback() { let (api, handle) = spawn(NodeConfig::test()).await; let provider = handle.http_provider(); // Mine 5 blocks for _ in 0..5 { api.mine_one().await; } // Get block 4 for later comparison let block4 = provider.get_block(4.into()).await.unwrap().unwrap(); // Rollback with None should rollback 1 block api.anvil_rollback(None).await.unwrap(); // Assert we're at block 4 and the block contents are kept the same let head = provider.get_block(BlockId::latest()).await.unwrap().unwrap(); assert_eq!(head, block4); // Get block 1 for comparison let block1 = provider.get_block(1.into()).await.unwrap().unwrap(); // Rollback to block 1 let depth = 3; // from block 4 to block 1 api.anvil_rollback(Some(depth)).await.unwrap(); // Assert we're at block 1 and the block contents are kept the same let head = provider.get_block(BlockId::latest()).await.unwrap().unwrap(); assert_eq!(head, block1); } #[tokio::test(flavor = "multi_thread")] async fn test_arb_get_block() { let (api, _handle) = spawn(NodeConfig::test().with_chain_id(Some(421611u64))).await; // Mine two blocks api.mine_one().await; api.mine_one().await; let best_number = api.block_number().unwrap().to::(); assert_eq!(best_number, 2); let block = api.block_by_number(1.into()).await.unwrap().unwrap(); assert_eq!(block.header.number, 1); } // Set next_block_timestamp same as previous block // api.evm_set_next_block_timestamp(0).unwrap(); #[tokio::test(flavor = "multi_thread")] async fn test_mine_blk_with_prev_timestamp() { let (api, handle) = spawn(NodeConfig::test()).await; let provider = handle.http_provider(); let init_blk = provider.get_block(BlockId::latest()).await.unwrap().unwrap(); let init_number = init_blk.header.number; let init_timestamp = init_blk.header.timestamp; // mock timestamp api.evm_set_next_block_timestamp(init_timestamp).unwrap(); api.mine_one().await; let block = provider.get_block(BlockId::latest()).await.unwrap().unwrap(); let next_blk_num = block.header.number; let next_blk_timestamp = block.header.timestamp; assert_eq!(next_blk_num, init_number + 1); assert_eq!(next_blk_timestamp, init_timestamp); // Sleep for 1 second tokio::time::sleep(Duration::from_secs(1)).await; // Subsequent block should have a greater timestamp than previous block api.mine_one().await; let block = provider.get_block(BlockId::latest()).await.unwrap().unwrap(); let third_blk_num = block.header.number; let third_blk_timestamp = block.header.timestamp; assert_eq!(third_blk_num, init_number + 2); assert_ne!(third_blk_timestamp, next_blk_timestamp); assert!(third_blk_timestamp > next_blk_timestamp); } // increase time by 0 seconds i.e next_block_timestamp = prev_block_timestamp // api.evm_increase_time(0).unwrap(); #[tokio::test(flavor = "multi_thread")] async fn test_increase_time_by_zero() { let (api, handle) = spawn(NodeConfig::test()).await; let provider = handle.http_provider(); let init_blk = provider.get_block(BlockId::latest()).await.unwrap().unwrap(); let init_number = init_blk.header.number; let init_timestamp = init_blk.header.timestamp; let _ = api.evm_increase_time(U256::ZERO).await; api.mine_one().await; let block = provider.get_block(BlockId::latest()).await.unwrap().unwrap(); let next_blk_num = block.header.number; let next_blk_timestamp = block.header.timestamp; assert_eq!(next_blk_num, init_number + 1); assert_eq!(next_blk_timestamp, init_timestamp); } // evm_mine(MineOptions::Timestamp(prev_block_timestamp)) #[tokio::test(flavor = "multi_thread")] async fn evm_mine_blk_with_same_timestamp() { let (api, handle) = spawn(NodeConfig::test()).await; let provider = handle.http_provider(); let init_blk = provider.get_block(BlockId::latest()).await.unwrap().unwrap(); let init_number = init_blk.header.number; let init_timestamp = init_blk.header.timestamp; api.evm_mine(Some(MineOptions::Timestamp(Some(init_timestamp)))).await.unwrap(); let block = provider.get_block(BlockId::latest()).await.unwrap().unwrap(); let next_blk_num = block.header.number; let next_blk_timestamp = block.header.timestamp; assert_eq!(next_blk_num, init_number + 1); assert_eq!(next_blk_timestamp, init_timestamp); } // mine 4 blocks instantly. #[tokio::test(flavor = "multi_thread")] async fn test_mine_blk_with_same_timestamp() { let (api, handle) = spawn(NodeConfig::test()).await; let provider = handle.http_provider(); let init_blk = provider.get_block(BlockId::latest()).await.unwrap().unwrap(); let init_number = init_blk.header.number; let init_timestamp = init_blk.header.timestamp; // Mine 4 blocks instantly let _ = api.anvil_mine(Some(U256::from(4)), None).await; let latest_blk_num = api.block_number().unwrap().to::(); assert_eq!(latest_blk_num, init_number + 4); let mut blk_futs = vec![]; for i in 1..=4 { blk_futs.push(provider.get_block(i.into()).into_future()); } let timestamps = futures::future::join_all(blk_futs) .await .into_iter() .map(|blk| blk.unwrap().unwrap().header.timestamp) .collect::>(); // All timestamps should be equal. Allow for 1 second difference. assert!(timestamps.windows(2).all(|w| w[0] == w[1]), "{timestamps:#?}"); assert!( timestamps[0] == init_timestamp || timestamps[0] == init_timestamp + 1, "{timestamps:#?} != {init_timestamp}" ); } // #[tokio::test(flavor = "multi_thread")] async fn test_mine_first_block_with_interval() { let (api, _) = spawn(NodeConfig::test()).await; let init_block = api.block_by_number(0.into()).await.unwrap().unwrap(); let init_timestamp = init_block.header.timestamp; // Mine 2 blocks with interval of 60. let _ = api.anvil_mine(Some(U256::from(2)), Some(U256::from(60))).await; let first_block = api.block_by_number(1.into()).await.unwrap().unwrap(); assert_eq!(first_block.header.timestamp, init_timestamp + 60); let second_block = api.block_by_number(2.into()).await.unwrap().unwrap(); assert_eq!(second_block.header.timestamp, init_timestamp + 120); } #[tokio::test(flavor = "multi_thread")] async fn test_anvil_reset_non_fork() { let (api, handle) = spawn(NodeConfig::test()).await; let provider = handle.http_provider(); // Get initial state let init_block = provider.get_block(BlockId::latest()).await.unwrap().unwrap(); let init_accounts = api.accounts().unwrap(); let init_balance = provider.get_balance(init_accounts[0]).await.unwrap(); // Store the instance id before reset let instance_id_before = api.instance_id(); // Mine some blocks and make transactions for _ in 0..5 { api.mine_one().await; } // Send a transaction let to = Address::random(); let val = U256::from(1337); let tx = TransactionRequest::default().with_from(init_accounts[0]).with_to(to).with_value(val); let tx = WithOtherFields::new(tx); let _ = provider.send_transaction(tx).await.unwrap().get_receipt().await.unwrap(); // Check state has changed let block_before_reset = provider.get_block(BlockId::latest()).await.unwrap().unwrap(); assert!(block_before_reset.header.number > init_block.header.number); let balance_before_reset = provider.get_balance(init_accounts[0]).await.unwrap(); assert!(balance_before_reset < init_balance); let to_balance_before_reset = provider.get_balance(to).await.unwrap(); assert_eq!(to_balance_before_reset, val); // Reset to fresh in-memory state (non-fork) api.anvil_reset(None).await.unwrap(); // Check instance id has changed let instance_id_after = api.instance_id(); assert_ne!(instance_id_before, instance_id_after); // Check we're back at genesis let block_after_reset = provider.get_block(BlockId::latest()).await.unwrap().unwrap(); assert_eq!(block_after_reset.header.number, 0); // Check accounts are restored to initial state let balance_after_reset = provider.get_balance(init_accounts[0]).await.unwrap(); assert_eq!(balance_after_reset, init_balance); // Check the recipient's balance is zero let to_balance_after_reset = provider.get_balance(to).await.unwrap(); assert_eq!(to_balance_after_reset, U256::ZERO); // Test we can continue mining after reset api.mine_one().await; let new_block = provider.get_block(BlockId::latest()).await.unwrap().unwrap(); assert_eq!(new_block.header.number, 1); } #[tokio::test(flavor = "multi_thread")] async fn test_anvil_reset_fork_to_non_fork() { let (api, handle) = spawn(fork_config()).await; let provider = handle.http_provider(); // Verify we're in fork mode let metadata = api.anvil_metadata().await.unwrap(); assert!(metadata.forked_network.is_some()); // Mine some blocks for _ in 0..3 { api.mine_one().await; } // Reset to non-fork mode api.anvil_reset(None).await.unwrap(); // Verify we're no longer in fork mode let metadata_after = api.anvil_metadata().await.unwrap(); assert!(metadata_after.forked_network.is_none()); // Check we're at block 0 let block = provider.get_block(BlockId::latest()).await.unwrap().unwrap(); assert_eq!(block.header.number, 0); // Verify we can still mine blocks api.mine_one().await; let new_block = provider.get_block(BlockId::latest()).await.unwrap().unwrap(); assert_eq!(new_block.header.number, 1); } ================================================ FILE: crates/anvil/tests/it/api.rs ================================================ //! general eth api tests use crate::{ abi::{Multicall, SimpleStorage, VendingMachine}, utils::{connect_pubsub_with_wallet, http_provider, http_provider_with_signer}, }; use alloy_consensus::{ BlobTransactionSidecar, SidecarBuilder, SignableTransaction, SimpleCoder, Transaction, TxEip1559, }; use alloy_network::{ EthereumWallet, ReceiptResponse, TransactionBuilder, TransactionBuilder4844, TxSignerSync, }; use alloy_primitives::{ Address, B256, ChainId, U256, b256, bytes, map::{AddressHashMap, B256HashMap, HashMap}, }; use alloy_provider::Provider; use alloy_rpc_types::{ BlockId, BlockNumberOrTag, BlockTransactions, request::TransactionRequest, state::AccountOverride, }; use alloy_serde::WithOtherFields; use alloy_sol_types::SolCall; use anvil::{CHAIN_ID, EthereumHardfork, NodeConfig, eth::api::CLIENT_VERSION, spawn}; use foundry_test_utils::rpc; use futures::join; use std::time::Duration; #[tokio::test(flavor = "multi_thread")] async fn can_get_block_number() { let (api, handle) = spawn(NodeConfig::test()).await; let block_num = api.block_number().unwrap(); assert_eq!(block_num, U256::from(0)); let provider = handle.http_provider(); let num = provider.get_block_number().await.unwrap(); assert_eq!(num, block_num.to::()); } #[tokio::test(flavor = "multi_thread")] async fn can_dev_get_balance() { let (_api, handle) = spawn(NodeConfig::test()).await; let provider = handle.http_provider(); let genesis_balance = handle.genesis_balance(); for acc in handle.genesis_accounts() { let balance = provider.get_balance(acc).await.unwrap(); assert_eq!(balance, genesis_balance); } } #[tokio::test(flavor = "multi_thread")] async fn can_get_price() { let (api, handle) = spawn(NodeConfig::test()).await; let provider = handle.http_provider(); let gas_price = provider.get_gas_price().await.unwrap(); assert!(gas_price > 0); assert_eq!(gas_price, api.gas_price()); } #[tokio::test(flavor = "multi_thread")] async fn can_get_accounts() { let (_api, handle) = spawn(NodeConfig::test()).await; let provider = handle.http_provider(); let accounts = provider.get_accounts().await.unwrap(); let dev_accounts: Vec<_> = handle.dev_accounts().collect(); assert_eq!(accounts.len(), dev_accounts.len()); for account in dev_accounts { assert!(accounts.contains(&account), "Missing dev account {account}"); } } #[tokio::test(flavor = "multi_thread")] async fn can_get_client_version() { let (_api, handle) = spawn(NodeConfig::test()).await; let provider = handle.http_provider(); let version = provider.get_client_version().await.unwrap(); assert_eq!(CLIENT_VERSION, version); } #[tokio::test(flavor = "multi_thread")] async fn can_get_chain_id() { let (_api, handle) = spawn(NodeConfig::test()).await; let provider = handle.http_provider(); let chain_id = provider.get_chain_id().await.unwrap(); assert_eq!(chain_id, CHAIN_ID); } #[tokio::test(flavor = "multi_thread")] async fn can_modify_chain_id() { let (_api, handle) = spawn(NodeConfig::test().with_chain_id(Some(ChainId::from(777_u64)))).await; let provider = handle.http_provider(); let chain_id = provider.get_chain_id().await.unwrap(); assert_eq!(chain_id, 777); let chain_id = provider.get_net_version().await.unwrap(); assert_eq!(chain_id, 777); } #[tokio::test(flavor = "multi_thread")] async fn can_get_network_id() { let (api, handle) = spawn(NodeConfig::test()).await; let provider = handle.http_provider(); let network_id = api.network_id().unwrap().unwrap(); assert_eq!(network_id, CHAIN_ID.to_string()); let provider_network_id = provider.get_net_version().await.unwrap(); assert_eq!(provider_network_id, CHAIN_ID); } #[tokio::test(flavor = "multi_thread")] async fn can_get_block_by_number() { let (_api, handle) = spawn(NodeConfig::test()).await; let accounts: Vec<_> = handle.dev_wallets().collect(); let signer: EthereumWallet = accounts[0].clone().into(); let from = accounts[0].address(); let to = accounts[1].address(); let provider = http_provider_with_signer(&handle.http_endpoint(), signer); let val = handle.genesis_balance().checked_div(U256::from(2)).unwrap(); // send a dummy transaction let tx = TransactionRequest::default().with_from(from).with_to(to).with_value(val); let tx = WithOtherFields::new(tx); provider.send_transaction(tx.clone()).await.unwrap().get_receipt().await.unwrap(); let block = provider.get_block(BlockId::number(1)).full().await.unwrap().unwrap(); assert_eq!(block.transactions.len(), 1); let block = provider.get_block(BlockId::hash(block.header.hash)).full().await.unwrap().unwrap(); assert_eq!(block.transactions.len(), 1); } #[tokio::test(flavor = "multi_thread")] async fn can_get_pending_block() { let (api, handle) = spawn(NodeConfig::test()).await; let accounts: Vec<_> = handle.dev_wallets().collect(); let signer: EthereumWallet = accounts[0].clone().into(); let from = accounts[0].address(); let to = accounts[1].address(); let provider = connect_pubsub_with_wallet(&handle.http_endpoint(), signer).await; let block = provider.get_block(BlockId::pending()).await.unwrap().unwrap(); assert_eq!(block.header.number, 1); let num = provider.get_block_number().await.unwrap(); assert_eq!(num, 0); api.anvil_set_auto_mine(false).await.unwrap(); let tx = TransactionRequest::default().with_from(from).with_to(to).with_value(U256::from(100)); let pending = provider.send_transaction(tx.clone()).await.unwrap().register().await.unwrap(); let num = provider.get_block_number().await.unwrap(); assert_eq!(num, 0); let block = provider.get_block(BlockId::pending()).await.unwrap().unwrap(); assert_eq!(block.header.number, 1); assert_eq!(block.transactions.len(), 1); assert_eq!(block.transactions, BlockTransactions::Hashes(vec![*pending.tx_hash()])); let block = provider.get_block(BlockId::pending()).full().await.unwrap().unwrap(); assert_eq!(block.header.number, 1); assert_eq!(block.transactions.len(), 1); } #[tokio::test(flavor = "multi_thread")] async fn can_estimate_gas_with_undersized_max_fee_per_gas() { let (api, handle) = spawn(NodeConfig::test()).await; let wallet = handle.dev_wallets().next().unwrap(); let signer: EthereumWallet = wallet.clone().into(); let provider = http_provider_with_signer(&handle.http_endpoint(), signer); api.anvil_set_auto_mine(true).await.unwrap(); let init_value = "toto".to_string(); let simple_storage_contract = SimpleStorage::deploy(&provider, init_value.clone()).await.unwrap(); let undersized_max_fee_per_gas = 1; let latest_block = api.block_by_number(BlockNumberOrTag::Latest).await.unwrap().unwrap(); let latest_block_base_fee_per_gas = latest_block.header.base_fee_per_gas.unwrap(); assert!(undersized_max_fee_per_gas < latest_block_base_fee_per_gas); let estimated_gas = simple_storage_contract .setValue("new_value".to_string()) .max_fee_per_gas(undersized_max_fee_per_gas.into()) .from(wallet.address()) .estimate_gas() .await .unwrap(); assert!(estimated_gas > 0); } #[tokio::test(flavor = "multi_thread")] async fn can_call_on_pending_block() { let (api, handle) = spawn(NodeConfig::test()).await; let wallet = handle.dev_wallets().next().unwrap(); let signer: EthereumWallet = wallet.clone().into(); let sender = wallet.address(); let provider = http_provider_with_signer(&handle.http_endpoint(), signer); let num = provider.get_block_number().await.unwrap(); assert_eq!(num, 0); api.anvil_set_auto_mine(false).await.unwrap(); let _contract_pending = Multicall::deploy_builder(&provider) .from(wallet.address()) .send() .await .unwrap() .register() .await .unwrap(); let contract_address = sender.create(0); let contract = Multicall::new(contract_address, &provider); let num = provider.get_block_number().await.unwrap(); assert_eq!(num, 0); // Ensure that we can get the block_number from the pending contract let Multicall::aggregateReturn { blockNumber: ret_block_number, .. } = contract.aggregate(vec![]).block(BlockId::pending()).call().await.unwrap(); assert_eq!(ret_block_number, U256::from(1)); let accounts: Vec
= handle.dev_wallets().map(|w| w.address()).collect(); for i in 1..10 { api.anvil_set_coinbase(accounts[i % accounts.len()]).await.unwrap(); api.evm_set_block_gas_limit(U256::from(30_000_000 + i)).unwrap(); api.anvil_mine(Some(U256::from(1)), None).await.unwrap(); tokio::time::sleep(Duration::from_millis(100)).await; } // Ensure that the right header values are set when calling a past block for anvil_block_number in 1..(api.block_number().unwrap().to::() + 1) { let block_number = BlockNumberOrTag::Number(anvil_block_number as u64); let block = api.block_by_number(block_number).await.unwrap().unwrap(); let ret_timestamp = contract .getCurrentBlockTimestamp() .block(BlockId::number(anvil_block_number as u64)) .call() .await .unwrap(); assert_eq!(block.header.timestamp, ret_timestamp.to::()); let ret_gas_limit = contract .getCurrentBlockGasLimit() .block(BlockId::number(anvil_block_number as u64)) .call() .await .unwrap(); assert_eq!(block.header.gas_limit, ret_gas_limit.to::()); let ret_coinbase = contract .getCurrentBlockCoinbase() .block(BlockId::number(anvil_block_number as u64)) .call() .await .unwrap(); assert_eq!(block.header.beneficiary, ret_coinbase); } } #[tokio::test(flavor = "multi_thread")] async fn can_call_with_undersized_max_fee_per_gas() { let (api, handle) = spawn(NodeConfig::test()).await; let wallet = handle.dev_wallets().next().unwrap(); let signer: EthereumWallet = wallet.clone().into(); let provider = http_provider_with_signer(&handle.http_endpoint(), signer); api.anvil_set_auto_mine(true).await.unwrap(); let init_value = "toto".to_string(); let simple_storage_contract = SimpleStorage::deploy(&provider, init_value.clone()).await.unwrap(); let latest_block = api.block_by_number(BlockNumberOrTag::Latest).await.unwrap().unwrap(); let latest_block_base_fee_per_gas = latest_block.header.base_fee_per_gas.unwrap(); let undersized_max_fee_per_gas = 1; assert!(undersized_max_fee_per_gas < latest_block_base_fee_per_gas); let last_sender = simple_storage_contract .lastSender() .max_fee_per_gas(undersized_max_fee_per_gas.into()) .from(wallet.address()) .call() .await .unwrap(); assert_eq!(last_sender, Address::ZERO); } #[tokio::test(flavor = "multi_thread")] async fn can_call_with_state_override() { let (api, handle) = spawn(NodeConfig::test()).await; let wallet = handle.dev_wallets().next().unwrap(); let signer: EthereumWallet = wallet.clone().into(); let account = wallet.address(); let provider = http_provider_with_signer(&handle.http_endpoint(), signer); api.anvil_set_auto_mine(true).await.unwrap(); let multicall_contract = Multicall::deploy(&provider).await.unwrap(); let init_value = "toto".to_string(); let simple_storage_contract = SimpleStorage::deploy(&provider, init_value.clone()).await.unwrap(); // Test the `balance` account 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(); assert_eq!(result, balance); // Test the `state_diff` account override let mut state_diff = B256HashMap::default(); state_diff.insert(B256::ZERO, account.into_word()); let mut overrides = AddressHashMap::default(); overrides.insert( *simple_storage_contract.address(), AccountOverride { // The `lastSender` is in the first storage slot state_diff: Some(state_diff), ..Default::default() }, ); let last_sender = 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(); // `sender` *is* set with override assert_eq!(last_sender, account); let value = simple_storage_contract.getValue().state(overrides).call().await.unwrap(); // `value` *is not* changed with state-diff assert_eq!(value, init_value); // Test the `state` account override let mut state = B256HashMap::default(); state.insert(B256::ZERO, account.into_word()); let mut overrides = AddressHashMap::default(); overrides.insert( *simple_storage_contract.address(), AccountOverride { // The `lastSender` is in the first storage slot state: Some(state), ..Default::default() }, ); let last_sender = 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(); // `value` *is* changed with state assert_eq!(value, ""); } #[tokio::test(flavor = "multi_thread")] async fn can_mine_while_mining() { let (api, _) = spawn(NodeConfig::test()).await; let total_blocks = 200; let block_number = api.block_by_number(BlockNumberOrTag::Latest).await.unwrap().unwrap().header.number; assert_eq!(block_number, 0); let block = api.block_by_number(BlockNumberOrTag::Number(block_number)).await.unwrap().unwrap(); assert_eq!(block.header.number, 0); let result = join!( api.anvil_mine(Some(U256::from(total_blocks / 2)), None), api.anvil_mine(Some(U256::from(total_blocks / 2)), None) ); result.0.unwrap(); result.1.unwrap(); tokio::time::sleep(Duration::from_millis(100)).await; let block_number = api.block_by_number(BlockNumberOrTag::Latest).await.unwrap().unwrap().header.number; assert_eq!(block_number, total_blocks); let block = api.block_by_number(BlockNumberOrTag::Number(block_number)).await.unwrap().unwrap(); assert_eq!(block.header.number, total_blocks); } #[tokio::test(flavor = "multi_thread")] async fn can_send_raw_tx_sync() { 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::>(); let eip1559_est = provider.estimate_eip1559_fees().await.unwrap(); let from = wallets[0].address(); let mut tx = TxEip1559 { 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: alloy_primitives::TxKind::Call(from), input: bytes!("11112222"), ..Default::default() }; let signature = wallets[1].sign_transaction_sync(&mut tx).unwrap(); let tx = tx.into_signed(signature); let mut encoded = Vec::new(); tx.eip2718_encode(&mut encoded); let receipt = api.send_raw_transaction_sync(encoded.into()).await.unwrap(); assert_eq!(receipt.from(), wallets[1].address()); assert_eq!(receipt.to(), tx.to()); } #[tokio::test(flavor = "multi_thread")] async fn can_send_tx_sync() { let node_config = NodeConfig::test().with_hardfork(Some(EthereumHardfork::Prague.into())); let (api, handle) = spawn(node_config).await; let wallets = handle.dev_wallets().collect::>(); let logger_bytecode = bytes!("66365f5f37365fa05f5260076019f3"); let from = wallets[0].address(); let tx = TransactionRequest::default() .with_from(from) .into_create() .with_nonce(0) .with_input(logger_bytecode); let receipt = api.send_transaction_sync(WithOtherFields::new(tx)).await.unwrap(); assert_eq!(receipt.from(), wallets[0].address()); } #[tokio::test(flavor = "multi_thread")] #[ignore = "no debug_"] async fn can_get_code_by_hash() { let (api, _) = spawn(NodeConfig::test().with_eth_rpc_url(Some(rpc::next_http_archive_rpc_url()))).await; // The code hash for DEFAULT_CREATE2_DEPLOYER_RUNTIME_CODE let code_hash = b256!("2fa86add0aed31f33a762c9d88e807c475bd51d0f52bd0955754b2608f7e4989"); let code = api.debug_code_by_hash(code_hash, None).await.unwrap(); assert_eq!(&code.unwrap(), foundry_evm::constants::DEFAULT_CREATE2_DEPLOYER_RUNTIME_CODE); } #[tokio::test(flavor = "multi_thread")] async fn test_fill_transaction_fills_chain_id() { let (api, handle) = spawn(NodeConfig::test()).await; let wallet = handle.dev_wallets().next().unwrap(); let from = wallet.address(); let tx_req = TransactionRequest::default() .with_from(from) .with_to(Address::random()) .with_gas_limit(21_000); let filled = api.fill_transaction(WithOtherFields::new(tx_req)).await.unwrap(); // Should fill with the chain id from provider assert!(filled.tx.chain_id().is_some()); assert_eq!(filled.tx.chain_id().unwrap(), CHAIN_ID); } #[tokio::test(flavor = "multi_thread")] async fn test_fill_transaction_fills_nonce() { let (api, handle) = spawn(NodeConfig::test()).await; let accounts: Vec<_> = handle.dev_wallets().collect(); let signer: EthereumWallet = accounts[0].clone().into(); let from = accounts[0].address(); let to = accounts[1].address(); let provider = http_provider_with_signer(&handle.http_endpoint(), signer); // Send a transaction to increment nonce let tx = TransactionRequest::default().with_from(from).with_to(to).with_value(U256::from(100)); let tx = WithOtherFields::new(tx); provider.send_transaction(tx).await.unwrap().get_receipt().await.unwrap(); // Now the account should have nonce 1 let tx_req = TransactionRequest::default() .with_from(from) .with_to(to) .with_value(U256::from(1000)) .with_gas_limit(21_000); let filled = api.fill_transaction(WithOtherFields::new(tx_req)).await.unwrap(); assert_eq!(filled.tx.nonce(), 1); } #[tokio::test(flavor = "multi_thread")] async fn test_fill_transaction_preserves_provided_fields() { let (api, handle) = spawn(NodeConfig::test()).await; let wallet = handle.dev_wallets().next().unwrap(); let from = wallet.address(); let provided_nonce = 100u64; let provided_gas_limit = 50_000u64; let tx_req = TransactionRequest::default() .with_from(from) .with_to(Address::random()) .with_value(U256::from(1000)) .with_nonce(provided_nonce) .with_gas_limit(provided_gas_limit); let filled = api.fill_transaction(WithOtherFields::new(tx_req)).await.unwrap(); // Should preserve the provided nonce and gas limit assert_eq!(filled.tx.nonce(), provided_nonce); assert_eq!(filled.tx.gas_limit(), provided_gas_limit); } #[tokio::test(flavor = "multi_thread")] async fn test_fill_transaction_fills_all_missing_fields() { let (api, handle) = spawn(NodeConfig::test()).await; let wallet = handle.dev_wallets().next().unwrap(); let from = wallet.address(); // Create a simple transfer transaction with minimal fields let tx_req = TransactionRequest::default().with_from(from).with_to(Address::random()); let filled = api.fill_transaction(WithOtherFields::new(tx_req)).await.unwrap(); // Should fill all required fields and be EIP-1559 assert!(filled.tx.is_eip1559()); assert!(filled.tx.gas_limit() > 0); assert!(filled.tx.max_fee_per_gas() > 0); assert!(filled.tx.max_priority_fee_per_gas().is_some()); } #[tokio::test(flavor = "multi_thread")] async fn test_fill_transaction_eip4844_blob_fee() { let node_config = NodeConfig::test().with_hardfork(Some(EthereumHardfork::Cancun.into())); let (api, handle) = spawn(node_config).await; let wallet = handle.dev_wallets().next().unwrap(); let from = wallet.address(); let mut builder = SidecarBuilder::::new(); builder.ingest(b"dummy blob"); let sidecar: BlobTransactionSidecar = builder.build().unwrap(); // EIP-4844 blob transaction with sidecar but no blob fee let mut tx_req = TransactionRequest::default().with_from(from).with_to(Address::random()); tx_req.sidecar = Some(sidecar.into()); tx_req.transaction_type = Some(3); // EIP-4844 let filled = api.fill_transaction(WithOtherFields::new(tx_req)).await.unwrap(); // Blob transaction should have max_fee_per_blob_gas filled assert!( filled.tx.max_fee_per_blob_gas().is_some(), "max_fee_per_blob_gas should be filled for blob tx" ); assert!(filled.tx.blob_versioned_hashes().is_some(), "blob_versioned_hashes should be present"); } #[tokio::test(flavor = "multi_thread")] async fn test_fill_transaction_eip4844_preserves_blob_fee() { let node_config = NodeConfig::test().with_hardfork(Some(EthereumHardfork::Cancun.into())); let (api, handle) = spawn(node_config).await; let wallet = handle.dev_wallets().next().unwrap(); let from = wallet.address(); let provided_blob_fee = 5_000_000u128; let mut builder = SidecarBuilder::::new(); builder.ingest(b"dummy blob"); let sidecar: BlobTransactionSidecar = builder.build().unwrap(); // EIP-4844 blob transaction with blob fee already set let mut tx_req = TransactionRequest::default() .with_from(from) .with_to(Address::random()) .with_max_fee_per_blob_gas(provided_blob_fee); tx_req.sidecar = Some(sidecar.into()); tx_req.transaction_type = Some(3); // EIP-4844 let filled = api.fill_transaction(WithOtherFields::new(tx_req)).await.unwrap(); // Should preserve the provided blob fee assert_eq!( filled.tx.max_fee_per_blob_gas(), Some(provided_blob_fee), "should preserve provided max_fee_per_blob_gas" ); } #[tokio::test(flavor = "multi_thread")] async fn test_fill_transaction_non_blob_tx_no_blob_fee() { let (api, handle) = spawn(NodeConfig::test()).await; let wallet = handle.dev_wallets().next().unwrap(); let from = wallet.address(); // EIP-1559 transaction without blob fields let mut tx_req = TransactionRequest::default().with_from(from).with_to(Address::random()); tx_req.transaction_type = Some(2); // EIP-1559 let filled = api.fill_transaction(WithOtherFields::new(tx_req)).await.unwrap(); // Non-blob transaction should NOT have blob fee filled assert!( filled.tx.max_fee_per_blob_gas().is_none(), "max_fee_per_blob_gas should not be set for non-blob tx" ); } #[tokio::test(flavor = "multi_thread")] async fn test_fill_transaction_reverts_on_gas_estimation_failure() { let (api, handle) = spawn(NodeConfig::test()).await; let accounts: Vec<_> = handle.dev_wallets().collect(); let signer: EthereumWallet = accounts[0].clone().into(); let from = accounts[0].address(); let provider = http_provider_with_signer(&handle.http_endpoint(), signer); // Deploy VendingMachine contract let contract = VendingMachine::deploy(&provider).await.unwrap(); let contract_address = *contract.address(); // Call buy function with insufficient ether let tx_req = TransactionRequest::default() .with_from(from) .with_to(contract_address) .with_input(VendingMachine::buyCall { amount: U256::from(10) }.abi_encode()); // fill_transaction should fail because gas estimation fails due to revert let result = api.fill_transaction(WithOtherFields::new(tx_req)).await; assert!(result.is_err(), "fill_transaction should return an error when gas estimation fails"); let error_message = result.unwrap_err().to_string(); assert!( error_message.contains("execution reverted"), "Error should indicate a revert, got: {error_message}" ); } ================================================ FILE: crates/anvil/tests/it/beacon_api.rs ================================================ use crate::utils::http_provider; use alloy_consensus::{Blob, BlobTransactionSidecar, SidecarBuilder, SimpleCoder, Transaction}; use alloy_network::{TransactionBuilder, TransactionBuilder4844}; use alloy_primitives::{B256, FixedBytes, U256, b256}; use alloy_provider::Provider; use alloy_rpc_types::TransactionRequest; use alloy_rpc_types_beacon::{genesis::GenesisResponse, sidecar::GetBlobsResponse}; use alloy_serde::WithOtherFields; use anvil::{NodeConfig, spawn}; use foundry_evm::hardfork::EthereumHardfork; use ssz::Decode; #[tokio::test(flavor = "multi_thread")] async fn test_beacon_api_get_blob_sidecars() { let node_config = NodeConfig::test().with_hardfork(Some(EthereumHardfork::Cancun.into())); let (_api, handle) = spawn(node_config).await; // Test Beacon API endpoint using HTTP client let client = reqwest::Client::new(); let url = format!("{}/eth/v1/beacon/blob_sidecars/latest", handle.http_endpoint()); // This endpoint is deprecated, so we expect a 410 Gone response let response = client.get(&url).send().await.unwrap(); assert_eq!( response.text().await.unwrap(), r#"{"code":410,"message":"This endpoint is deprecated. Use `GET /eth/v1/beacon/blobs/{block_id}` instead."}"#, "Expected deprecation message for blob_sidecars endpoint" ); } #[tokio::test(flavor = "multi_thread")] async fn test_beacon_api_get_blobs() { let node_config = NodeConfig::test().with_hardfork(Some(EthereumHardfork::Cancun.into())); let (api, handle) = spawn(node_config).await; // Disable auto-mining so we can include multiple transactions in the same block api.anvil_set_auto_mine(false).await.unwrap(); let wallets = handle.dev_wallets().collect::>(); let from = wallets[0].address(); let to = wallets[1].address(); let provider = http_provider(&handle.http_endpoint()); let eip1559_est = provider.estimate_eip1559_fees().await.unwrap(); let gas_price = provider.get_gas_price().await.unwrap(); // Create multiple blob transactions to be included in the same block let blob_data = [b"Hello Beacon API - Blob 1", b"Hello Beacon API - Blob 2", b"Hello Beacon API - Blob 3"]; let mut pending_txs = Vec::new(); // Send all transactions without waiting for receipts for (i, data) in blob_data.iter().enumerate() { let sidecar: SidecarBuilder = SidecarBuilder::from_slice(data.as_slice()); let sidecar = sidecar.build::().unwrap(); let tx = TransactionRequest::default() .with_from(from) .with_to(to) .with_nonce(i as u64) .with_max_fee_per_blob_gas(gas_price + 1) .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_blob_sidecar_4844(sidecar) .value(U256::from(100)); let tx = WithOtherFields::new(tx); let pending = provider.send_transaction(tx).await.unwrap(); pending_txs.push(pending); } // Mine a block to include all transactions api.evm_mine(None).await.unwrap(); // Get receipts for all transactions let mut receipts = Vec::new(); for pending in pending_txs { let receipt = pending.get_receipt().await.unwrap(); receipts.push(receipt); } // Verify all transactions were included in the same block let block_number = receipts[0].block_number.unwrap(); for (i, receipt) in receipts.iter().enumerate() { assert_eq!( receipt.block_number.unwrap(), block_number, "Transaction {i} was not included in block {block_number}" ); } // Extract the actual versioned hashes from the mined transactions let mut actual_versioned_hashes = Vec::new(); for receipt in &receipts { let tx = provider.get_transaction_by_hash(receipt.transaction_hash).await.unwrap().unwrap(); if let Some(blob_versioned_hashes) = tx.blob_versioned_hashes() { actual_versioned_hashes.extend(blob_versioned_hashes.iter().copied()); } } // Test Beacon API endpoint using HTTP client let client = reqwest::Client::new(); let url = format!("{}/eth/v1/beacon/blobs/{}", handle.http_endpoint(), block_number); let response = client.get(&url).send().await.unwrap(); assert_eq!(response.status(), reqwest::StatusCode::OK); assert_eq!( response.headers().get("content-type").and_then(|h| h.to_str().ok()), Some("application/json"), "Expected application/json content-type header" ); let blobs_response: GetBlobsResponse = response.json().await.unwrap(); // Verify response structure assert!(!blobs_response.execution_optimistic); assert!(!blobs_response.finalized); // Verify we have blob data from all transactions assert_eq!(blobs_response.data.len(), 3, "Expected 3 blobs from 3 transactions"); // Test response with SSZ encoding let url = format!("{}/eth/v1/beacon/blobs/{}", handle.http_endpoint(), block_number); let response = client .get(&url) .header(axum::http::header::ACCEPT, "application/octet-stream") .send() .await .unwrap(); assert_eq!(response.status(), reqwest::StatusCode::OK); assert_eq!( response.headers().get("content-type").and_then(|h| h.to_str().ok()), Some("application/octet-stream"), "Expected application/octet-stream content-type header" ); let body_bytes = response.bytes().await.unwrap(); // Decode the SSZ-encoded blobs in a spawned thread with larger stack to handle recursion let decoded_blobs = std::thread::Builder::new() .stack_size(8 * 1024 * 1024) // 8MB stack for SSZ decoding of large blobs .spawn(move || Vec::::from_ssz_bytes(&body_bytes)) .expect("Failed to spawn decode thread") .join() .expect("Decode thread panicked") .expect("Failed to decode SSZ-encoded blobs"); // Verify we got exactly 3 blobs assert_eq!( decoded_blobs.len(), 3, "Expected 3 blobs from SSZ-encoded response, got {}", decoded_blobs.len() ); // Verify the decoded blobs match the JSON response blobs for (i, (decoded, json)) in decoded_blobs.iter().zip(blobs_response.data.iter()).enumerate() { assert_eq!(decoded, json, "Blob {i} mismatch between SSZ and JSON responses"); } // Test filtering with versioned_hashes query parameter - single hash let url = format!( "{}/eth/v1/beacon/blobs/{}?versioned_hashes={}", handle.http_endpoint(), block_number, actual_versioned_hashes[1] ); let response = client.get(&url).send().await.unwrap(); let status = response.status(); if status != reqwest::StatusCode::OK { let error_body = response.text().await.unwrap(); panic!("Expected OK status, got {status}: {error_body}"); } let blobs_response: GetBlobsResponse = response.json().await.unwrap(); assert_eq!( blobs_response.data.len(), 1, "Expected 1 blob when filtering by single versioned_hash" ); // Test filtering with versioned_hashes query parameter - multiple versioned_hashes // (comma-separated) let url = format!( "{}/eth/v1/beacon/blobs/{}?versioned_hashes={},{}", handle.http_endpoint(), block_number, actual_versioned_hashes[0], actual_versioned_hashes[2] ); let response = client.get(&url).send().await.unwrap(); assert_eq!(response.status(), reqwest::StatusCode::OK); let blobs_response: GetBlobsResponse = response.json().await.unwrap(); assert_eq!( blobs_response.data.len(), 2, "Expected 2 blobs when filtering by two versioned_hashes" ); // Test filtering with non-existent versioned_hash let non_existent_hash = b256!("0x0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"); let url = format!( "{}/eth/v1/beacon/blobs/{}?versioned_hashes={}", handle.http_endpoint(), block_number, non_existent_hash ); let response = client.get(&url).send().await.unwrap(); assert_eq!(response.status(), reqwest::StatusCode::OK); let blobs_response: GetBlobsResponse = response.json().await.unwrap(); assert_eq!( blobs_response.data.len(), 0, "Expected 0 blobs when filtering by non-existent versioned_hash" ); // Test with special block identifiers let test_ids = vec!["latest", "finalized", "safe", "earliest"]; for block_id in test_ids { let url = format!("{}/eth/v1/beacon/blobs/{}", handle.http_endpoint(), block_id); assert_eq!(client.get(&url).send().await.unwrap().status(), reqwest::StatusCode::OK); } let url = format!("{}/eth/v1/beacon/blobs/pending", handle.http_endpoint()); assert_eq!(client.get(&url).send().await.unwrap().status(), reqwest::StatusCode::NOT_FOUND); // Test with hex block number let url = format!("{}/eth/v1/beacon/blobs/0x{block_number:x}", handle.http_endpoint()); let response = client.get(&url).send().await.unwrap(); assert_eq!(response.status(), reqwest::StatusCode::OK); // Test with non-existent block let url = format!("{}/eth/v1/beacon/blobs/999999", handle.http_endpoint()); let response = client.get(&url).send().await.unwrap(); assert_eq!(response.status(), reqwest::StatusCode::NOT_FOUND); } #[tokio::test(flavor = "multi_thread")] async fn test_beacon_api_get_genesis() { let node_config = NodeConfig::test().with_hardfork(Some(EthereumHardfork::Cancun.into())); let (_api, handle) = spawn(node_config).await; // Test Beacon API genesis endpoint using HTTP client let client = reqwest::Client::new(); let url = format!("{}/eth/v1/beacon/genesis", handle.http_endpoint()); let response = client.get(&url).send().await.unwrap(); assert_eq!(response.status(), reqwest::StatusCode::OK); let genesis_response: GenesisResponse = response.json().await.unwrap(); assert!(genesis_response.data.genesis_time > 0); assert_eq!(genesis_response.data.genesis_validators_root, B256::ZERO); assert_eq!( genesis_response.data.genesis_fork_version, FixedBytes::from([0x00, 0x00, 0x00, 0x00]) ); } ================================================ FILE: crates/anvil/tests/it/eip2935.rs ================================================ use crate::utils::http_provider; use alloy_eips::{BlockNumberOrTag, eip2935::HISTORY_STORAGE_ADDRESS}; use alloy_network::TransactionBuilder; use alloy_primitives::U256; use alloy_provider::Provider; use alloy_rpc_types::TransactionRequest; use anvil::{NodeConfig, spawn}; use foundry_evm::hardfork::EthereumHardfork; #[tokio::test(flavor = "multi_thread")] async fn eip2935_contract_deployed_at_genesis() { 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 code = provider.get_code_at(HISTORY_STORAGE_ADDRESS).await.unwrap(); assert!(!code.is_empty(), "EIP-2935 history storage contract should be deployed at genesis"); } #[tokio::test(flavor = "multi_thread")] async fn eip2935_stores_parent_block_hash() { 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()); // Mine a few blocks so there are parent hashes to store api.mine_one().await; api.mine_one().await; api.mine_one().await; // Block 1's hash should be stored when block 2 was mined let block1 = provider .get_block_by_number(BlockNumberOrTag::from(1)) .await .unwrap() .expect("block 1 should exist"); let block1_hash = block1.header.hash; // Query the history storage contract for block 1's hash. // The EIP-2935 contract uses raw calldata (not ABI-encoded): pass the block number // as a 32-byte big-endian word directly. let call_data: [u8; 32] = U256::from(1).to_be_bytes(); let tx = TransactionRequest::default().with_to(HISTORY_STORAGE_ADDRESS).with_input(call_data); let result = provider.call(tx.into()).await.unwrap(); let stored_hash = alloy_primitives::B256::from_slice(&result); assert_eq!(stored_hash, block1_hash, "EIP-2935 contract should store parent block hash"); } #[tokio::test(flavor = "multi_thread")] async fn eip2935_no_system_call_on_genesis() { 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()); // At genesis (block 0), the contract should exist but no system call should have // written any parent hash into its storage. Check raw storage slot 0 directly. let slot = provider.get_storage_at(HISTORY_STORAGE_ADDRESS, U256::from(0)).await.unwrap(); assert_eq!(slot, U256::ZERO, "No hash should be stored in the contract at genesis"); } #[tokio::test(flavor = "multi_thread")] async fn eip2935_not_deployed_before_prague() { let node_config = NodeConfig::test().with_hardfork(Some(EthereumHardfork::Cancun.into())); let (_api, handle) = spawn(node_config).await; let provider = http_provider(&handle.http_endpoint()); let code = provider.get_code_at(HISTORY_STORAGE_ADDRESS).await.unwrap(); assert!(code.is_empty(), "EIP-2935 contract should NOT be deployed before Prague"); } ================================================ FILE: crates/anvil/tests/it/eip4844.rs ================================================ use crate::utils::{http_provider, http_provider_with_signer}; use alloy_consensus::{BlobTransactionSidecar, SidecarBuilder, SimpleCoder, Transaction}; use alloy_eips::{ Typed2718, eip4844::{BLOB_TX_MIN_BLOB_GASPRICE, DATA_GAS_PER_BLOB, MAX_DATA_GAS_PER_BLOCK_DENCUN}, }; use alloy_network::{EthereumWallet, ReceiptResponse, TransactionBuilder, TransactionBuilder4844}; use alloy_primitives::{Address, U256, b256}; use alloy_provider::{Provider, ProviderBuilder}; use alloy_rpc_types::{BlockId, TransactionRequest}; use alloy_serde::WithOtherFields; use anvil::{NodeConfig, spawn}; use foundry_evm::hardfork::EthereumHardfork; use foundry_test_utils::rpc; #[tokio::test(flavor = "multi_thread")] async fn can_send_eip4844_transaction() { let node_config = NodeConfig::test().with_hardfork(Some(EthereumHardfork::Cancun.into())); let (_api, handle) = spawn(node_config).await; let wallets = handle.dev_wallets().collect::>(); let from = wallets[0].address(); let to = wallets[1].address(); let provider = http_provider(&handle.http_endpoint()); let eip1559_est = provider.estimate_eip1559_fees().await.unwrap(); let gas_price = provider.get_gas_price().await.unwrap(); let sidecar: SidecarBuilder = SidecarBuilder::from_slice(b"Hello World"); let sidecar = sidecar.build().unwrap(); let tx = TransactionRequest::default() .with_from(from) .with_to(to) .with_nonce(0) .with_max_fee_per_blob_gas(gas_price + 1) .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_blob_sidecar_4844(sidecar) .value(U256::from(5)); let tx = WithOtherFields::new(tx); let receipt = provider.send_transaction(tx).await.unwrap().get_receipt().await.unwrap(); assert_eq!(receipt.blob_gas_used, Some(131072)); assert_eq!(receipt.blob_gas_price, Some(0x1)); // 1 wei } #[tokio::test(flavor = "multi_thread")] async fn can_send_eip4844_transaction_fork() { let node_config = NodeConfig::test() .with_eth_rpc_url(Some(rpc::next_http_archive_rpc_url())) .with_fork_block_number(Some(23432306u64)) .with_hardfork(Some(EthereumHardfork::Cancun.into())); let (api, handle) = spawn(node_config).await; let provider = handle.http_provider(); let accounts = provider.get_accounts().await.unwrap(); let alice = accounts[0]; let bob = accounts[1]; let sidecar: SidecarBuilder = SidecarBuilder::from_slice(b"Blobs are fun!"); let sidecar: BlobTransactionSidecar = sidecar.build().unwrap(); let tx = TransactionRequest::default() .with_from(alice) .with_to(bob) .with_blob_sidecar_4844(sidecar.clone()); let pending_tx = provider.send_transaction(tx.into()).await.unwrap(); let receipt = pending_tx.get_receipt().await.unwrap(); let tx_hash = receipt.transaction_hash; let _blobs = api.anvil_get_blob_by_tx_hash(tx_hash).unwrap().unwrap(); } #[tokio::test(flavor = "multi_thread")] async fn can_send_eip4844_transaction_eth_send_transaction() { let node_config = NodeConfig::test() .with_eth_rpc_url(Some(rpc::next_http_archive_rpc_url())) .with_fork_block_number(Some(23552208u64)) .with_hardfork(Some(EthereumHardfork::Cancun.into())); let (api, handle) = spawn(node_config).await; let provider = ProviderBuilder::new().connect(handle.http_endpoint().as_str()).await.unwrap(); let accounts = provider.get_accounts().await.unwrap(); let alice = accounts[0]; let bob = accounts[1]; let sidecar: SidecarBuilder = SidecarBuilder::from_slice(b"Blobs are fun!"); let sidecar: BlobTransactionSidecar = sidecar.build().unwrap(); let tx = TransactionRequest::default() .with_from(alice) .with_to(bob) .with_blob_sidecar_4844(sidecar.clone()); let pending_tx = provider.send_transaction(tx).await.unwrap(); let receipt = pending_tx.get_receipt().await.unwrap(); let tx_hash = receipt.transaction_hash; let _blobs = api.anvil_get_blob_by_tx_hash(tx_hash).unwrap().unwrap(); } // #[tokio::test(flavor = "multi_thread")] async fn can_send_eip4844_transaction_with_eip7594_sidecar_format() { let node_config = NodeConfig::test().with_hardfork(Some(EthereumHardfork::Osaka.into())); let (api, handle) = spawn(node_config).await; let provider = ProviderBuilder::new().connect(handle.http_endpoint().as_str()).await.unwrap(); let accounts = provider.get_accounts().await.unwrap(); let alice = accounts[0]; let bob = accounts[1]; let sidecar: SidecarBuilder = SidecarBuilder::from_slice(b"Blobs are fun!"); let sidecar = sidecar.build_7594().unwrap(); let tx = TransactionRequest::default().with_from(alice).with_to(bob).with_blob_sidecar_7594(sidecar); let pending_tx = provider.send_transaction(tx).await.unwrap(); let receipt = pending_tx.get_receipt().await.unwrap(); let tx_hash = receipt.transaction_hash; let _blobs = api.anvil_get_blob_by_tx_hash(tx_hash).unwrap().unwrap(); } #[tokio::test(flavor = "multi_thread")] async fn can_send_multiple_blobs_in_one_tx() { let node_config = NodeConfig::test().with_hardfork(Some(EthereumHardfork::Cancun.into())); let (_api, handle) = spawn(node_config).await; let wallets = handle.dev_wallets().collect::>(); let from = wallets[0].address(); let to = wallets[1].address(); let provider = http_provider(&handle.http_endpoint()); let eip1559_est = provider.estimate_eip1559_fees().await.unwrap(); let gas_price = provider.get_gas_price().await.unwrap(); let large_data = vec![1u8; DATA_GAS_PER_BLOB as usize * 5]; // 131072 is DATA_GAS_PER_BLOB and also BYTE_PER_BLOB let sidecar: SidecarBuilder = SidecarBuilder::from_slice(&large_data); let sidecar = sidecar.build().unwrap(); let tx = TransactionRequest::default() .with_from(from) .with_to(to) .with_nonce(0) .with_max_fee_per_blob_gas(gas_price + 1) .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_blob_sidecar_4844(sidecar); let tx = WithOtherFields::new(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_DENCUN)); assert_eq!(receipt.blob_gas_price, Some(0x1)); // 1 wei } #[tokio::test(flavor = "multi_thread")] async fn cannot_exceed_six_blobs() { let node_config = NodeConfig::test().with_hardfork(Some(EthereumHardfork::Cancun.into())); let (_api, handle) = spawn(node_config).await; let wallets = handle.dev_wallets().collect::>(); let from = wallets[0].address(); let to = wallets[1].address(); let provider = http_provider(&handle.http_endpoint()); let eip1559_est = provider.estimate_eip1559_fees().await.unwrap(); let gas_price = provider.get_gas_price().await.unwrap(); let large_data = vec![1u8; DATA_GAS_PER_BLOB as usize * 6]; // 131072 is DATA_GAS_PER_BLOB and also BYTE_PER_BLOB let sidecar: SidecarBuilder = SidecarBuilder::from_slice(&large_data); let sidecar = sidecar.build().unwrap(); let tx = TransactionRequest::default() .with_from(from) .with_to(to) .with_nonce(0) .with_max_fee_per_blob_gas(gas_price + 1) .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_blob_sidecar_4844(sidecar); let tx = WithOtherFields::new(tx); let err = provider.send_transaction(tx).await.unwrap_err(); assert!(err.to_string().contains("too many blobs")); } #[tokio::test(flavor = "multi_thread")] async fn can_mine_blobs_when_exceeds_max_blobs() { let node_config = NodeConfig::test().with_hardfork(Some(EthereumHardfork::Cancun.into())); let (api, handle) = spawn(node_config).await; api.anvil_set_auto_mine(false).await.unwrap(); let wallets = handle.dev_wallets().collect::>(); let from = wallets[0].address(); let to = wallets[1].address(); let provider = http_provider(&handle.http_endpoint()); let eip1559_est = provider.estimate_eip1559_fees().await.unwrap(); let gas_price = provider.get_gas_price().await.unwrap(); let first_batch = vec![1u8; DATA_GAS_PER_BLOB as usize * 3]; let sidecar: SidecarBuilder = SidecarBuilder::from_slice(&first_batch); let num_blobs_first = sidecar.clone().take().len() as u64; let sidecar = sidecar.build().unwrap(); let tx = TransactionRequest::default() .with_from(from) .with_to(to) .with_nonce(0) .with_max_fee_per_blob_gas(gas_price + 1) .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_blob_sidecar_4844(sidecar); let mut tx = WithOtherFields::new(tx); let first_tx = provider.send_transaction(tx.clone()).await.unwrap(); let second_batch = vec![1u8; DATA_GAS_PER_BLOB as usize * 2]; let sidecar: SidecarBuilder = SidecarBuilder::from_slice(&second_batch); let num_blobs_second = sidecar.clone().take().len() as u64; let sidecar = sidecar.build().unwrap(); tx.set_blob_sidecar_4844(sidecar); tx.set_nonce(1); let second_tx = provider.send_transaction(tx).await.unwrap(); api.mine_one().await; let first_receipt = first_tx.get_receipt().await.unwrap(); api.mine_one().await; let second_receipt = second_tx.get_receipt().await.unwrap(); let (first_block, second_block) = tokio::join!( provider.get_block_by_number(first_receipt.block_number.unwrap().into()), provider.get_block_by_number(second_receipt.block_number.unwrap().into()) ); assert_eq!( first_block.unwrap().unwrap().header.blob_gas_used, Some(DATA_GAS_PER_BLOB * num_blobs_first) ); assert_eq!( second_block.unwrap().unwrap().header.blob_gas_used, Some(DATA_GAS_PER_BLOB * num_blobs_second) ); // Mined in two different blocks assert_eq!(first_receipt.block_number.unwrap() + 1, second_receipt.block_number.unwrap()); } #[tokio::test(flavor = "multi_thread")] async fn can_check_blob_fields_on_genesis() { let node_config = NodeConfig::test().with_hardfork(Some(EthereumHardfork::Cancun.into())); let (_api, handle) = spawn(node_config).await; let provider = http_provider(&handle.http_endpoint()); let block = provider.get_block(BlockId::latest()).await.unwrap().unwrap(); assert_eq!(block.header.blob_gas_used, Some(0)); assert_eq!(block.header.excess_blob_gas, Some(0)); } #[expect(clippy::disallowed_macros)] #[tokio::test(flavor = "multi_thread")] async fn can_correctly_estimate_blob_gas_with_recommended_fillers() { let node_config = NodeConfig::test().with_hardfork(Some(EthereumHardfork::Cancun.into())); let (_api, handle) = spawn(node_config).await; let provider = http_provider(&handle.http_endpoint()); let accounts = provider.get_accounts().await.unwrap(); let alice = accounts[0]; let bob = accounts[1]; let sidecar: SidecarBuilder = SidecarBuilder::from_slice(b"Blobs are fun!"); let sidecar = sidecar.build().unwrap(); let tx = TransactionRequest::default().with_to(bob).with_blob_sidecar_4844(sidecar); let tx = WithOtherFields::new(tx); // Send the transaction and wait for the broadcast. let pending_tx = provider.send_transaction(tx).await.unwrap(); println!("Pending transaction... {}", pending_tx.tx_hash()); // Wait for the transaction to be included and get the receipt. let receipt = pending_tx.get_receipt().await.unwrap(); // Grab the processed transaction. let tx = provider.get_transaction_by_hash(receipt.transaction_hash).await.unwrap().unwrap(); println!( "Transaction included in block {}", receipt.block_number.expect("Failed to get block number") ); assert!(tx.max_fee_per_blob_gas().unwrap() >= BLOB_TX_MIN_BLOB_GASPRICE); assert_eq!(receipt.from, alice); assert_eq!(receipt.to, Some(bob)); assert_eq!( receipt.blob_gas_used.expect("Expected to be EIP-4844 transaction"), DATA_GAS_PER_BLOB ); } #[expect(clippy::disallowed_macros)] #[tokio::test(flavor = "multi_thread")] async fn can_correctly_estimate_blob_gas_with_recommended_fillers_with_signer() { let node_config = NodeConfig::test().with_hardfork(Some(EthereumHardfork::Cancun.into())); let (_api, handle) = spawn(node_config).await; let signer = handle.dev_wallets().next().unwrap(); let wallet: EthereumWallet = signer.clone().into(); let provider = http_provider_with_signer(&handle.http_endpoint(), wallet); let accounts = provider.get_accounts().await.unwrap(); let alice = accounts[0]; let bob = accounts[1]; let sidecar: SidecarBuilder = SidecarBuilder::from_slice(b"Blobs are fun!"); let sidecar = sidecar.build().unwrap(); let tx = TransactionRequest::default().with_to(bob).with_blob_sidecar_4844(sidecar); let tx = WithOtherFields::new(tx); // Send the transaction and wait for the broadcast. let pending_tx = provider.send_transaction(tx).await.unwrap(); println!("Pending transaction... {}", pending_tx.tx_hash()); // Wait for the transaction to be included and get the receipt. let receipt = pending_tx.get_receipt().await.unwrap(); // Grab the processed transaction. let tx = provider.get_transaction_by_hash(receipt.transaction_hash).await.unwrap().unwrap(); println!( "Transaction included in block {}", receipt.block_number.expect("Failed to get block number") ); assert!(tx.max_fee_per_blob_gas().unwrap() >= BLOB_TX_MIN_BLOB_GASPRICE); assert_eq!(receipt.from, alice); assert_eq!(receipt.to, Some(bob)); assert_eq!( receipt.blob_gas_used.expect("Expected to be EIP-4844 transaction"), DATA_GAS_PER_BLOB ); } // #[tokio::test] async fn can_bypass_sidecar_requirement() { crate::init_tracing(); let node_config = NodeConfig::test() .with_hardfork(Some(EthereumHardfork::Cancun.into())) .with_auto_impersonate(true); let (api, handle) = spawn(node_config).await; let provider = http_provider(&handle.http_endpoint()); let eip1559_est = provider.estimate_eip1559_fees().await.unwrap(); let gas_price = provider.get_gas_price().await.unwrap(); let from = Address::random(); let to = Address::random(); api.anvil_set_balance(from, U256::from(60262144030131080_u128)).await.unwrap(); let tx = TransactionRequest { from: Some(from), to: Some(alloy_primitives::TxKind::Call(to)), nonce: Some(0), value: Some(U256::from(0)), max_fee_per_blob_gas: Some(gas_price + 1), max_fee_per_gas: Some(eip1559_est.max_fee_per_gas), max_priority_fee_per_gas: Some(eip1559_est.max_priority_fee_per_gas), blob_versioned_hashes: Some(vec![b256!( "0x01d5446006b21888d0267829344ab8624fdf1b425445a8ae1ca831bf1b8fbcd4" )]), sidecar: None, transaction_type: Some(3), ..Default::default() }; let receipt = provider .send_transaction(WithOtherFields::new(tx)) .await .unwrap() .get_receipt() .await .unwrap(); assert!(receipt.status()); let tx = provider.get_transaction_by_hash(receipt.transaction_hash).await.unwrap().unwrap(); assert_eq!(tx.inner.ty(), 3); } #[tokio::test(flavor = "multi_thread")] async fn can_get_blobs_by_versioned_hash() { let node_config = NodeConfig::test().with_hardfork(Some(EthereumHardfork::Prague.into())); let (api, handle) = spawn(node_config).await; let wallets = handle.dev_wallets().collect::>(); let from = wallets[0].address(); let to = wallets[1].address(); let provider = http_provider(&handle.http_endpoint()); let eip1559_est = provider.estimate_eip1559_fees().await.unwrap(); let gas_price = provider.get_gas_price().await.unwrap(); let sidecar: SidecarBuilder = SidecarBuilder::from_slice(b"Hello World"); let sidecar: BlobTransactionSidecar = sidecar.build().unwrap(); let tx = TransactionRequest::default() .with_from(from) .with_to(to) .with_nonce(0) .with_max_fee_per_blob_gas(gas_price + 1) .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_blob_sidecar_4844(sidecar.clone()) .value(U256::from(5)); let tx = WithOtherFields::new(tx); let _receipt = provider.send_transaction(tx).await.unwrap().get_receipt().await.unwrap(); let hash = sidecar.versioned_hash_for_blob(0).unwrap(); // api.anvil_set_auto_mine(true).await.unwrap(); let blob = api.anvil_get_blob_by_versioned_hash(hash).unwrap().unwrap(); assert_eq!(blob, sidecar.blobs[0]); } #[tokio::test(flavor = "multi_thread")] async fn can_get_blobs_by_tx_hash() { let node_config = NodeConfig::test().with_hardfork(Some(EthereumHardfork::Prague.into())); let (api, handle) = spawn(node_config).await; let wallets = handle.dev_wallets().collect::>(); let from = wallets[0].address(); let to = wallets[1].address(); let provider = http_provider(&handle.http_endpoint()); let eip1559_est = provider.estimate_eip1559_fees().await.unwrap(); let gas_price = provider.get_gas_price().await.unwrap(); let sidecar: SidecarBuilder = SidecarBuilder::from_slice(b"Hello World"); let sidecar: BlobTransactionSidecar = sidecar.build().unwrap(); let tx = TransactionRequest::default() .with_from(from) .with_to(to) .with_nonce(0) .with_max_fee_per_blob_gas(gas_price + 1) .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_blob_sidecar_4844(sidecar.clone()) .value(U256::from(5)); let tx = WithOtherFields::new(tx); let receipt = provider.send_transaction(tx).await.unwrap().get_receipt().await.unwrap(); let hash = receipt.transaction_hash; api.anvil_set_auto_mine(true).await.unwrap(); let blobs = api.anvil_get_blob_by_tx_hash(hash).unwrap().unwrap(); assert_eq!(blobs, sidecar.blobs); } ================================================ FILE: crates/anvil/tests/it/eip7702.rs ================================================ use crate::utils::http_provider; use alloy_consensus::{SignableTransaction, transaction::TxEip7702}; use alloy_network::{ReceiptResponse, TransactionBuilder, TxSignerSync}; use alloy_primitives::{U256, bytes}; use alloy_provider::{PendingTransactionConfig, Provider}; use alloy_rpc_types::{Authorization, TransactionRequest}; use alloy_serde::WithOtherFields; use alloy_signer::{Signature, SignerSync}; use anvil::{NodeConfig, spawn}; use foundry_evm::hardfork::EthereumHardfork; #[tokio::test(flavor = "multi_thread")] async fn can_send_eip7702_tx() { 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 mut 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 signature = wallets[1].sign_transaction_sync(&mut tx).unwrap(); let tx = tx.into_signed(signature); let mut encoded = Vec::new(); tx.eip2718_encode(&mut encoded); let receipt = provider.send_raw_transaction(&encoded).await.unwrap().get_receipt().await.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); } #[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); } #[tokio::test(flavor = "multi_thread")] async fn eip7702_authorization_bypass() { 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 fake_auth_sig = Signature::new(U256::ZERO, U256::ZERO, true); api.anvil_impersonate_signature(fake_auth_sig.as_bytes().into(), from).await.unwrap(); let authorization = authorization.into_signed(fake_auth_sig); let log_data = bytes!("11112222"); let mut 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 signature = wallets[1].sign_transaction_sync(&mut tx).unwrap(); let tx = tx.into_signed(signature); let mut encoded = Vec::new(); tx.eip2718_encode(&mut encoded); let receipt = provider.send_raw_transaction(&encoded).await.unwrap().get_receipt().await.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); } ================================================ FILE: crates/anvil/tests/it/fork.rs ================================================ //! various fork related test use crate::{ abi::{ERC721, Greeter}, utils::{http_provider, http_provider_with_signer}, }; use alloy_chains::NamedChain; use alloy_eips::{ eip7840::BlobParams, eip7910::{EthConfig, SystemContract}, }; use alloy_network::{EthereumWallet, ReceiptResponse, TransactionBuilder, TransactionResponse}; use alloy_primitives::{Address, Bytes, TxHash, TxKind, U64, U256, address, b256, bytes, uint}; use alloy_provider::Provider; use alloy_rpc_types::{ AccountInfo, BlockId, BlockNumberOrTag, anvil::Forking, request::{TransactionInput, TransactionRequest}, state::EvmOverrides, }; use alloy_serde::WithOtherFields; use alloy_signer_local::PrivateKeySigner; use anvil::{EthereumHardfork, NodeConfig, NodeHandle, PrecompileFactory, eth::EthApi, spawn}; use foundry_common::provider::get_http_provider; use foundry_config::Config; use foundry_evm_networks::NetworkConfigs; use foundry_primitives::FoundryNetwork; use foundry_test_utils::rpc::{self, next_http_rpc_endpoint, next_rpc_endpoint}; use futures::StreamExt; use std::{ collections::{BTreeMap, BTreeSet}, sync::Arc, thread::sleep, time::Duration, }; const BLOCK_NUMBER: u64 = 14_608_400u64; const DEAD_BALANCE_AT_BLOCK_NUMBER: u128 = 12_556_069_338_441_120_059_867u128; const BLOCK_TIMESTAMP: u64 = 1_650_274_250u64; /// Represents an anvil fork of an anvil node #[expect(unused)] pub struct LocalFork { origin_api: EthApi, origin_handle: NodeHandle, fork_api: EthApi, fork_handle: NodeHandle, } #[expect(dead_code)] impl LocalFork { /// Spawns two nodes with the test config pub async fn new() -> Self { Self::setup(NodeConfig::test(), NodeConfig::test()).await } /// Spawns two nodes where one is a fork of the other pub async fn setup(origin: NodeConfig, fork: NodeConfig) -> Self { let (origin_api, origin_handle) = spawn(origin).await; let (fork_api, fork_handle) = spawn(fork.with_eth_rpc_url(Some(origin_handle.http_endpoint()))).await; Self { origin_api, origin_handle, fork_api, fork_handle } } } pub fn fork_config() -> NodeConfig { NodeConfig::test() .with_eth_rpc_url(Some(rpc::next_http_archive_rpc_url())) .with_fork_block_number(Some(BLOCK_NUMBER)) } #[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))).await; assert_eq!(api.gas_limit(), uint!(10_000_000_U256)); } #[tokio::test(flavor = "multi_thread")] async fn test_fork_gas_limit_disabled_from_config() { let (api, handle) = spawn(fork_config().disable_block_gas_limit(true)).await; // see https://github.com/foundry-rs/foundry/pull/8933 assert_eq!(api.gas_limit(), U256::from(U64::MAX)); // try to mine a couple blocks let provider = handle.http_provider(); let tx = TransactionRequest::default() .to(Address::random()) .value(U256::from(1337u64)) .from(handle.dev_wallets().next().unwrap().address()); let tx = WithOtherFields::new(tx); let _ = provider.send_transaction(tx).await.unwrap().get_receipt().await.unwrap(); let tx = TransactionRequest::default() .to(Address::random()) .value(U256::from(1337u64)) .from(handle.dev_wallets().next().unwrap().address()); let tx = WithOtherFields::new(tx); let _ = provider.send_transaction(tx).await.unwrap().get_receipt().await.unwrap(); } #[tokio::test(flavor = "multi_thread")] async fn test_spawn_fork() { let (api, _handle) = spawn(fork_config()).await; assert!(api.is_fork()); let head = api.block_number().unwrap(); assert_eq!(head, U256::from(BLOCK_NUMBER)) } #[tokio::test(flavor = "multi_thread")] async fn test_fork_eth_get_balance() { let (api, handle) = spawn(fork_config()).await; let provider = handle.http_provider(); for _ in 0..10 { let addr = Address::random(); let balance = api.balance(addr, None).await.unwrap(); let provider_balance = provider.get_balance(addr).await.unwrap(); assert_eq!(balance, provider_balance) } } // #[tokio::test(flavor = "multi_thread")] async fn test_fork_eth_get_balance_after_mine() { let (api, handle) = spawn(fork_config()).await; let provider = handle.http_provider(); let info = api.anvil_node_info().await.unwrap(); let number = info.fork_config.fork_block_number.unwrap(); assert_eq!(number, BLOCK_NUMBER); let address = Address::random(); let _balance = provider.get_balance(address).await.unwrap(); api.evm_mine(None).await.unwrap(); let _balance = provider.get_balance(address).await.unwrap(); } // #[tokio::test(flavor = "multi_thread")] async fn test_fork_eth_get_code_after_mine() { let (api, handle) = spawn(fork_config()).await; let provider = handle.http_provider(); let info = api.anvil_node_info().await.unwrap(); let number = info.fork_config.fork_block_number.unwrap(); assert_eq!(number, BLOCK_NUMBER); let address = Address::random(); let _code = provider.get_code_at(address).block_id(BlockId::number(1)).await.unwrap(); api.evm_mine(None).await.unwrap(); let _code = provider.get_code_at(address).block_id(BlockId::number(1)).await.unwrap(); } #[tokio::test(flavor = "multi_thread")] async fn test_fork_eth_get_code() { let (api, handle) = spawn(fork_config()).await; let provider = handle.http_provider(); for _ in 0..10 { let addr = Address::random(); let code = api.get_code(addr, None).await.unwrap(); let provider_code = provider.get_code_at(addr).await.unwrap(); assert_eq!(code, provider_code) } let addresses: Vec
= vec![ "0x6b175474e89094c44da98b954eedeac495271d0f".parse().unwrap(), "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48".parse().unwrap(), "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2".parse().unwrap(), "0x1F98431c8aD98523631AE4a59f267346ea31F984".parse().unwrap(), "0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45".parse().unwrap(), ]; for address in addresses { let prev_code = api .get_code(address, Some(BlockNumberOrTag::Number(BLOCK_NUMBER - 10).into())) .await .unwrap(); let code = api.get_code(address, None).await.unwrap(); let provider_code = provider.get_code_at(address).await.unwrap(); assert_eq!(code, prev_code); assert_eq!(code, provider_code); assert!(!code.as_ref().is_empty()); } } #[tokio::test(flavor = "multi_thread")] async fn test_fork_eth_get_nonce() { let (api, handle) = spawn(fork_config()).await; let provider = handle.http_provider(); for _ in 0..10 { let addr = Address::random(); let api_nonce = api.transaction_count(addr, None).await.unwrap().to::(); let provider_nonce = provider.get_transaction_count(addr).await.unwrap(); assert_eq!(api_nonce, provider_nonce); } let addr = Config::DEFAULT_SENDER; let api_nonce = api.transaction_count(addr, None).await.unwrap().to::(); let provider_nonce = provider.get_transaction_count(addr).await.unwrap(); assert_eq!(api_nonce, provider_nonce); } #[tokio::test(flavor = "multi_thread")] async fn test_fork_optimism_with_transaction_hash() { use std::str::FromStr; // Fork to a block with a specific transaction let fork_tx_hash = TxHash::from_str("fcb864b5a50f0f0b111dbbf9e9167b2cb6179dfd6270e1ad53aac6049c0ec038") .unwrap(); let (api, _handle) = spawn( NodeConfig::test() .with_eth_rpc_url(Some(rpc::next_rpc_endpoint(NamedChain::Optimism))) .with_fork_transaction_hash(Some(fork_tx_hash)), ) .await; // Make sure the fork starts from previous block let block_number = api.block_number().unwrap().to::(); assert_eq!(block_number, 125777954 - 1); } #[tokio::test(flavor = "multi_thread")] async fn test_fork_eth_fee_history() { let (api, handle) = spawn(fork_config()).await; let provider = handle.http_provider(); let count = 10u64; let _history = api.fee_history(U256::from(count), BlockNumberOrTag::Latest, vec![]).await.unwrap(); let _provider_history = provider.get_fee_history(count, BlockNumberOrTag::Latest, &[]).await.unwrap(); } #[tokio::test(flavor = "multi_thread")] async fn test_fork_reset() { let (api, handle) = spawn(fork_config()).await; let provider = handle.http_provider(); let accounts: Vec<_> = handle.dev_wallets().collect(); let from = accounts[0].address(); let to = accounts[1].address(); let block_number = provider.get_block_number().await.unwrap(); let balance_before = provider.get_balance(to).await.unwrap(); let amount = handle.genesis_balance().checked_div(U256::from(2u64)).unwrap(); let initial_nonce = provider.get_transaction_count(from).await.unwrap(); let tx = TransactionRequest::default().to(to).value(amount).from(from); let tx = WithOtherFields::new(tx); let tx = provider.send_transaction(tx).await.unwrap().get_receipt().await.unwrap(); assert_eq!(tx.transaction_index, Some(0)); let nonce = provider.get_transaction_count(from).await.unwrap(); assert_eq!(nonce, initial_nonce + 1); let to_balance = provider.get_balance(to).await.unwrap(); assert_eq!(balance_before.saturating_add(amount), to_balance); api.anvil_reset(Some(Forking { json_rpc_url: None, block_number: Some(block_number) })) .await .unwrap(); // reset block number assert_eq!(block_number, provider.get_block_number().await.unwrap()); let nonce = provider.get_transaction_count(from).await.unwrap(); assert_eq!(nonce, initial_nonce); let balance = provider.get_balance(from).await.unwrap(); assert_eq!(balance, handle.genesis_balance()); let balance = provider.get_balance(to).await.unwrap(); assert_eq!(balance, handle.genesis_balance()); // reset to latest api.anvil_reset(Some(Forking::default())).await.unwrap(); let new_block_num = provider.get_block_number().await.unwrap(); assert!(new_block_num > block_number); } #[tokio::test(flavor = "multi_thread")] async fn test_fork_reset_setup() { let (api, handle) = spawn(NodeConfig::test()).await; let provider = handle.http_provider(); let dead_addr: Address = "000000000000000000000000000000000000dEaD".parse().unwrap(); let block_number = provider.get_block_number().await.unwrap(); assert_eq!(block_number, 0); let local_balance = provider.get_balance(dead_addr).await.unwrap(); assert_eq!(local_balance, U256::ZERO); api.anvil_reset(Some(Forking { json_rpc_url: Some(rpc::next_http_archive_rpc_url()), block_number: Some(BLOCK_NUMBER), })) .await .unwrap(); let block_number = provider.get_block_number().await.unwrap(); assert_eq!(block_number, BLOCK_NUMBER); let remote_balance = provider.get_balance(dead_addr).await.unwrap(); assert_eq!(remote_balance, U256::from(DEAD_BALANCE_AT_BLOCK_NUMBER)); } #[tokio::test(flavor = "multi_thread")] async fn test_fork_state_snapshotting() { let (api, handle) = spawn(fork_config()).await; let provider = handle.http_provider(); let state_snapshot = api.evm_snapshot().await.unwrap(); let accounts: Vec<_> = handle.dev_wallets().collect(); let from = accounts[0].address(); let to = accounts[1].address(); let block_number = provider.get_block_number().await.unwrap(); let initial_nonce = provider.get_transaction_count(from).await.unwrap(); let balance_before = provider.get_balance(to).await.unwrap(); let amount = handle.genesis_balance().checked_div(U256::from(2u64)).unwrap(); let provider = handle.http_provider(); let tx = TransactionRequest::default().to(to).value(amount).from(from); let tx = WithOtherFields::new(tx); let _ = provider.send_transaction(tx).await.unwrap().get_receipt().await.unwrap(); let provider = handle.http_provider(); let nonce = provider.get_transaction_count(from).await.unwrap(); assert_eq!(nonce, initial_nonce + 1); let to_balance = provider.get_balance(to).await.unwrap(); assert_eq!(balance_before.saturating_add(amount), to_balance); assert!(api.evm_revert(state_snapshot).await.unwrap()); let nonce = provider.get_transaction_count(from).await.unwrap(); assert_eq!(nonce, initial_nonce); let balance = provider.get_balance(from).await.unwrap(); assert_eq!(balance, handle.genesis_balance()); let balance = provider.get_balance(to).await.unwrap(); assert_eq!(balance, handle.genesis_balance()); assert_eq!(block_number, provider.get_block_number().await.unwrap()); } #[tokio::test(flavor = "multi_thread")] async fn test_fork_state_snapshotting_repeated() { let (api, handle) = spawn(fork_config()).await; let provider = handle.http_provider(); let state_snapshot = api.evm_snapshot().await.unwrap(); let accounts: Vec<_> = handle.dev_wallets().collect(); let from = accounts[0].address(); let to = accounts[1].address(); let block_number = provider.get_block_number().await.unwrap(); let initial_nonce = provider.get_transaction_count(from).await.unwrap(); let balance_before = provider.get_balance(to).await.unwrap(); let amount = handle.genesis_balance().checked_div(U256::from(92u64)).unwrap(); let tx = TransactionRequest::default().to(to).value(amount).from(from); let tx = WithOtherFields::new(tx); let tx_provider = handle.http_provider(); let _ = tx_provider.send_transaction(tx).await.unwrap().get_receipt().await.unwrap(); let nonce = provider.get_transaction_count(from).await.unwrap(); assert_eq!(nonce, initial_nonce + 1); let to_balance = provider.get_balance(to).await.unwrap(); assert_eq!(balance_before.saturating_add(amount), to_balance); let _second_state_snapshot = api.evm_snapshot().await.unwrap(); assert!(api.evm_revert(state_snapshot).await.unwrap()); let nonce = provider.get_transaction_count(from).await.unwrap(); assert_eq!(nonce, initial_nonce); let balance = provider.get_balance(from).await.unwrap(); assert_eq!(balance, handle.genesis_balance()); let balance = provider.get_balance(to).await.unwrap(); assert_eq!(balance, handle.genesis_balance()); assert_eq!(block_number, provider.get_block_number().await.unwrap()); // invalidated // TODO enable after // assert!(!api.evm_revert(second_snapshot).await.unwrap()); // nothing is reverted, snapshot gone assert!(!api.evm_revert(state_snapshot).await.unwrap()); } // #[tokio::test(flavor = "multi_thread")] async fn test_fork_state_snapshotting_blocks() { let (api, handle) = spawn(fork_config()).await; let provider = handle.http_provider(); let state_snapshot = api.evm_snapshot().await.unwrap(); let accounts: Vec<_> = handle.dev_wallets().collect(); let from = accounts[0].address(); let to = accounts[1].address(); let block_number = provider.get_block_number().await.unwrap(); let initial_nonce = provider.get_transaction_count(from).await.unwrap(); let balance_before = provider.get_balance(to).await.unwrap(); let amount = handle.genesis_balance().checked_div(U256::from(2u64)).unwrap(); // send the transaction let tx = TransactionRequest::default().to(to).value(amount).from(from); let tx = WithOtherFields::new(tx); let _ = provider.send_transaction(tx.clone()).await.unwrap().get_receipt().await.unwrap(); let block_number_after = provider.get_block_number().await.unwrap(); assert_eq!(block_number_after, block_number + 1); let nonce = provider.get_transaction_count(from).await.unwrap(); assert_eq!(nonce, initial_nonce + 1); let to_balance = provider.get_balance(to).await.unwrap(); assert_eq!(balance_before.saturating_add(amount), to_balance); assert!(api.evm_revert(state_snapshot).await.unwrap()); assert_eq!(initial_nonce, provider.get_transaction_count(from).await.unwrap()); let block_number_after = provider.get_block_number().await.unwrap(); assert_eq!(block_number_after, block_number); // repeat transaction let _ = provider.send_transaction(tx.clone()).await.unwrap().get_receipt().await.unwrap(); let nonce = provider.get_transaction_count(from).await.unwrap(); assert_eq!(nonce, initial_nonce + 1); // revert again: nothing to revert since state snapshot gone assert!(!api.evm_revert(state_snapshot).await.unwrap()); let nonce = provider.get_transaction_count(from).await.unwrap(); assert_eq!(nonce, initial_nonce + 1); let block_number_after = provider.get_block_number().await.unwrap(); assert_eq!(block_number_after, block_number + 1); } /// tests that the remote state and local state are kept separate. /// changes don't make into the read only Database that holds the remote state, which is flushed to /// a cache file. #[tokio::test(flavor = "multi_thread")] async fn test_separate_states() { let (api, handle) = spawn(fork_config().with_fork_block_number(Some(14723772u64))).await; let provider = handle.http_provider(); let addr: Address = "000000000000000000000000000000000000dEaD".parse().unwrap(); let remote_balance = provider.get_balance(addr).await.unwrap(); assert_eq!(remote_balance, U256::from(12556104082473169733500u128)); api.anvil_set_balance(addr, U256::from(1337u64)).await.unwrap(); let balance = provider.get_balance(addr).await.unwrap(); assert_eq!(balance, U256::from(1337u64)); let fork = api.get_fork().unwrap(); let fork_db = fork.database.read().await; let acc = fork_db .maybe_inner() .expect("could not get fork db inner") .db() .accounts .read() .get(&addr) .cloned() .unwrap(); assert_eq!(acc.balance, remote_balance); } #[tokio::test(flavor = "multi_thread")] async fn can_deploy_greeter_on_fork() { let (_api, handle) = spawn(fork_config().with_fork_block_number(Some(14723772u64))).await; let wallet = handle.dev_wallets().next().unwrap(); let signer: EthereumWallet = wallet.into(); let provider = http_provider_with_signer(&handle.http_endpoint(), signer); 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); 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); } #[tokio::test(flavor = "multi_thread")] async fn can_reset_properly() { let (origin_api, origin_handle) = spawn(NodeConfig::test()).await; let account = origin_handle.dev_accounts().next().unwrap(); let origin_provider = origin_handle.http_provider(); let origin_nonce = 1u64; origin_api.anvil_set_nonce(account, U256::from(origin_nonce)).await.unwrap(); assert_eq!(origin_nonce, origin_provider.get_transaction_count(account).await.unwrap()); let (fork_api, fork_handle) = spawn(NodeConfig::test().with_eth_rpc_url(Some(origin_handle.http_endpoint()))).await; let fork_provider = fork_handle.http_provider(); let fork_tx_provider = http_provider(&fork_handle.http_endpoint()); assert_eq!(origin_nonce, fork_provider.get_transaction_count(account).await.unwrap()); let to = Address::random(); let to_balance = fork_provider.get_balance(to).await.unwrap(); let tx = TransactionRequest::default().from(account).to(to).value(U256::from(1337u64)); let tx = WithOtherFields::new(tx); let tx = fork_tx_provider.send_transaction(tx).await.unwrap().get_receipt().await.unwrap(); // nonce incremented by 1 assert_eq!(origin_nonce + 1, fork_provider.get_transaction_count(account).await.unwrap()); // resetting to origin state fork_api.anvil_reset(Some(Forking::default())).await.unwrap(); // nonce reset to origin assert_eq!(origin_nonce, fork_provider.get_transaction_count(account).await.unwrap()); // balance is reset assert_eq!(to_balance, fork_provider.get_balance(to).await.unwrap()); // tx does not exist anymore assert!(fork_tx_provider.get_transaction_by_hash(tx.transaction_hash).await.unwrap().is_none()) } // Ref: #[tokio::test(flavor = "multi_thread")] async fn can_reset_fork_to_new_fork() { let eth_rpc_url = next_rpc_endpoint(NamedChain::Mainnet); let (api, handle) = spawn(NodeConfig::test().with_eth_rpc_url(Some(eth_rpc_url))).await; let provider = handle.http_provider(); let op = address!("0xC0d3c0d3c0D3c0D3C0d3C0D3C0D3c0d3c0d30007"); // L2CrossDomainMessenger - Dead on mainnet. let tx = TransactionRequest::default().with_to(op).with_input("0x54fd4d50"); let tx = WithOtherFields::new(tx); let mainnet_call_output = provider.call(tx).await.unwrap(); assert_eq!(mainnet_call_output, Bytes::new()); // 0x let optimism = next_rpc_endpoint(NamedChain::Optimism); api.anvil_reset(Some(Forking { json_rpc_url: Some(optimism.to_string()), block_number: Some(124659890), })) .await .unwrap(); let code = provider.get_code_at(op).await.unwrap(); assert_ne!(code, Bytes::new()); } #[tokio::test(flavor = "multi_thread")] async fn test_fork_timestamp() { let start = std::time::Instant::now(); let (api, handle) = spawn(fork_config()).await; let provider = handle.http_provider(); let block = provider.get_block(BlockId::Number(BLOCK_NUMBER.into())).await.unwrap().unwrap(); assert_eq!(block.header.timestamp, BLOCK_TIMESTAMP); let accounts: Vec<_> = handle.dev_wallets().collect(); let from = accounts[0].address(); let tx = TransactionRequest::default().to(Address::random()).value(U256::from(1337u64)).from(from); let tx = WithOtherFields::new(tx); let tx = provider.send_transaction(tx).await.unwrap().get_receipt().await.unwrap(); let status = tx.inner.inner.inner.receipt.status.coerce_status(); assert!(status); let block = provider.get_block(BlockId::latest()).await.unwrap().unwrap(); let elapsed = start.elapsed().as_secs() + 1; // ensure the diff between the new mined block and the original block is within the elapsed time let diff = block.header.timestamp - BLOCK_TIMESTAMP; assert!(diff <= elapsed, "diff={diff}, elapsed={elapsed}"); let start = std::time::Instant::now(); // reset to check timestamp works after resetting api.anvil_reset(Some(Forking { json_rpc_url: None, block_number: Some(BLOCK_NUMBER) })) .await .unwrap(); let block = provider.get_block(BlockId::Number(BLOCK_NUMBER.into())).await.unwrap().unwrap(); assert_eq!(block.header.timestamp, BLOCK_TIMESTAMP); let tx = TransactionRequest::default().to(Address::random()).value(U256::from(1337u64)).from(from); let tx = WithOtherFields::new(tx); let _ = provider.send_transaction(tx).await.unwrap().get_receipt().await.unwrap(); // FIXME: Awaits endlessly here. let block = provider.get_block(BlockId::latest()).await.unwrap().unwrap(); let elapsed = start.elapsed().as_secs() + 1; let diff = block.header.timestamp - BLOCK_TIMESTAMP; assert!(diff <= elapsed); // ensure that after setting a timestamp manually, then next block time is correct let start = std::time::Instant::now(); api.anvil_reset(Some(Forking { json_rpc_url: None, block_number: Some(BLOCK_NUMBER) })) .await .unwrap(); api.evm_set_next_block_timestamp(BLOCK_TIMESTAMP + 1).unwrap(); let tx = TransactionRequest::default().to(Address::random()).value(U256::from(1337u64)).from(from); let tx = WithOtherFields::new(tx); let _tx = provider.send_transaction(tx).await.unwrap().get_receipt().await.unwrap(); let block = provider.get_block(BlockId::latest()).await.unwrap().unwrap(); assert_eq!(block.header.timestamp, BLOCK_TIMESTAMP + 1); let tx = TransactionRequest::default().to(Address::random()).value(U256::from(1337u64)).from(from); let tx = WithOtherFields::new(tx); let _ = provider.send_transaction(tx).await.unwrap().get_receipt().await.unwrap(); let block = provider.get_block(BlockId::latest()).await.unwrap().unwrap(); let elapsed = start.elapsed().as_secs() + 1; let diff = block.header.timestamp - (BLOCK_TIMESTAMP + 1); assert!(diff <= elapsed); } #[tokio::test(flavor = "multi_thread")] async fn test_fork_set_empty_code() { let (api, _handle) = spawn(fork_config()).await; let addr = "0x1f9840a85d5af5bf1d1762f925bdaddc4201f984".parse().unwrap(); let code = api.get_code(addr, None).await.unwrap(); assert!(!code.as_ref().is_empty()); api.anvil_set_code(addr, Vec::new().into()).await.unwrap(); let code = api.get_code(addr, None).await.unwrap(); assert!(code.as_ref().is_empty()); } #[tokio::test(flavor = "multi_thread")] async fn test_fork_can_send_tx() { let (api, handle) = spawn(fork_config().with_blocktime(Some(std::time::Duration::from_millis(800)))).await; let wallet = PrivateKeySigner::random(); let signer = wallet.address(); let provider = handle.http_provider(); // let provider = SignerMiddleware::new(provider, wallet); api.anvil_set_balance(signer, U256::MAX).await.unwrap(); api.anvil_impersonate_account(signer).await.unwrap(); // Added until WalletFiller for alloy-provider is fixed. let balance = provider.get_balance(signer).await.unwrap(); assert_eq!(balance, U256::MAX); let addr = Address::random(); let val = U256::from(1337u64); let tx = TransactionRequest::default().to(addr).value(val).from(signer); let tx = WithOtherFields::new(tx); // broadcast it via the eth_sendTransaction API let _ = provider.send_transaction(tx).await.unwrap().get_receipt().await.unwrap(); let balance = provider.get_balance(addr).await.unwrap(); assert_eq!(balance, val); } // #[tokio::test(flavor = "multi_thread")] async fn test_fork_nft_set_approve_all() { let (api, handle) = spawn( fork_config() .with_fork_block_number(Some(14812197u64)) .with_blocktime(Some(Duration::from_secs(5))) .with_chain_id(1u64.into()), ) .await; // create and fund a random wallet let wallet = PrivateKeySigner::random(); let signer = wallet.address(); api.anvil_set_balance(signer, U256::from(1000e18)).await.unwrap(); let provider = handle.http_provider(); // pick a random nft let nouns_addr: Address = "0x9c8ff314c9bc7f6e59a9d9225fb22946427edc03".parse().unwrap(); let owner: Address = "0x052564eb0fd8b340803df55def89c25c432f43f4".parse().unwrap(); let token_id: U256 = U256::from(154u64); let nouns = ERC721::new(nouns_addr, provider.clone()); let real_owner = nouns.ownerOf(token_id).call().await.unwrap(); assert_eq!(real_owner, owner); let approval = nouns.setApprovalForAll(nouns_addr, true); let tx = TransactionRequest::default() .from(owner) .to(nouns_addr) .with_input(approval.calldata().to_owned()); let tx = WithOtherFields::new(tx); api.anvil_impersonate_account(owner).await.unwrap(); let tx = provider.send_transaction(tx).await.unwrap().get_receipt().await.unwrap(); let status = tx.inner.inner.inner.receipt.status.coerce_status(); assert!(status); // transfer: impersonate real owner and transfer nft api.anvil_impersonate_account(real_owner).await.unwrap(); api.anvil_set_balance(real_owner, U256::from(10000e18 as u64)).await.unwrap(); let call = nouns.transferFrom(real_owner, signer, token_id); let tx = TransactionRequest::default() .from(real_owner) .to(nouns_addr) .with_input(call.calldata().to_owned()); let tx = WithOtherFields::new(tx); let tx = provider.send_transaction(tx).await.unwrap().get_receipt().await.unwrap(); let status = tx.inner.inner.inner.receipt.status.coerce_status(); assert!(status); let real_owner = nouns.ownerOf(token_id).call().await.unwrap(); assert_eq!(real_owner, wallet.address()); } // #[tokio::test(flavor = "multi_thread")] async fn test_fork_with_custom_chain_id() { // spawn a forked node with some random chainId let (api, handle) = spawn( fork_config() .with_fork_block_number(Some(14812197u64)) .with_blocktime(Some(Duration::from_secs(5))) .with_chain_id(3145u64.into()), ) .await; // get the eth chainId and the txn chainId let eth_chain_id = api.eth_chain_id(); let txn_chain_id = api.chain_id(); // get the chainId in the config let config_chain_id = handle.config().chain_id; // check that the chainIds are the same assert_eq!(eth_chain_id.unwrap().unwrap().to::(), 3145u64); assert_eq!(txn_chain_id, 3145u64); assert_eq!(config_chain_id, Some(3145u64)); } // #[tokio::test(flavor = "multi_thread")] async fn test_fork_can_send_opensea_tx() { let (api, handle) = spawn( fork_config() .with_fork_block_number(Some(14983338u64)) .with_blocktime(Some(Duration::from_millis(5000))), ) .await; let sender: Address = "0x8fdbae54b6d9f3fc2c649e3dd4602961967fd42f".parse().unwrap(); // transfer: impersonate real sender api.anvil_impersonate_account(sender).await.unwrap(); let provider = handle.http_provider(); let input: Bytes = "0xfb0f3ee1000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003ff2e795f5000000000000000000000000000023f28ae3e9756ba982a6290f9081b6a84900b758000000000000000000000000004c00500000ad104d7dbd00e3ae0a5c00560c0000000000000000000000000003235b597a78eabcb08ffcb4d97411073211dbcb0000000000000000000000000000000000000000000000000000000000000e72000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000062ad47c20000000000000000000000000000000000000000000000000000000062d43104000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000df44e65d2a2cf40000007b02230091a7ed01230072f7006a004d60a8d4e71d599b8104250f00000000007b02230091a7ed01230072f7006a004d60a8d4e71d599b8104250f00000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000024000000000000000000000000000000000000000000000000000000000000002e000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000001c6bf526340000000000000000000000000008de9c5a032463c561423387a9648c5c7bcc5bc900000000000000000000000000000000000000000000000000005543df729c0000000000000000000000000006eb234847a9e3a546539aac57a071c01dc3f398600000000000000000000000000000000000000000000000000000000000000416d39b5352353a22cf2d44faa696c2089b03137a13b5acfee0366306f2678fede043bc8c7e422f6f13a3453295a4a063dac7ee6216ab7bade299690afc77397a51c00000000000000000000000000000000000000000000000000000000000000".parse().unwrap(); let to: Address = "0x00000000006c3852cbef3e08e8df289169ede581".parse().unwrap(); let tx = TransactionRequest::default() .from(sender) .to(to) .value(U256::from(20000000000000000u64)) .with_input(input) .with_gas_price(22180711707u128) .with_gas_limit(150_000); let tx = WithOtherFields::new(tx); let tx = provider.send_transaction(tx).await.unwrap().get_receipt().await.unwrap(); let status = tx.inner.inner.inner.receipt.status.coerce_status(); assert!(status); } #[tokio::test(flavor = "multi_thread")] async fn test_fork_base_fee() { let (api, handle) = spawn(fork_config()).await; let accounts: Vec<_> = handle.dev_wallets().collect(); let from = accounts[0].address(); let provider = handle.http_provider(); api.anvil_set_next_block_base_fee_per_gas(U256::ZERO).await.unwrap(); let addr = Address::random(); let val = U256::from(1337u64); let tx = TransactionRequest::default().from(from).to(addr).value(val); let tx = WithOtherFields::new(tx); let _res = provider.send_transaction(tx).await.unwrap().get_receipt().await.unwrap(); } #[tokio::test(flavor = "multi_thread")] async fn test_fork_init_base_fee() { let (api, handle) = spawn(fork_config().with_fork_block_number(Some(13184859u64))).await; let provider = handle.http_provider(); let block = provider.get_block(BlockId::latest()).await.unwrap().unwrap(); // assert_eq!(block.header.number, 13184859u64); let init_base_fee = block.header.base_fee_per_gas.unwrap(); assert_eq!(init_base_fee, 63739886069); api.mine_one().await; let block = provider.get_block(BlockId::latest()).await.unwrap().unwrap(); let next_base_fee = block.header.base_fee_per_gas.unwrap(); assert!(next_base_fee < init_base_fee); } #[tokio::test(flavor = "multi_thread")] async fn test_fork_init_blob_base_fee_with_explicit_base_fee() { let fork_rpc_url = rpc::next_http_archive_rpc_url(); let fork_block_number = 24_127_158u64; let (default_api, _) = spawn( NodeConfig::test() .with_eth_rpc_url(Some(fork_rpc_url.clone())) .with_fork_block_number(Some(fork_block_number)), ) .await; let explicit_base_fee = default_api .block_by_number(BlockNumberOrTag::Latest) .await .unwrap() .unwrap() .header .base_fee_per_gas .unwrap(); let (explicit_api, _) = spawn( NodeConfig::test() .with_eth_rpc_url(Some(fork_rpc_url)) .with_fork_block_number(Some(fork_block_number)) .with_base_fee(Some(explicit_base_fee)), ) .await; let default_blob_base_fee = default_api.blob_base_fee().unwrap(); let explicit_blob_base_fee = explicit_api.blob_base_fee().unwrap(); assert!(default_blob_base_fee > U256::from(1)); assert_eq!(explicit_blob_base_fee, default_blob_base_fee); } #[tokio::test(flavor = "multi_thread")] async fn flaky_test_reset_fork_on_new_blocks() { let (api, handle) = spawn(NodeConfig::test().with_eth_rpc_url(Some(rpc::next_http_archive_rpc_url()))).await; let anvil_provider = handle.http_provider(); let endpoint = next_http_rpc_endpoint(); let provider = Arc::new(get_http_provider(&endpoint)); let current_block = anvil_provider.get_block_number().await.unwrap(); handle .task_manager() .spawn_reset_on_new_polled_blocks::(provider.clone(), api); let mut stream = provider .watch_blocks() .await .unwrap() .with_poll_interval(Duration::from_secs(2)) .into_stream() .flat_map(futures::stream::iter); // the http watcher may fetch multiple blocks at once, so we set a timeout here to offset edge // cases where the stream immediately returns a block tokio::time::sleep(Duration::from_secs(12)).await; stream.next().await.unwrap(); stream.next().await.unwrap(); let next_block = anvil_provider.get_block_number().await.unwrap(); assert!(next_block > current_block, "nextblock={next_block} currentblock={current_block}") } #[tokio::test(flavor = "multi_thread")] async fn test_fork_call() { let input: Bytes = "0x77c7b8fc".parse().unwrap(); let to: Address = "0x99d1Fa417f94dcD62BfE781a1213c092a47041Bc".parse().unwrap(); let block_number = 14746300u64; let provider = http_provider(rpc::next_http_archive_rpc_url().as_str()); let tx = TransactionRequest::default().to(to).with_input(input.clone()); let tx = WithOtherFields::new(tx); let res0 = provider.call(tx).block(BlockId::Number(block_number.into())).await.unwrap(); let (api, _) = spawn(fork_config().with_fork_block_number(Some(block_number))).await; let res1 = api .call( WithOtherFields::new(TransactionRequest { to: Some(TxKind::from(to)), input: input.into(), ..Default::default() }), None, EvmOverrides::default(), ) .await .unwrap(); assert_eq!(res0, res1); } #[tokio::test(flavor = "multi_thread")] async fn test_fork_block_timestamp() { let (api, _) = spawn(fork_config()).await; let initial_block = api.block_by_number(BlockNumberOrTag::Latest).await.unwrap().unwrap(); api.anvil_mine(Some(U256::from(1)), None).await.unwrap(); let latest_block = api.block_by_number(BlockNumberOrTag::Latest).await.unwrap().unwrap(); assert!(initial_block.header.timestamp <= latest_block.header.timestamp); } #[tokio::test(flavor = "multi_thread")] async fn test_fork_snapshot_block_timestamp() { let (api, _) = spawn(fork_config()).await; let snapshot_id = api.evm_snapshot().await.unwrap(); api.anvil_mine(Some(U256::from(1)), None).await.unwrap(); let initial_block = api.block_by_number(BlockNumberOrTag::Latest).await.unwrap().unwrap(); api.evm_revert(snapshot_id).await.unwrap(); api.evm_set_next_block_timestamp(initial_block.header.timestamp).unwrap(); api.anvil_mine(Some(U256::from(1)), None).await.unwrap(); let latest_block = api.block_by_number(BlockNumberOrTag::Latest).await.unwrap().unwrap(); assert_eq!(initial_block.header.timestamp, latest_block.header.timestamp); } #[tokio::test(flavor = "multi_thread")] async fn test_fork_uncles_fetch() { let (api, handle) = spawn(fork_config()).await; let provider = handle.http_provider(); // Block on ETH mainnet with 2 uncles let block_with_uncles = 190u64; let block = api.block_by_number(BlockNumberOrTag::Number(block_with_uncles)).await.unwrap().unwrap(); assert_eq!(block.uncles.len(), 2); let count = provider.get_uncle_count(block_with_uncles.into()).await.unwrap(); assert_eq!(count as usize, block.uncles.len()); let hash = BlockId::hash(block.header.hash); let count = provider.get_uncle_count(hash).await.unwrap(); assert_eq!(count as usize, block.uncles.len()); for (uncle_idx, uncle_hash) in block.uncles.iter().enumerate() { // Try with block number let uncle = provider .get_uncle(BlockId::number(block_with_uncles), uncle_idx as u64) .await .unwrap() .unwrap(); assert_eq!(*uncle_hash, uncle.header.hash); // Try with block hash let uncle = provider .get_uncle(BlockId::hash(block.header.hash), uncle_idx as u64) .await .unwrap() .unwrap(); assert_eq!(*uncle_hash, uncle.header.hash); } } #[tokio::test(flavor = "multi_thread")] async fn test_fork_block_transaction_count() { let (api, handle) = spawn(fork_config()).await; let provider = handle.http_provider(); let accounts: Vec<_> = handle.dev_wallets().collect(); let sender = accounts[0].address(); // disable automine (so there are pending transactions) api.anvil_set_auto_mine(false).await.unwrap(); // transfer: impersonate real sender api.anvil_impersonate_account(sender).await.unwrap(); let tx = TransactionRequest::default().from(sender).value(U256::from(42u64)).with_gas_limit(100_000); let tx = WithOtherFields::new(tx); let _ = provider.send_transaction(tx).await.unwrap(); let pending_txs = api.block_transaction_count_by_number(BlockNumberOrTag::Pending).await.unwrap().unwrap(); assert_eq!(pending_txs.to::(), 1); // mine a new block api.anvil_mine(None, None).await.unwrap(); let pending_txs = api.block_transaction_count_by_number(BlockNumberOrTag::Pending).await.unwrap().unwrap(); assert_eq!(pending_txs.to::(), 0); let latest_txs = api.block_transaction_count_by_number(BlockNumberOrTag::Latest).await.unwrap().unwrap(); assert_eq!(latest_txs.to::(), 1); let latest_block = api.block_by_number(BlockNumberOrTag::Latest).await.unwrap().unwrap(); let latest_txs = api.block_transaction_count_by_hash(latest_block.header.hash).await.unwrap().unwrap(); assert_eq!(latest_txs.to::(), 1); // check txs count on an older block: 420000 has 3 txs on mainnet let count_txs = api .block_transaction_count_by_number(BlockNumberOrTag::Number(420000)) .await .unwrap() .unwrap(); assert_eq!(count_txs.to::(), 3); let count_txs = api .block_transaction_count_by_hash( "0xb3b0e3e0c64e23fb7f1ccfd29245ae423d2f6f1b269b63b70ff882a983ce317c".parse().unwrap(), ) .await .unwrap() .unwrap(); assert_eq!(count_txs.to::(), 3); } // #[tokio::test(flavor = "multi_thread")] async fn can_impersonate_in_fork() { let (api, handle) = spawn(fork_config().with_fork_block_number(Some(15347924u64))).await; let provider = handle.http_provider(); let token_holder: Address = "0x2f0b23f53734252bda2277357e97e1517d6b042a".parse().unwrap(); let to = Address::random(); let val = U256::from(1337u64); // fund the impersonated account api.anvil_set_balance(token_holder, U256::from(1e18)).await.unwrap(); let tx = TransactionRequest::default().from(token_holder).to(to).value(val); let tx = WithOtherFields::new(tx); let res = provider.send_transaction(tx.clone()).await; res.unwrap_err(); api.anvil_impersonate_account(token_holder).await.unwrap(); let res = provider.send_transaction(tx.clone()).await.unwrap().get_receipt().await.unwrap(); assert_eq!(res.from, token_holder); let status = res.inner.inner.inner.receipt.status.coerce_status(); assert!(status); let balance = provider.get_balance(to).await.unwrap(); assert_eq!(balance, val); api.anvil_stop_impersonating_account(token_holder).await.unwrap(); let res = provider.send_transaction(tx).await; res.unwrap_err(); } // #[tokio::test(flavor = "multi_thread")] #[ignore] async fn test_total_difficulty_fork() { let (api, handle) = spawn(fork_config()).await; let total_difficulty = U256::from(46_673_965_560_973_856_260_636u128); let difficulty = U256::from(13_680_435_288_526_144u128); let provider = handle.http_provider(); let block = provider.get_block(BlockId::latest()).await.unwrap().unwrap(); assert_eq!(block.header.total_difficulty, Some(total_difficulty)); assert_eq!(block.header.difficulty, difficulty); api.mine_one().await; api.mine_one().await; let next_total_difficulty = total_difficulty + difficulty; let block = provider.get_block(BlockId::latest()).await.unwrap().unwrap(); assert_eq!(block.header.total_difficulty, Some(next_total_difficulty)); assert_eq!(block.header.difficulty, U256::ZERO); } // #[tokio::test(flavor = "multi_thread")] async fn test_transaction_receipt() { let (api, _) = spawn(fork_config()).await; // A transaction from the forked block (14608400) let receipt = api .transaction_receipt( "0xce495d665e9091613fd962351a5cbca27a992b919d6a87d542af97e2723ec1e4".parse().unwrap(), ) .await .unwrap(); assert!(receipt.is_some()); // A transaction from a block in the future (14608401) let receipt = api .transaction_receipt( "0x1a15472088a4a97f29f2f9159511dbf89954b58d9816e58a32b8dc17171dc0e8".parse().unwrap(), ) .await .unwrap(); assert!(receipt.is_none()); } // #[tokio::test(flavor = "multi_thread")] async fn test_block_receipts() { let (api, _) = spawn(fork_config()).await; // Receipts from the forked block (14608400) let receipts = api.block_receipts(BlockNumberOrTag::Number(BLOCK_NUMBER).into()).await.unwrap(); assert!(receipts.is_some()); // Receipts from a block in the future (14608401) let receipts = api.block_receipts(BlockNumberOrTag::Number(BLOCK_NUMBER + 1).into()).await.unwrap(); assert!(receipts.is_none()); // Receipts from a block hash (14608400) let hash = b256!("0x4c1c76f89cfe4eb503b09a0993346dd82865cac9d76034efc37d878c66453f0a"); let receipts = api.block_receipts(BlockId::Hash(hash.into())).await.unwrap(); assert!(receipts.is_some()); } #[tokio::test(flavor = "multi_thread")] async fn can_override_fork_chain_id() { let chain_id_override = 5u64; let (_api, handle) = spawn( fork_config() .with_fork_block_number(Some(16506610u64)) .with_chain_id(Some(chain_id_override)), ) .await; let wallet = handle.dev_wallets().next().unwrap(); let signer: EthereumWallet = wallet.into(); let provider = http_provider_with_signer(&handle.http_endpoint(), signer); 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); 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); let provider = handle.http_provider(); let chain_id = provider.get_chain_id().await.unwrap(); assert_eq!(chain_id, chain_id_override); } // #[tokio::test(flavor = "multi_thread")] async fn test_fork_reset_moonbeam() { crate::init_tracing(); let (api, handle) = spawn( fork_config() .with_eth_rpc_url(Some("https://rpc.api.moonbeam.network".to_string())) .with_fork_block_number(None::), ) .await; let provider = handle.http_provider(); let accounts: Vec<_> = handle.dev_wallets().collect(); let from = accounts[0].address(); let tx = TransactionRequest::default().to(Address::random()).value(U256::from(1337u64)).from(from); let tx = WithOtherFields::new(tx); api.anvil_impersonate_account(from).await.unwrap(); let tx = provider.send_transaction(tx).await.unwrap().get_receipt().await.unwrap(); let status = tx.inner.inner.inner.receipt.status.coerce_status(); assert!(status); // reset to check timestamp works after resetting api.anvil_reset(Some(Forking { json_rpc_url: Some("https://rpc.api.moonbeam.network".to_string()), block_number: None, })) .await .unwrap(); let tx = TransactionRequest::default().to(Address::random()).value(U256::from(1337u64)).from(from); let tx = WithOtherFields::new(tx); let tx = provider.send_transaction(tx).await.unwrap().get_receipt().await.unwrap(); let status = tx.inner.inner.inner.receipt.status.coerce_status(); assert!(status); } // let (api, _handle) = spawn(fork_config().with_fork_block_number(Some(18835000u64))).await; api.mine_one().await; let latest = api.block_by_number(BlockNumberOrTag::Latest).await.unwrap().unwrap(); // basefee of +1 block: assert_eq!(latest.header.base_fee_per_gas.unwrap(), 59455969592u64); // now reset to block 18835000 -1 api.anvil_reset(Some(Forking { json_rpc_url: None, block_number: Some(18835000u64 - 1) })) .await .unwrap(); api.mine_one().await; let latest = api.block_by_number(BlockNumberOrTag::Latest).await.unwrap().unwrap(); // basefee of the forked block: assert_eq!(latest.header.base_fee_per_gas.unwrap(), 59017001138); } // #[tokio::test(flavor = "multi_thread")] async fn flaky_test_arbitrum_fork_dev_balance() { let (api, handle) = spawn( fork_config() .with_fork_block_number(None::) .with_eth_rpc_url(Some(next_rpc_endpoint(NamedChain::Arbitrum))), ) .await; let accounts: Vec<_> = handle.dev_wallets().collect(); for acc in accounts { let balance = api.balance(acc.address(), Some(Default::default())).await.unwrap(); assert_eq!(balance, U256::from(100000000000000000000u128)); } } // #[tokio::test(flavor = "multi_thread")] async fn test_arb_fork_mining() { let fork_block_number = 394274860u64; let fork_rpc = next_rpc_endpoint(NamedChain::Arbitrum); let (api, _handle) = spawn( fork_config() .with_fork_block_number(Some(fork_block_number)) .with_eth_rpc_url(Some(fork_rpc)), ) .await; let init_blk_num = api.block_number().unwrap().to::(); // Mine one api.mine_one().await; let mined_blk_num = api.block_number().unwrap().to::(); assert_eq!(mined_blk_num, init_blk_num + 1); } // #[tokio::test(flavor = "multi_thread")] async fn flaky_test_arbitrum_fork_block_number() { // fork to get initial block for test let (_, handle) = spawn( fork_config() .with_fork_block_number(None::) .with_eth_rpc_url(Some(next_rpc_endpoint(NamedChain::Arbitrum))), ) .await; let provider = handle.http_provider(); let initial_block_number = provider.get_block_number().await.unwrap(); // fork again at block number returned by `eth_blockNumber` // if wrong block number returned (e.g. L1) then fork will fail with error code -32000: missing // trie node let (api, _) = spawn( fork_config() .with_fork_block_number(Some(initial_block_number)) .with_eth_rpc_url(Some(next_rpc_endpoint(NamedChain::Arbitrum))), ) .await; let block_number = api.block_number().unwrap().to::(); assert_eq!(block_number, initial_block_number); // take snapshot at initial block number let snapshot_state = api.evm_snapshot().await.unwrap(); // mine new block and check block number returned by `eth_blockNumber` api.mine_one().await; let block_number = api.block_number().unwrap().to::(); assert_eq!(block_number, initial_block_number + 1); // test block by number API call returns proper block number and `l1BlockNumber` is set let block_by_number = api.block_by_number(BlockNumberOrTag::Latest).await.unwrap().unwrap(); assert_eq!(block_by_number.header.number, initial_block_number + 1); assert!(block_by_number.other.get("l1BlockNumber").is_some()); // revert to recorded snapshot and check block number assert!(api.evm_revert(snapshot_state).await.unwrap()); let block_number = api.block_number().unwrap().to::(); assert_eq!(block_number, initial_block_number); // reset fork to different block number and compare with block returned by `eth_blockNumber` api.anvil_reset(Some(Forking { json_rpc_url: Some(next_rpc_endpoint(NamedChain::Arbitrum)), block_number: Some(initial_block_number - 2), })) .await .unwrap(); let block_number = api.block_number().unwrap().to::(); assert_eq!(block_number, initial_block_number - 2); } #[tokio::test(flavor = "multi_thread")] async fn test_base_fork_gas_limit() { // fork to get initial block for test let (api, handle) = spawn( fork_config() .with_fork_block_number(None::) .with_eth_rpc_url(Some(next_rpc_endpoint(NamedChain::Base))), ) .await; let provider = handle.http_provider(); let block = provider.get_block(BlockId::Number(BlockNumberOrTag::Latest)).await.unwrap().unwrap(); assert!(api.gas_limit() >= uint!(96_000_000_U256)); assert!(block.header.gas_limit >= 96_000_000_u64); } // #[tokio::test(flavor = "multi_thread")] async fn test_fork_execution_reverted() { let target = 16681681u64; let (api, _handle) = spawn(fork_config().with_fork_block_number(Some(target + 1))).await; let resp = api .call( WithOtherFields::new(TransactionRequest { to: Some(TxKind::from(address!("0xFd6CC4F251eaE6d02f9F7B41D1e80464D3d2F377"))), input: TransactionInput::new(bytes!("8f283b3c")), ..Default::default() }), Some(target.into()), EvmOverrides::default(), ) .await; assert!(resp.is_err()); let err = resp.unwrap_err(); assert!(err.to_string().contains("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(); let (api, _) = spawn( fork_config() .with_blocktime(Some(Duration::from_millis(500))) .with_fork_transaction_hash(Some(fork_tx_hash)) .with_eth_rpc_url(Some("https://immutable-zkevm.drpc.org".to_string())), ) .await; let fork_block_number = 21824325; // Make sure the fork starts from previous block let mut block_number = api.block_number().unwrap().to::(); assert_eq!(block_number, fork_block_number - 1); // Wait for fork to pass the target block while block_number < fork_block_number { sleep(Duration::from_millis(250)); block_number = api.block_number().unwrap().to::(); } let block = api .block_by_number(BlockNumberOrTag::Number(fork_block_number - 1)) .await .unwrap() .unwrap(); assert_eq!(block.transactions.len(), 6); let block = api .block_by_number_full(BlockNumberOrTag::Number(fork_block_number)) .await .unwrap() .unwrap(); assert!(!block.transactions.is_empty()); // Validate the transactions preceding the target transaction exist let expected_transactions = [ TxHash::from_str("c900784c993221ba192c53a3ff9996f6af83a951100ceb93e750f7ef86bd43d5") .unwrap(), TxHash::from_str("f86f001bbdf69f8f64ff8a4a5fc3e684cf3a7706f204eba8439752f6f67cd2c4") .unwrap(), fork_tx_hash, ]; for expected in [ (expected_transactions[0], address!("0x0a02a416f87a13626dda0ad386859497565222aa")), (expected_transactions[1], address!("0x0a02a416f87a13626dda0ad386859497565222aa")), (expected_transactions[2], address!("0x4f07d669d76ed9a17799fc4c04c4005196240940")), ] { let tx = api.backend.mined_transaction_by_hash(expected.0).unwrap(); assert_eq!(tx.inner.inner.signer(), expected.1); } // Validate the order of transactions in the new block for expected in [ (expected_transactions[0], 0), (expected_transactions[1], 1), (expected_transactions[2], 2), ] { let tx = api .backend .mined_block_by_number(BlockNumberOrTag::Number(fork_block_number)) .map(|b| b.header.hash) .and_then(|hash| { api.backend.mined_transaction_by_block_hash_and_index(hash, expected.1.into()) }) .unwrap(); assert_eq!(tx.tx_hash().to_string(), expected.0.to_string()); } } // #[tokio::test(flavor = "multi_thread")] async fn test_fork_query_at_fork_block() { let (api, handle) = spawn(fork_config()).await; let provider = handle.http_provider(); let info = api.anvil_node_info().await.unwrap(); let number = info.fork_config.fork_block_number.unwrap(); assert_eq!(number, BLOCK_NUMBER); let address = Address::random(); let balance = provider.get_balance(address).await.unwrap(); api.evm_mine(None).await.unwrap(); api.anvil_set_balance(address, balance + U256::from(1)).await.unwrap(); let balance_before = provider.get_balance(address).block_id(BlockId::number(number)).await.unwrap(); assert_eq!(balance_before, balance); } // #[tokio::test(flavor = "multi_thread")] async fn test_reset_dev_account_nonce() { let config: NodeConfig = fork_config(); let address = config.genesis_accounts[0].address(); let (api, handle) = spawn(config).await; let provider = handle.http_provider(); let info = api.anvil_node_info().await.unwrap(); let number = info.fork_config.fork_block_number.unwrap(); assert_eq!(number, BLOCK_NUMBER); let nonce_before = provider.get_transaction_count(address).await.unwrap(); // Reset to older block with other nonce api.anvil_reset(Some(Forking { json_rpc_url: None, block_number: Some(BLOCK_NUMBER - 1_000_000), })) .await .unwrap(); let nonce_after = provider.get_transaction_count(address).await.unwrap(); assert!(nonce_before > nonce_after); let receipt = provider .send_transaction(WithOtherFields::new( TransactionRequest::default() .from(address) .to(address) .nonce(nonce_after) .gas_limit(21000), )) .await .unwrap() .get_receipt() .await .unwrap(); 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(); let (mut api, _handle) = spawn(config).await; let info = api.anvil_node_info().await.unwrap(); let number = info.fork_config.fork_block_number.unwrap(); assert_eq!(number, BLOCK_NUMBER); async fn get_block_from_cache_path(api: &mut EthApi) -> u64 { let db = api.backend.get_db().read().await; let cache_path = db.maybe_inner().unwrap().cache().cache_path().unwrap(); cache_path .parent() .expect("must have filename") .file_name() .expect("must have block number as dir name") .to_str() .expect("must be valid string") .parse::() .expect("must be valid number") } assert_eq!(BLOCK_NUMBER, get_block_from_cache_path(&mut api).await); // Reset to older block without specifying a new rpc url api.anvil_reset(Some(Forking { json_rpc_url: None, block_number: Some(BLOCK_NUMBER - 1_000_000), })) .await .unwrap(); assert_eq!(BLOCK_NUMBER - 1_000_000, get_block_from_cache_path(&mut api).await); } #[tokio::test(flavor = "multi_thread")] async fn test_fork_get_account() { let (_api, handle) = spawn(fork_config()).await; let provider = handle.http_provider(); let accounts = handle.dev_accounts().collect::>(); let alice = accounts[0]; let bob = accounts[1]; let init_block = provider.get_block_number().await.unwrap(); let alice_bal = provider.get_balance(alice).await.unwrap(); let alice_nonce = provider.get_transaction_count(alice).await.unwrap(); let alice_acc_init = provider.get_account(alice).await.unwrap(); assert_eq!(alice_acc_init.balance, alice_bal); assert_eq!(alice_acc_init.nonce, alice_nonce); let tx = TransactionRequest::default().from(alice).to(bob).value(U256::from(142)); let tx = WithOtherFields::new(tx); let receipt = provider.send_transaction(tx).await.unwrap().get_receipt().await.unwrap(); assert!(receipt.status()); assert_eq!(init_block + 1, receipt.block_number.unwrap()); let alice_acc = provider.get_account(alice).await.unwrap(); assert_eq!( alice_acc.balance, alice_bal - (U256::from(142) + U256::from(receipt.gas_used as u128 * receipt.effective_gas_price)), ); assert_eq!(alice_acc.nonce, alice_nonce + 1); let alice_acc_prev_block = provider.get_account(alice).number(init_block).await.unwrap(); assert_eq!(alice_acc_init, alice_acc_prev_block); } #[tokio::test(flavor = "multi_thread")] async fn test_fork_get_account_info() { let (api, handle) = spawn(fork_config()).await; let provider = handle.http_provider(); let info = provider .get_account_info(address!("0x19e53a7397bE5AA7908fE9eA991B03710bdC74Fd")) // predates fork .number(BLOCK_NUMBER - 1) .await .unwrap(); assert_eq!( info, AccountInfo { balance: U256::from(14353753764795095694u64), nonce: 6689, code: Default::default(), } ); // Check account info at block number, see https://github.com/foundry-rs/foundry/issues/12072 let info = provider .get_account_info(address!("0x19e53a7397bE5AA7908fE9eA991B03710bdC74Fd")) // predates fork .number(BLOCK_NUMBER) .await .unwrap(); assert_eq!( info, AccountInfo { balance: U256::from(14352720829244098514u64), nonce: 6690, code: Default::default(), } ); // Mine and check account info at new block number, see https://github.com/foundry-rs/foundry/issues/12148 api.evm_mine(None).await.unwrap(); let info = provider .get_account_info(address!("0x19e53a7397bE5AA7908fE9eA991B03710bdC74Fd")) // predates fork .number(BLOCK_NUMBER + 1) .await .unwrap(); assert_eq!( info, AccountInfo { balance: U256::from(14352720829244098514u64), nonce: 6690, code: Default::default(), } ); } fn assert_hardfork_config( config: &EthConfig, expected_blob_params: &BlobParams, expected_precompiles: &[Address], expected_system_contracts: &BTreeMap, ) { assert!(config.next.is_none()); assert!(config.last.is_none()); let current = &config.current; assert_eq!(current.activation_time, 0); assert_eq!(current.chain_id, 31337); assert_eq!(current.fork_id, Bytes::from(vec![0, 0, 0, 0])); assert_eq!(¤t.blob_schedule, expected_blob_params); assert_eq!( current.precompiles.values().copied().collect::>(), expected_precompiles.iter().copied().collect::>(), ); assert_eq!(current.system_contracts, *expected_system_contracts); } #[tokio::test(flavor = "multi_thread")] async fn test_config_with_cancun_hardfork() { let (api, _handle) = spawn(NodeConfig::test().with_hardfork(Some(EthereumHardfork::Cancun.into()))).await; let config = api.config().unwrap(); let expected_blob_params = BlobParams { target_blob_count: 3, max_blob_count: 6, update_fraction: 3338477, min_blob_fee: 1, max_blobs_per_tx: 6, blob_base_cost: 0, }; // <= Cancun precompiles let expected_precompiles = [ address!("0000000000000000000000000000000000000001"), address!("0000000000000000000000000000000000000002"), address!("0000000000000000000000000000000000000003"), address!("0000000000000000000000000000000000000004"), address!("0000000000000000000000000000000000000005"), address!("0000000000000000000000000000000000000006"), address!("0000000000000000000000000000000000000007"), address!("0000000000000000000000000000000000000008"), address!("0000000000000000000000000000000000000009"), address!("000000000000000000000000000000000000000a"), ]; let expected_system_contracts = BTreeMap::from([( SystemContract::BeaconRoots, address!("000f3df6d732807ef1319fb7b8bb8522d0beac02"), )]); assert_hardfork_config( &config, &expected_blob_params, &expected_precompiles, &expected_system_contracts, ); } #[tokio::test(flavor = "multi_thread")] async fn test_config_with_prague_hardfork_with_celo() { let (api, _handle) = spawn( NodeConfig::test() .with_hardfork(Some(EthereumHardfork::Prague.into())) .with_networks(NetworkConfigs::with_celo()), ) .await; let config = api.config().unwrap(); let expected_blob_params = BlobParams { target_blob_count: 6, max_blob_count: 9, update_fraction: 5007716, min_blob_fee: 1, max_blobs_per_tx: 9, blob_base_cost: 0, }; // <= Prague + Celo precompiles let expected_precompiles = [ address!("0000000000000000000000000000000000000001"), address!("0000000000000000000000000000000000000002"), address!("0000000000000000000000000000000000000003"), address!("0000000000000000000000000000000000000004"), address!("0000000000000000000000000000000000000005"), address!("0000000000000000000000000000000000000006"), address!("0000000000000000000000000000000000000007"), address!("0000000000000000000000000000000000000008"), address!("0000000000000000000000000000000000000009"), address!("000000000000000000000000000000000000000a"), address!("000000000000000000000000000000000000000b"), address!("000000000000000000000000000000000000000c"), address!("000000000000000000000000000000000000000d"), address!("000000000000000000000000000000000000000e"), address!("000000000000000000000000000000000000000f"), address!("0000000000000000000000000000000000000010"), address!("0000000000000000000000000000000000000011"), address!("00000000000000000000000000000000000000fd"), // `celo transfer` ]; let expected_system_contracts = BTreeMap::from([ (SystemContract::BeaconRoots, address!("000f3df6d732807ef1319fb7b8bb8522d0beac02")), ( SystemContract::ConsolidationRequestPredeploy, address!("0000bbddc7ce488642fb579f8b00f3a590007251"), ), (SystemContract::DepositContract, address!("00000000219ab540356cbb839cbe05303d7705fa")), (SystemContract::HistoryStorage, address!("0000f90827f1c53a10cb7a02335b175320002935")), ( SystemContract::WithdrawalRequestPredeploy, address!("00000961ef480eb55e80d19ad83579a64c007002"), ), ]); assert_hardfork_config( &config, &expected_blob_params, &expected_precompiles, &expected_system_contracts, ); } #[tokio::test(flavor = "multi_thread")] async fn test_config_with_osaka_hardfork() { let (api, _handle) = spawn(NodeConfig::test().with_hardfork(Some(EthereumHardfork::Osaka.into()))).await; let config = api.config().unwrap(); let expected_blob_params = BlobParams { target_blob_count: 6, max_blob_count: 9, update_fraction: 5007716, min_blob_fee: 1, max_blobs_per_tx: 6, blob_base_cost: 8192, }; // <= Osaka precompiles let expected_precompiles = [ address!("0000000000000000000000000000000000000001"), address!("0000000000000000000000000000000000000002"), address!("0000000000000000000000000000000000000003"), address!("0000000000000000000000000000000000000004"), address!("0000000000000000000000000000000000000005"), address!("0000000000000000000000000000000000000006"), address!("0000000000000000000000000000000000000007"), address!("0000000000000000000000000000000000000008"), address!("0000000000000000000000000000000000000009"), address!("000000000000000000000000000000000000000a"), address!("000000000000000000000000000000000000000b"), address!("000000000000000000000000000000000000000c"), address!("000000000000000000000000000000000000000d"), address!("000000000000000000000000000000000000000e"), address!("000000000000000000000000000000000000000f"), address!("0000000000000000000000000000000000000010"), address!("0000000000000000000000000000000000000011"), address!("0000000000000000000000000000000000000100"), ]; let expected_system_contracts = BTreeMap::from([ (SystemContract::BeaconRoots, address!("000f3df6d732807ef1319fb7b8bb8522d0beac02")), ( SystemContract::ConsolidationRequestPredeploy, address!("0000bbddc7ce488642fb579f8b00f3a590007251"), ), (SystemContract::DepositContract, address!("00000000219ab540356cbb839cbe05303d7705fa")), (SystemContract::HistoryStorage, address!("0000f90827f1c53a10cb7a02335b175320002935")), ( SystemContract::WithdrawalRequestPredeploy, address!("00000961ef480eb55e80d19ad83579a64c007002"), ), ]); assert_hardfork_config( &config, &expected_blob_params, &expected_precompiles, &expected_system_contracts, ); } #[tokio::test(flavor = "multi_thread")] async fn test_config_with_osaka_hardfork_with_precompile_factory() { #[derive(Debug)] struct CustomPrecompileFactory; impl PrecompileFactory for CustomPrecompileFactory { fn precompiles(&self) -> Vec<(Address, alloy_evm::precompiles::DynPrecompile)> { vec![( address!("0x0000000000000000000000000000000000000071"), alloy_evm::precompiles::DynPrecompile::from( |input: alloy_evm::precompiles::PrecompileInput<'_>| { Ok(revm::precompile::PrecompileOutput { bytes: Bytes::copy_from_slice(input.data), gas_used: 0, gas_refunded: 0, reverted: false, }) }, ), )] } } let (api, _handle) = spawn( NodeConfig::test() .with_hardfork(Some(EthereumHardfork::Osaka.into())) .with_precompile_factory(CustomPrecompileFactory), ) .await; let config = api.config().unwrap(); let expected_blob_params = BlobParams { target_blob_count: 6, max_blob_count: 9, update_fraction: 5007716, min_blob_fee: 1, max_blobs_per_tx: 6, blob_base_cost: 8192, }; // <= Osaka precompiles + custom precompile let expected_precompiles = [ address!("0000000000000000000000000000000000000001"), address!("0000000000000000000000000000000000000002"), address!("0000000000000000000000000000000000000003"), address!("0000000000000000000000000000000000000004"), address!("0000000000000000000000000000000000000005"), address!("0000000000000000000000000000000000000006"), address!("0000000000000000000000000000000000000007"), address!("0000000000000000000000000000000000000008"), address!("0000000000000000000000000000000000000009"), address!("000000000000000000000000000000000000000a"), address!("000000000000000000000000000000000000000b"), address!("000000000000000000000000000000000000000c"), address!("000000000000000000000000000000000000000d"), address!("000000000000000000000000000000000000000e"), address!("000000000000000000000000000000000000000f"), address!("0000000000000000000000000000000000000010"), address!("0000000000000000000000000000000000000011"), address!("0000000000000000000000000000000000000071"), // `custom_echo` address!("0000000000000000000000000000000000000100"), ]; let expected_system_contracts = BTreeMap::from([ (SystemContract::BeaconRoots, address!("000f3df6d732807ef1319fb7b8bb8522d0beac02")), ( SystemContract::ConsolidationRequestPredeploy, address!("0000bbddc7ce488642fb579f8b00f3a590007251"), ), (SystemContract::DepositContract, address!("00000000219ab540356cbb839cbe05303d7705fa")), (SystemContract::HistoryStorage, address!("0000f90827f1c53a10cb7a02335b175320002935")), ( SystemContract::WithdrawalRequestPredeploy, address!("00000961ef480eb55e80d19ad83579a64c007002"), ), ]); assert_hardfork_config( &config, &expected_blob_params, &expected_precompiles, &expected_system_contracts, ); } ================================================ FILE: crates/anvil/tests/it/gas.rs ================================================ //! Gas related tests use crate::utils::http_provider_with_signer; use alloy_network::{EthereumWallet, TransactionBuilder}; use alloy_primitives::{Address, U64, U256, uint}; use alloy_provider::Provider; use alloy_rpc_types::{BlockId, TransactionRequest}; use alloy_serde::WithOtherFields; use anvil::{NodeConfig, eth::fees::INITIAL_BASE_FEE, spawn}; const GAS_TRANSFER: u64 = 21_000; #[tokio::test(flavor = "multi_thread")] async fn test_gas_limit_applied_from_config() { let (api, _handle) = spawn(NodeConfig::test().with_gas_limit(Some(10_000_000))).await; assert_eq!(api.gas_limit(), uint!(10_000_000_U256)); } #[tokio::test(flavor = "multi_thread")] async fn test_gas_limit_disabled_from_config() { let (api, _handle) = spawn(NodeConfig::test().disable_block_gas_limit(true)).await; // see https://github.com/foundry-rs/foundry/pull/8933 assert_eq!(api.gas_limit(), U256::from(U64::MAX)); } #[tokio::test(flavor = "multi_thread")] async fn test_basefee_full_block() { let (_api, handle) = spawn( NodeConfig::test().with_base_fee(Some(INITIAL_BASE_FEE)).with_gas_limit(Some(GAS_TRANSFER)), ) .await; let wallet = handle.dev_wallets().next().unwrap(); let signer: EthereumWallet = wallet.clone().into(); let provider = http_provider_with_signer(&handle.http_endpoint(), signer); let tx = TransactionRequest::default().to(Address::random()).with_value(U256::from(1337)); let tx = WithOtherFields::new(tx); provider.send_transaction(tx.clone()).await.unwrap().get_receipt().await.unwrap(); let base_fee = provider .get_block(BlockId::latest()) .await .unwrap() .unwrap() .header .base_fee_per_gas .unwrap(); provider.send_transaction(tx.clone()).await.unwrap().get_receipt().await.unwrap(); let next_base_fee = provider .get_block(BlockId::latest()) .await .unwrap() .unwrap() .header .base_fee_per_gas .unwrap(); assert!(next_base_fee > base_fee); // max increase, full block assert_eq!(next_base_fee, INITIAL_BASE_FEE + 125_000_000); } #[tokio::test(flavor = "multi_thread")] async fn test_basefee_half_block() { let (_api, handle) = spawn( NodeConfig::test() .with_base_fee(Some(INITIAL_BASE_FEE)) .with_gas_limit(Some(GAS_TRANSFER * 2)), ) .await; let wallet = handle.dev_wallets().next().unwrap(); let signer: EthereumWallet = wallet.clone().into(); let provider = http_provider_with_signer(&handle.http_endpoint(), signer); let tx = TransactionRequest::default().to(Address::random()).with_value(U256::from(1337)); let tx = WithOtherFields::new(tx); provider.send_transaction(tx.clone()).await.unwrap().get_receipt().await.unwrap(); let tx = TransactionRequest::default().to(Address::random()).with_value(U256::from(1337)); let tx = WithOtherFields::new(tx); provider.send_transaction(tx.clone()).await.unwrap().get_receipt().await.unwrap(); let next_base_fee = provider .get_block(BlockId::latest()) .await .unwrap() .unwrap() .header .base_fee_per_gas .unwrap(); // unchanged, half block assert_eq!(next_base_fee, { INITIAL_BASE_FEE }); } #[tokio::test(flavor = "multi_thread")] async fn test_basefee_empty_block() { let (api, handle) = spawn(NodeConfig::test().with_base_fee(Some(INITIAL_BASE_FEE))).await; let wallet = handle.dev_wallets().next().unwrap(); let signer: EthereumWallet = wallet.clone().into(); let provider = http_provider_with_signer(&handle.http_endpoint(), signer); let tx = TransactionRequest::default().with_to(Address::random()).with_value(U256::from(1337)); let tx = WithOtherFields::new(tx); provider.send_transaction(tx.clone()).await.unwrap().get_receipt().await.unwrap(); let base_fee = provider .get_block(BlockId::latest()) .await .unwrap() .unwrap() .header .base_fee_per_gas .unwrap(); // mine empty block api.mine_one().await; let next_base_fee = provider .get_block(BlockId::latest()) .await .unwrap() .unwrap() .header .base_fee_per_gas .unwrap(); // empty block, decreased base fee assert!(next_base_fee < base_fee); } #[tokio::test(flavor = "multi_thread")] async fn test_respect_base_fee() { let base_fee = 50u128; let (_api, handle) = spawn(NodeConfig::test().with_base_fee(Some(base_fee as u64))).await; let provider = handle.http_provider(); let tx = TransactionRequest::default().with_to(Address::random()).with_value(U256::from(100)); let mut tx = WithOtherFields::new(tx); let mut underpriced = tx.clone(); underpriced.set_gas_price(base_fee - 1); let res = provider.send_transaction(underpriced).await; assert!(res.is_err()); assert!(res.unwrap_err().to_string().contains("max fee per gas less than block base fee")); tx.set_gas_price(base_fee); provider.send_transaction(tx.clone()).await.unwrap().get_receipt().await.unwrap(); } #[tokio::test(flavor = "multi_thread")] async fn test_tip_above_fee_cap() { let base_fee = 50u128; let (_api, handle) = spawn(NodeConfig::test().with_base_fee(Some(base_fee as u64))).await; let provider = handle.http_provider(); let tx = TransactionRequest::default() .max_fee_per_gas(base_fee) .max_priority_fee_per_gas(base_fee + 1) .with_to(Address::random()) .with_value(U256::from(100)); let tx = WithOtherFields::new(tx); let res = provider.send_transaction(tx.clone()).await; assert!(res.is_err()); assert!( res.unwrap_err() .to_string() .contains("max priority fee per gas higher than max fee per gas") ); } #[tokio::test(flavor = "multi_thread")] async fn test_can_use_fee_history() { let base_fee = 50u128; let (_api, handle) = spawn(NodeConfig::test().with_base_fee(Some(base_fee as u64))).await; let provider = handle.http_provider(); for _ in 0..10 { let fee_history = provider.get_fee_history(1, Default::default(), &[]).await.unwrap(); let next_base_fee = *fee_history.base_fee_per_gas.last().unwrap(); let tx = TransactionRequest::default() .with_to(Address::random()) .with_value(U256::from(100)) .with_gas_price(next_base_fee); let tx = WithOtherFields::new(tx); let receipt = provider.send_transaction(tx.clone()).await.unwrap().get_receipt().await.unwrap(); assert!(receipt.inner.inner.is_success()); let fee_history_after = provider.get_fee_history(1, Default::default(), &[]).await.unwrap(); let latest_fee_history_fee = *fee_history_after.base_fee_per_gas.first().unwrap() as u64; let latest_block = provider.get_block(BlockId::latest()).await.unwrap().unwrap(); assert_eq!(latest_block.header.base_fee_per_gas.unwrap(), latest_fee_history_fee); 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); } ================================================ FILE: crates/anvil/tests/it/genesis.rs ================================================ //! genesis.json tests use crate::fork::fork_config; use alloy_genesis::Genesis; use alloy_primitives::{Address, U256}; use alloy_provider::Provider; use anvil::{NodeConfig, spawn}; use std::str::FromStr; const GENESIS: &str = r#"{ "config": { "chainId": 19763, "homesteadBlock": 0, "eip150Block": 0, "eip155Block": 0, "eip158Block": 0, "byzantiumBlock": 0, "ethash": {} }, "nonce": "0xdeadbeefdeadbeef", "timestamp": "0x0", "extraData": "0x0000000000000000000000000000000000000000000000000000000000000000", "gasLimit": "0x80000000", "difficulty": "0x20000", "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000", "coinbase": "0x0000000000000000000000000000000000000000", "alloc": { "71562b71999873db5b286df957af199ec94617f7": { "balance": "0xffffffffffffffffffffffffff" } }, "number": 73, "gasUsed": "0x0", "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000" } "#; #[tokio::test(flavor = "multi_thread")] async fn can_apply_genesis() { let genesis: Genesis = serde_json::from_str(GENESIS).unwrap(); let (_api, handle) = spawn(NodeConfig::test().with_genesis(Some(genesis))).await; let provider = handle.http_provider(); assert_eq!(provider.get_chain_id().await.unwrap(), 19763u64); let addr: Address = Address::from_str("71562b71999873db5b286df957af199ec94617f7").unwrap(); let balance = provider.get_balance(addr).await.unwrap(); let expected: U256 = U256::from_str_radix("ffffffffffffffffffffffffff", 16).unwrap(); assert_eq!(balance, expected); let block_number = provider.get_block_number().await.unwrap(); assert_eq!(block_number, 73u64); } // // #[tokio::test(flavor = "multi_thread")] async fn chain_id_precedence() { // Order: --chain-id > fork-chain-id > Genesis > default. // --chain-id > Genesis. let genesis: Genesis = serde_json::from_str(GENESIS).unwrap(); let (_api, handle) = spawn(NodeConfig::test().with_genesis(Some(genesis.clone())).with_chain_id(Some(300u64))) .await; let provider = handle.http_provider(); let chain_id = provider.get_chain_id().await.unwrap(); assert_eq!(chain_id, 300u64); // fork > Genesis. let (_api, handle) = spawn(fork_config().with_genesis(Some(genesis.clone()))).await; let provider = handle.http_provider(); let chain_id = provider.get_chain_id().await.unwrap(); assert_eq!(chain_id, 1); // --chain-id > fork. let (_api, handle) = spawn(fork_config().with_chain_id(Some(300u64))).await; let provider = handle.http_provider(); let chain_id = provider.get_chain_id().await.unwrap(); assert_eq!(chain_id, 300u64); // fork let (_api, handle) = spawn(fork_config()).await; let provider = handle.http_provider(); let chain_id = provider.get_chain_id().await.unwrap(); assert_eq!(chain_id, 1); // Genesis let (_api, handle) = spawn(NodeConfig::test().with_genesis(Some(genesis))).await; let provider = handle.http_provider(); let chain_id = provider.get_chain_id().await.unwrap(); assert_eq!(chain_id, 19763u64); // default let (_api, handle) = spawn(NodeConfig::test()).await; let provider = handle.http_provider(); let chain_id = provider.get_chain_id().await.unwrap(); assert_eq!(chain_id, 31337); } ================================================ FILE: crates/anvil/tests/it/ipc.rs ================================================ //! IPC tests use crate::{init_tracing, utils::connect_pubsub}; use alloy_primitives::U256; use alloy_provider::Provider; use anvil::{NodeConfig, spawn}; use futures::StreamExt; use tempfile::TempDir; fn ipc_config() -> (Option, NodeConfig) { let path; let dir; if cfg!(unix) { let tmp = tempfile::tempdir().unwrap(); path = tmp.path().join("anvil.ipc").to_string_lossy().into_owned(); dir = Some(tmp); } else { dir = None; path = format!(r"\\.\pipe\anvil_test_{}.ipc", rand::random::()); } let config = NodeConfig::test().with_ipc(Some(Some(path))); (dir, config) } #[tokio::test(flavor = "multi_thread")] async fn can_get_block_number_ipc() { init_tracing(); let (_dir, config) = ipc_config(); let (api, handle) = spawn(config).await; let block_num = api.block_number().unwrap(); assert_eq!(block_num, U256::ZERO); let provider = handle.ipc_provider().unwrap(); let num = provider.get_block_number().await.unwrap(); assert_eq!(num, block_num.to::()); } #[tokio::test(flavor = "multi_thread")] async fn test_sub_new_heads_ipc() { init_tracing(); let (_dir, config) = ipc_config(); let (api, handle) = spawn(config).await; let provider = connect_pubsub(handle.ipc_path().unwrap().as_str()).await; // mine a block every 1 seconds api.anvil_set_interval_mining(1).unwrap(); let blocks = provider.subscribe_blocks().await.unwrap().into_stream(); let blocks = blocks.take(3).collect::>().await; let block_numbers = blocks.into_iter().map(|b| b.number).collect::>(); assert_eq!(block_numbers, vec![1, 2, 3]); } ================================================ FILE: crates/anvil/tests/it/logs.rs ================================================ //! log/event related tests use crate::{ abi::SimpleStorage::{self}, utils::{http_provider_with_signer, ws_provider_with_signer}, }; use alloy_network::EthereumWallet; use alloy_primitives::{B256, map::B256HashSet}; use alloy_provider::Provider; use alloy_rpc_types::{BlockNumberOrTag, Filter}; use anvil::{NodeConfig, spawn}; use futures::StreamExt; #[tokio::test(flavor = "multi_thread")] async fn get_past_events() { let (_api, handle) = spawn(NodeConfig::test()).await; let wallet = handle.dev_wallets().next().unwrap(); let account = wallet.address(); let signer: EthereumWallet = wallet.into(); let provider = http_provider_with_signer(&handle.http_endpoint(), signer); let contract = SimpleStorage::deploy(provider.clone(), "initial value".to_string()).await.unwrap(); let _ = contract .setValue("hi".to_string()) .from(account) .send() .await .unwrap() .get_receipt() .await .unwrap(); let simple_storage_address = *contract.address(); let filter = Filter::new() .address(simple_storage_address) .topic1(B256::from(account.into_word())) .from_block(BlockNumberOrTag::from(0)); let logs = provider .get_logs(&filter) .await .unwrap() .into_iter() .map(|log| log.log_decode::().unwrap()) .collect::>(); // 2 events, 1 in constructor, 1 in call assert_eq!(logs[0].inner.newValue, "initial value"); assert_eq!(logs[1].inner.newValue, "hi"); assert_eq!(logs.len(), 2); // and we can fetch the events at a block hash // let hash = provider.get_block(1).await.unwrap().unwrap().hash.unwrap(); let hash = provider.get_block_by_number(BlockNumberOrTag::from(1)).await.unwrap().unwrap().header.hash; let filter = Filter::new() .address(simple_storage_address) .topic1(B256::from(account.into_word())) .at_block_hash(hash); let logs = provider .get_logs(&filter) .await .unwrap() .into_iter() .map(|log| log.log_decode::().unwrap()) .collect::>(); assert_eq!(logs[0].inner.newValue, "initial value"); assert_eq!(logs.len(), 1); } #[tokio::test(flavor = "multi_thread")] async fn get_all_events() { let (api, handle) = spawn(NodeConfig::test()).await; let wallet = handle.dev_wallets().next().unwrap(); let account = wallet.address(); let signer: EthereumWallet = wallet.into(); let provider = http_provider_with_signer(&handle.http_endpoint(), signer); let contract = SimpleStorage::deploy(provider.clone(), "initial value".to_string()).await.unwrap(); api.anvil_set_auto_mine(false).await.unwrap(); let pre_logs = provider.get_logs(&Filter::new().from_block(BlockNumberOrTag::Earliest)).await.unwrap(); assert_eq!(pre_logs.len(), 1); let pre_logs = provider.get_logs(&Filter::new().from_block(BlockNumberOrTag::Number(0))).await.unwrap(); assert_eq!(pre_logs.len(), 1); // spread logs across several blocks let num_tx = 10; let tx = contract.setValue("hi".to_string()).from(account); for _ in 0..num_tx { let tx = tx.send().await.unwrap(); api.mine_one().await; tx.get_receipt().await.unwrap(); } let logs = provider.get_logs(&Filter::new().from_block(BlockNumberOrTag::Earliest)).await.unwrap(); let num_logs = num_tx + pre_logs.len(); assert_eq!(logs.len(), num_logs); // test that logs returned from get_logs and get_transaction_receipt have // the same log_index, block_number, and transaction_hash let mut tasks = vec![]; let mut seen_tx_hashes = B256HashSet::default(); for log in &logs { if seen_tx_hashes.contains(&log.transaction_hash.unwrap()) { continue; } tasks.push(provider.get_transaction_receipt(log.transaction_hash.unwrap())); seen_tx_hashes.insert(log.transaction_hash.unwrap()); } let receipt_logs = futures::future::join_all(tasks) .await .into_iter() .collect::, _>>() .unwrap() .into_iter() .flat_map(|receipt| receipt.unwrap().inner.inner.inner.receipt.logs) .collect::>(); assert_eq!(receipt_logs.len(), logs.len()); for (receipt_log, log) in receipt_logs.iter().zip(logs.iter()) { assert_eq!(receipt_log.transaction_hash, log.transaction_hash); assert_eq!(receipt_log.block_number, log.block_number); assert_eq!(receipt_log.log_index, log.log_index); } } #[tokio::test(flavor = "multi_thread")] async fn watch_events() { let (_api, handle) = spawn(NodeConfig::test()).await; let wallet = handle.dev_wallets().next().unwrap(); let account = wallet.address(); let signer: EthereumWallet = wallet.into(); let provider = http_provider_with_signer(&handle.http_endpoint(), signer.clone()); let contract1 = SimpleStorage::deploy(provider.clone(), "initial value".to_string()).await.unwrap(); // Spawn the event listener. let event1 = contract1.event_filter::(); let mut stream1 = event1.watch().await.unwrap().into_stream(); // Also set up a subscription for the same thing. let ws = ws_provider_with_signer(&handle.ws_endpoint(), signer.clone()); let contract2 = SimpleStorage::new(*contract1.address(), ws); let event2 = contract2.event_filter::(); let mut stream2 = event2.watch().await.unwrap().into_stream(); let num_tx = 3; let starting_block_number = provider.get_block_number().await.unwrap(); for i in 0..num_tx { contract1 .setValue(i.to_string()) .from(account) .send() .await .unwrap() .get_receipt() .await .unwrap(); let log = stream1.next().await.unwrap().unwrap(); let log2 = stream2.next().await.unwrap().unwrap(); assert_eq!(log.0.newValue, log2.0.newValue); assert_eq!(log.0.newValue, i.to_string()); assert_eq!(log.1.block_number.unwrap(), starting_block_number + i + 1); let hash = provider .get_block_by_number(BlockNumberOrTag::from(starting_block_number + i + 1)) .await .unwrap() .unwrap() .header .hash; assert_eq!(log.1.block_hash.unwrap(), hash); } } ================================================ FILE: crates/anvil/tests/it/main.rs ================================================ mod abi; mod anvil; mod anvil_api; mod api; mod beacon_api; mod eip2935; mod eip4844; mod eip7702; mod fork; mod gas; mod genesis; mod ipc; mod logs; mod optimism; mod otterscan; mod proof; mod pubsub; mod revert; mod sign; mod simulate; mod state; mod traces; mod transaction; mod txpool; pub mod utils; mod wsapi; pub use foundry_test_utils::init_tracing; ================================================ FILE: crates/anvil/tests/it/optimism.rs ================================================ //! Tests for OP chain support. use crate::utils::{http_provider, http_provider_with_signer}; use alloy_eips::eip2718::Encodable2718; use alloy_network::{EthereumWallet, TransactionBuilder}; use alloy_primitives::{Address, Bloom, TxHash, TxKind, U256, b256}; use alloy_provider::Provider; use alloy_rpc_types::{BlockId, TransactionRequest}; use alloy_serde::WithOtherFields; use anvil::{NodeConfig, eth::fees::INITIAL_BASE_FEE, spawn}; use foundry_evm_networks::NetworkConfigs; use op_alloy_consensus::TxDeposit; use op_alloy_rpc_types::OpTransactionFields; use serde_json::{Value, json}; #[tokio::test(flavor = "multi_thread")] async fn test_deposits_not_supported_if_optimism_disabled() { let (_api, handle) = spawn(NodeConfig::test()).await; let provider = handle.http_provider(); let accounts: Vec<_> = handle.dev_wallets().collect(); let from = accounts[0].address(); let to = accounts[1].address(); let tx = TransactionRequest::default() .with_from(from) .with_to(to) .with_value(U256::from(1234)) .with_gas_limit(21000); let op_fields = OpTransactionFields { source_hash: Some(b256!( "0x0000000000000000000000000000000000000000000000000000000000000000" )), mint: Some(0), is_system_tx: Some(true), deposit_receipt_version: None, }; let other = serde_json::to_value(op_fields).unwrap().try_into().unwrap(); let tx = WithOtherFields { inner: tx, other }; let err = provider.send_transaction(tx).await.unwrap_err(); let s = err.to_string(); assert!(s.contains("op-stack deposit tx received but is not supported"), "{s:?}"); } #[tokio::test(flavor = "multi_thread")] async fn test_send_value_deposit_transaction() { // enable the Optimism flag let (api, handle) = spawn(NodeConfig::test().with_networks(NetworkConfigs::with_optimism())).await; let accounts: Vec<_> = handle.dev_wallets().collect(); let signer: EthereumWallet = accounts[0].clone().into(); let from = accounts[0].address(); let to = accounts[1].address(); let provider = http_provider_with_signer(&handle.http_endpoint(), signer); let send_value = U256::from(1234); let before_balance_to = provider.get_balance(to).await.unwrap(); let op_fields = OpTransactionFields { source_hash: Some(b256!( "0x0000000000000000000000000000000000000000000000000000000000000000" )), mint: Some(0), is_system_tx: Some(true), deposit_receipt_version: None, }; let other = serde_json::to_value(op_fields).unwrap().try_into().unwrap(); let tx = TransactionRequest::default() .with_from(from) .with_to(to) .with_value(send_value) .with_gas_limit(21000); let tx: WithOtherFields = WithOtherFields { inner: tx, other }; let pending = provider.send_transaction(tx).await.unwrap().register().await.unwrap(); // mine block api.evm_mine(None).await.unwrap(); let receipt = provider.get_transaction_receipt(pending.tx_hash().to_owned()).await.unwrap().unwrap(); assert_eq!(receipt.from, from); assert_eq!(receipt.to, Some(to)); // the recipient should have received the value let after_balance_to = provider.get_balance(to).await.unwrap(); assert_eq!(after_balance_to, before_balance_to + send_value); } #[tokio::test(flavor = "multi_thread")] async fn test_send_value_raw_deposit_transaction() { // enable the Optimism flag let (api, handle) = spawn(NodeConfig::test().with_networks(NetworkConfigs::with_optimism())).await; let accounts: Vec<_> = handle.dev_wallets().collect(); let signer: EthereumWallet = accounts[0].clone().into(); let from = accounts[0].address(); let to = accounts[1].address(); let provider = http_provider_with_signer(&handle.http_endpoint(), signer.clone()); let send_value = U256::from(1234); let before_balance_to = provider.get_balance(to).await.unwrap(); let tx = TransactionRequest::default() .with_chain_id(31337) .with_nonce(0) .with_from(from) .with_to(to) .with_value(send_value) .with_gas_limit(21_000) .with_max_fee_per_gas(20_000_000_000) .with_max_priority_fee_per_gas(1_000_000_000); let op_fields = OpTransactionFields { source_hash: Some(b256!( "0x0000000000000000000000000000000000000000000000000000000000000000" )), mint: Some(0), is_system_tx: Some(true), deposit_receipt_version: None, }; let other = serde_json::to_value(op_fields).unwrap().try_into().unwrap(); let tx = WithOtherFields { inner: tx, other }; let tx_envelope = tx.build(&signer).await.unwrap(); let mut tx_buffer = Vec::with_capacity(tx_envelope.encode_2718_len()); tx_envelope.encode_2718(&mut tx_buffer); let tx_encoded = tx_buffer.as_slice(); let pending = provider.send_raw_transaction(tx_encoded).await.unwrap().register().await.unwrap(); // mine block api.evm_mine(None).await.unwrap(); let receipt = provider.get_transaction_receipt(pending.tx_hash().to_owned()).await.unwrap().unwrap(); assert_eq!(receipt.from, from); assert_eq!(receipt.to, Some(to)); // the recipient should have received the value let after_balance_to = provider.get_balance(to).await.unwrap(); assert_eq!(after_balance_to, before_balance_to + send_value); } #[tokio::test(flavor = "multi_thread")] async fn test_deposit_transaction_hash_matches_sepolia() { // enable the Optimism flag let (_api, handle) = spawn(NodeConfig::test().with_networks(NetworkConfigs::with_optimism())).await; let accounts: Vec<_> = handle.dev_wallets().collect(); let signer: EthereumWallet = accounts[0].clone().into(); // https://sepolia-optimism.etherscan.io/tx/0xbf8b5f08c43e4b860715cd64fc0849bbce0d0ea20a76b269e7bc8886d112fca7 let tx_hash: TxHash = "0xbf8b5f08c43e4b860715cd64fc0849bbce0d0ea20a76b269e7bc8886d112fca7" .parse::() .unwrap(); // https://sepolia-optimism.etherscan.io/getRawTx?tx=0xbf8b5f08c43e4b860715cd64fc0849bbce0d0ea20a76b269e7bc8886d112fca7 let raw_deposit_tx = alloy_primitives::hex::decode( "7ef861a0dfd7ae78bf3c414cfaa77f13c0205c82eb9365e217b2daa3448c3156b69b27ac94778f2146f48179643473b82931c4cd7b8f153efd94778f2146f48179643473b82931c4cd7b8f153efd872386f26fc10000872386f26fc10000830186a08080", ) .unwrap(); let provider = http_provider_with_signer(&handle.http_endpoint(), signer.clone()); let receipt = provider .send_raw_transaction(raw_deposit_tx.as_slice()) .await .unwrap() .get_receipt() .await .unwrap(); assert_eq!(receipt.transaction_hash, tx_hash); } #[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_networks(NetworkConfigs::with_optimism())).await; let provider = http_provider(&handle.http_endpoint()); let sender = Address::random(); let recipient = Address::random(); let send_value = 1_000_000_000_u128; let sender_prev_balance = provider.get_balance(sender).await.unwrap(); assert_eq!(sender_prev_balance, U256::from(0)); let recipient_prev_balance = provider.get_balance(recipient).await.unwrap(); assert_eq!(recipient_prev_balance, U256::from(0)); let deposit_tx = TxDeposit { source_hash: b256!("0x0000000000000000000000000000000000000000000000000000000000000000"), from: sender, to: TxKind::Call(recipient), mint: send_value, value: U256::from(send_value), gas_limit: 21_000, is_system_transaction: false, input: Vec::new().into(), }; let mut tx_buffer = Vec::new(); deposit_tx.encode_2718(&mut tx_buffer); provider.send_raw_transaction(&tx_buffer).await.unwrap().get_receipt().await.unwrap(); let sender_new_balance = provider.get_balance(sender).await.unwrap(); // sender should've sent the entire deposited value to recipient assert_eq!(sender_new_balance, U256::from(0)); let recipient_new_balance = provider.get_balance(recipient).await.unwrap(); // recipient should've received the entire deposited value assert_eq!(recipient_new_balance, U256::from(send_value)); } #[test] fn preserves_op_fields_in_convert_to_anvil_receipt() { let receipt_json = json!({ "status": "0x1", "cumulativeGasUsed": "0x74e483", "logs": [], "logsBloom": Bloom::default(), "type": "0x2", "transactionHash": "0x91181b0dca3b29aa136eeb2f536be5ce7b0aebc949be1c44b5509093c516097d", "transactionIndex": "0x10", "blockHash": "0x54bafb12e8cea9bb355fbf03a4ac49e42a2a1a80fa6cf4364b342e2de6432b5d", "blockNumber": "0x7b1ab93", "gasUsed": "0xc222", "effectiveGasPrice": "0x18961", "from": "0x2d815240a61731c75fa01b2793e1d3ed09f289d0", "to": "0x4200000000000000000000000000000000000000", "contractAddress": Value::Null, "l1BaseFeeScalar": "0x146b", "l1BlobBaseFee": "0x6a83078", "l1BlobBaseFeeScalar": "0xf79c5", "l1Fee": "0x51a9af7fd3", "l1GasPrice": "0x972fe4acc", "l1GasUsed": "0x640", }); let receipt: alloy_network::AnyTransactionReceipt = serde_json::from_value(receipt_json).expect("valid receipt json"); let converted = foundry_primitives::FoundryTxReceipt::try_from(receipt).expect("conversion should succeed"); let converted_json = serde_json::to_value(&converted).expect("serialize to json"); for (key, expected) in [ ("l1Fee", "0x51a9af7fd3"), ("l1GasPrice", "0x972fe4acc"), ("l1GasUsed", "0x640"), ("l1BaseFeeScalar", "0x146b"), ("l1BlobBaseFee", "0x6a83078"), ("l1BlobBaseFeeScalar", "0xf79c5"), ] { let got = converted_json.get(key).and_then(Value::as_str); assert_eq!(got, Some(expected), "field `{key}` mismatch"); } } const GAS_TRANSFER: u64 = 21_000; /// Test that Optimism uses Canyon base fee params instead of Ethereum params. /// /// Optimism Canyon uses different EIP-1559 parameters: /// - elasticity_multiplier: 6 (vs Ethereum's 2) /// - base_fee_max_change_denominator: 250 (vs Ethereum's 8) /// /// This means with a full block: /// - Ethereum: base_fee increases by base_fee * 1 / 8 = 12.5% /// - Optimism: base_fee increases by base_fee * 5 / 250 = 2% #[tokio::test(flavor = "multi_thread")] async fn test_optimism_base_fee_params() { // Spawn an Optimism node with a gas limit equal to one transfer (full block scenario) let (_api, handle) = spawn( NodeConfig::test() .with_networks(NetworkConfigs::with_optimism()) .with_base_fee(Some(INITIAL_BASE_FEE)) .with_gas_limit(Some(GAS_TRANSFER)), ) .await; let wallet = handle.dev_wallets().next().unwrap(); let signer: EthereumWallet = wallet.clone().into(); let provider = http_provider_with_signer(&handle.http_endpoint(), signer); let tx = TransactionRequest::default().to(Address::random()).with_value(U256::from(1337)); let tx = WithOtherFields::new(tx); // Send first transaction to fill the block provider.send_transaction(tx.clone()).await.unwrap().get_receipt().await.unwrap(); let base_fee = provider .get_block(BlockId::latest()) .await .unwrap() .unwrap() .header .base_fee_per_gas .unwrap(); // Send second transaction to fill the next block provider.send_transaction(tx.clone()).await.unwrap().get_receipt().await.unwrap(); let next_base_fee = provider .get_block(BlockId::latest()) .await .unwrap() .unwrap() .header .base_fee_per_gas .unwrap(); assert!(next_base_fee > base_fee, "base fee should increase with full block"); // Optimism Canyon formula: base_fee * (elasticity - 1) / denominator = base_fee * 5 / 250 // = INITIAL_BASE_FEE * 5 / 250 = 1_000_000_000 * 5 / 250 = 20_000_000 // // Note: Ethereum would be INITIAL_BASE_FEE + 125_000_000 (12.5% increase) let expected_op_increase = INITIAL_BASE_FEE * 5 / 250; // 2% increase = 20_000_000 assert_eq!( next_base_fee, INITIAL_BASE_FEE + expected_op_increase, "Optimism should use Canyon base fee params (2% max increase), not Ethereum's (12.5%)" ); // Explicitly verify it's NOT using Ethereum params (which would give 12.5% increase) let ethereum_increase = INITIAL_BASE_FEE / 8; // 125_000_000 assert_ne!( next_base_fee, INITIAL_BASE_FEE + ethereum_increase, "Should not be using Ethereum base fee params" ); } ================================================ FILE: crates/anvil/tests/it/otterscan.rs ================================================ //! Tests for otterscan endpoints. use crate::abi::Multicall; use alloy_network::TransactionResponse; use alloy_primitives::{Address, Bytes, U256, address}; use alloy_provider::Provider; use alloy_rpc_types::{ BlockNumberOrTag, TransactionRequest, trace::otterscan::{InternalOperation, OperationType, TraceEntry}, }; use alloy_serde::WithOtherFields; use alloy_sol_types::{SolCall, SolError, SolValue, sol}; use anvil::{NodeConfig, spawn}; use foundry_evm::hardfork::EthereumHardfork; use std::collections::VecDeque; #[tokio::test(flavor = "multi_thread")] async fn erigon_get_header_by_number() { let (api, _handle) = spawn(NodeConfig::test()).await; api.mine_one().await; let res0 = api.erigon_get_header_by_number(0.into()).await.unwrap().unwrap(); assert_eq!(res0.header.number, 0); let res1 = api.erigon_get_header_by_number(1.into()).await.unwrap().unwrap(); assert_eq!(res1.header.number, 1); } #[tokio::test(flavor = "multi_thread")] async fn ots_get_api_level() { let (api, _handle) = spawn(NodeConfig::test()).await; assert_eq!(api.ots_get_api_level().await.unwrap(), 8); } #[tokio::test(flavor = "multi_thread")] async fn ots_get_internal_operations_contract_deploy() { let (api, handle) = spawn(NodeConfig::test()).await; let provider = handle.http_provider(); let sender = handle.dev_accounts().next().unwrap(); let contract_receipt = Multicall::deploy_builder(&provider).send().await.unwrap().get_receipt().await.unwrap(); let res = api.ots_get_internal_operations(contract_receipt.transaction_hash).await.unwrap(); assert_eq!( res, [InternalOperation { r#type: OperationType::OpCreate, from: sender, to: contract_receipt.contract_address.unwrap(), value: U256::from(0) }], ); } #[tokio::test(flavor = "multi_thread")] async fn ots_get_internal_operations_contract_transfer() { let (api, handle) = spawn(NodeConfig::test()).await; let provider = handle.http_provider(); let accounts: Vec<_> = handle.dev_wallets().collect(); let from = accounts[0].address(); let to = accounts[1].address(); let amount = handle.genesis_balance().checked_div(U256::from(2u64)).unwrap(); let tx = TransactionRequest::default().to(to).value(amount).from(from); let tx = WithOtherFields::new(tx); let receipt = provider.send_transaction(tx).await.unwrap().get_receipt().await.unwrap(); let res = api.ots_get_internal_operations(receipt.transaction_hash).await.unwrap(); assert_eq!( res, [InternalOperation { r#type: OperationType::OpTransfer, from, to, value: amount }], ); } #[tokio::test(flavor = "multi_thread")] async fn ots_get_internal_operations_contract_create2() { sol!( #[sol(rpc, bytecode = "60808060405234601557610147908161001a8239f35b5f80fdfe6080600436101561000e575f80fd5b5f3560e01c636cd5c39b14610021575f80fd5b346100d0575f3660031901126100d0575f602082810191825282526001600160401b03916040810191838311828410176100d4578261008960405f959486958252606081019486865281518091608084015e81018660808201520360208101845201826100ee565b519082734e59b44847b379578588920ca78fbf26c0b4956c5af1903d156100e8573d9081116100d4576040516100c991601f01601f1916602001906100ee565b156100d057005b5f80fd5b634e487b7160e01b5f52604160045260245ffd5b506100c9565b601f909101601f19168101906001600160401b038211908210176100d45760405256fea2646970667358221220f76968e121fc002b537029df51a2aecca0793282491baf84b872ffbfbfb1c9d764736f6c63430008190033")] contract Contract { address constant CREATE2_DEPLOYER = 0x4e59b44847b379578588920cA78FbF26c0B4956C; function deployContract() public { uint256 salt = 0; uint256 code = 0; bytes memory creationCode = abi.encodePacked(code); (bool success,) = address(CREATE2_DEPLOYER).call(abi.encodePacked(salt, creationCode)); require(success); } } ); let (api, handle) = spawn(NodeConfig::test()).await; let provider = handle.http_provider(); let contract = Contract::deploy(&provider).await.unwrap(); let receipt = contract.deployContract().send().await.unwrap().get_receipt().await.unwrap(); let res = api.ots_get_internal_operations(receipt.transaction_hash).await.unwrap(); assert_eq!( res, [InternalOperation { r#type: OperationType::OpCreate2, from: address!("0x4e59b44847b379578588920cA78FbF26c0B4956C"), to: address!("0x347bcdad821abc09b8c275881b368de36476b62c"), value: U256::from(0), }], ); } #[tokio::test(flavor = "multi_thread")] async fn ots_get_internal_operations_contract_selfdestruct_london() { ots_get_internal_operations_contract_selfdestruct(EthereumHardfork::London).await; } #[tokio::test(flavor = "multi_thread")] async fn ots_get_internal_operations_contract_selfdestruct_cancun() { ots_get_internal_operations_contract_selfdestruct(EthereumHardfork::Cancun).await; } async fn ots_get_internal_operations_contract_selfdestruct(hardfork: EthereumHardfork) { sol!( #[sol(rpc, bytecode = "608080604052607f908160108239f3fe6004361015600c57600080fd5b6000803560e01c6375fc8e3c14602157600080fd5b346046578060031936011260465773dcdd539da22bffaa499dbea4d37d086dde196e75ff5b80fdfea264697066735822122080a9ad005cc408b2d4e30ca11216d8e310700fbcdf58a629d6edbb91531f9c6164736f6c63430008190033")] contract Contract { constructor() payable {} function goodbye() public { selfdestruct(payable(0xDcDD539DA22bfFAa499dBEa4d37d086Dde196E75)); } } ); let (api, handle) = spawn(NodeConfig::test().with_hardfork(Some(hardfork.into()))).await; let provider = handle.http_provider(); let sender = handle.dev_accounts().next().unwrap(); let value = U256::from(69); let contract_address = Contract::deploy_builder(&provider).from(sender).value(value).deploy().await.unwrap(); let contract = Contract::new(contract_address, &provider); let receipt = contract.goodbye().send().await.unwrap().get_receipt().await.unwrap(); let expected_to = address!("0xDcDD539DA22bfFAa499dBEa4d37d086Dde196E75"); let expected_value = value; let res = api.ots_get_internal_operations(receipt.transaction_hash).await.unwrap(); assert_eq!( res, [InternalOperation { r#type: OperationType::OpSelfDestruct, from: contract_address, to: expected_to, value: expected_value, }], ); } #[tokio::test(flavor = "multi_thread")] async fn ots_has_code() { let (api, handle) = spawn(NodeConfig::test()).await; let provider = handle.http_provider(); let sender = handle.dev_accounts().next().unwrap(); api.mine_one().await; let contract_address = sender.create(0); // no code in the address before deploying assert!(!api.ots_has_code(contract_address, BlockNumberOrTag::Number(1)).await.unwrap()); let contract_builder = Multicall::deploy_builder(&provider); let contract_receipt = contract_builder.send().await.unwrap().get_receipt().await.unwrap(); let num = provider.get_block_number().await.unwrap(); assert_eq!(num, contract_receipt.block_number.unwrap()); // code is detected after deploying assert!(api.ots_has_code(contract_address, BlockNumberOrTag::Number(num)).await.unwrap()); // code is not detected for the previous block assert!(!api.ots_has_code(contract_address, BlockNumberOrTag::Number(num - 1)).await.unwrap()); } #[tokio::test(flavor = "multi_thread")] async fn test_call_ots_trace_transaction() { sol!( #[sol(rpc, bytecode = "608080604052346026575f80546001600160a01b0319163317905561025e908161002b8239f35b5f80fdfe6080604081815260049081361015610015575f80fd5b5f925f3560e01c9081636a6758fe1461019a5750806396385e3914610123578063a1325397146101115763c04062261461004d575f80fd5b5f3660031901126100d5578051633533ac7f60e11b81526020818481305afa80156100cb576100d9575b50303b156100d55780516396385e3960e01b8152915f83828183305af180156100cb576100a2578380f35b919250906001600160401b0383116100b8575052005b604190634e487b7160e01b5f525260245ffd5b82513d5f823e3d90fd5b5f80fd5b6020813d602011610109575b816100f2602093836101b3565b810103126100d55751801515036100d5575f610077565b3d91506100e5565b346100d5575f3660031901126100d557005b5090346100d5575f3660031901126100d5575f805481908190819047906001600160a01b03165af1506101546101ea565b50815163a132539760e01b6020820190815282825292909182820191906001600160401b038311848410176100b8575f8086868686525190305af4506101986101ea565b005b346100d5575f3660031901126100d55780600160209252f35b601f909101601f19168101906001600160401b038211908210176101d657604052565b634e487b7160e01b5f52604160045260245ffd5b3d15610223573d906001600160401b0382116101d65760405191610218601f8201601f1916602001846101b3565b82523d5f602084013e565b60609056fea264697066735822122099817ea378044f1f6434272aeb1f3f01a734645e599e69b4caf2ba7a4fb65f9d64736f6c63430008190033")] contract Contract { address private owner; constructor() { owner = msg.sender; } function run() payable public { this.do_staticcall(); this.do_call(); } function do_staticcall() external view returns (bool) { return true; } function do_call() external { owner.call{value: address(this).balance}(""); address(this).delegatecall(abi.encodeWithSignature("do_delegatecall()")); } function do_delegatecall() external {} } ); let (api, handle) = spawn(NodeConfig::test()).await; let provider = handle.http_provider(); let wallets = handle.dev_wallets().collect::>(); let sender = wallets[0].address(); let contract_address = Contract::deploy_builder(&provider).from(sender).deploy().await.unwrap(); let contract = Contract::new(contract_address, &provider); let receipt = contract.run().value(U256::from(1337)).send().await.unwrap().get_receipt().await.unwrap(); let res = api.ots_trace_transaction(receipt.transaction_hash).await.unwrap(); let expected = vec![ TraceEntry { r#type: "CALL".to_string(), depth: 0, from: sender, to: contract_address, value: Some(U256::from(1337)), input: Contract::runCall::SELECTOR.into(), output: Bytes::new(), }, TraceEntry { r#type: "STATICCALL".to_string(), depth: 1, from: contract_address, to: contract_address, value: Some(U256::ZERO), input: Contract::do_staticcallCall::SELECTOR.into(), output: true.abi_encode().into(), }, TraceEntry { r#type: "CALL".to_string(), depth: 1, from: contract_address, to: contract_address, value: Some(U256::ZERO), input: Contract::do_callCall::SELECTOR.into(), output: Bytes::new(), }, TraceEntry { r#type: "CALL".to_string(), depth: 2, from: contract_address, to: sender, value: Some(U256::from(1337)), input: Bytes::new(), output: Bytes::new(), }, TraceEntry { r#type: "DELEGATECALL".to_string(), depth: 2, from: contract_address, to: contract_address, value: Some(U256::ZERO), input: Contract::do_delegatecallCall::SELECTOR.into(), output: Bytes::new(), }, ]; assert_eq!(res, expected); } #[tokio::test(flavor = "multi_thread")] async fn ots_get_transaction_error() { sol!( #[sol(rpc, bytecode = "6080806040523460135760a3908160188239f35b5f80fdfe60808060405260043610156011575f80fd5b5f3560e01c63f67f4650146023575f80fd5b346069575f3660031901126069576346b7545f60e11b81526020600482015260126024820152712932bb32b93a29ba3934b733a337b7a130b960711b6044820152606490fd5b5f80fdfea264697066735822122069222918090d4d3ddc6a9c8b6ef282464076c71f923a0e8618ed25489b87f12b64736f6c63430008190033")] contract Contract { error CustomError(string msg); function trigger_revert() public { revert CustomError("RevertStringFooBar"); } } ); let (api, handle) = spawn(NodeConfig::test()).await; let provider = handle.http_provider(); let contract = Contract::deploy(&provider).await.unwrap(); let receipt = contract.trigger_revert().send().await.unwrap().get_receipt().await.unwrap(); let err = api.ots_get_transaction_error(receipt.transaction_hash).await.unwrap(); let expected = Contract::CustomError { msg: String::from("RevertStringFooBar") }.abi_encode(); assert_eq!(err, Bytes::from(expected)); } #[tokio::test(flavor = "multi_thread")] async fn ots_get_transaction_error_no_error() { let (api, handle) = spawn(NodeConfig::test()).await; let provider = handle.http_provider(); // Send a successful transaction let tx = TransactionRequest::default().to(Address::random()).value(U256::from(100)); let tx = WithOtherFields::new(tx); let receipt = provider.send_transaction(tx).await.unwrap().get_receipt().await.unwrap(); let res = api.ots_get_transaction_error(receipt.transaction_hash).await.unwrap(); assert!(res.is_empty(), "{res}"); } #[tokio::test(flavor = "multi_thread")] async fn ots_get_block_details() { let (api, handle) = spawn(NodeConfig::test()).await; let provider = handle.http_provider(); let tx = TransactionRequest::default().to(Address::random()).value(U256::from(100)); let tx = WithOtherFields::new(tx); provider.send_transaction(tx).await.unwrap().get_receipt().await.unwrap(); let result = api.ots_get_block_details(1.into()).await.unwrap(); assert_eq!(result.block.transaction_count, 1); } #[tokio::test(flavor = "multi_thread")] async fn ots_get_block_details_by_hash() { let (api, handle) = spawn(NodeConfig::test()).await; let provider = handle.http_provider(); let tx = TransactionRequest::default().to(Address::random()).value(U256::from(100)); let tx = WithOtherFields::new(tx); let receipt = provider.send_transaction(tx).await.unwrap().get_receipt().await.unwrap(); let block_hash = receipt.block_hash.unwrap(); let result = api.ots_get_block_details_by_hash(block_hash).await.unwrap(); assert_eq!(result.block.transaction_count, 1); } #[tokio::test(flavor = "multi_thread")] async fn ots_get_block_transactions() { let (api, handle) = spawn(NodeConfig::test()).await; let provider = handle.http_provider(); // disable automine api.anvil_set_auto_mine(false).await.unwrap(); let mut hashes = VecDeque::new(); for i in 0..10 { let tx = TransactionRequest::default().to(Address::random()).value(U256::from(100)).nonce(i); let tx = WithOtherFields::new(tx); let pending_receipt = provider.send_transaction(tx).await.unwrap().register().await.unwrap(); hashes.push_back(*pending_receipt.tx_hash()); } api.mine_one().await; let page_size = 3; for page in 0..4 { let result = api.ots_get_block_transactions(1, page, page_size).await.unwrap(); assert!(result.receipts.len() <= page_size); let len = result.receipts.len(); assert!(len <= page_size); assert!(result.fullblock.transaction_count == result.receipts.len()); result.receipts.iter().enumerate().for_each(|(i, receipt)| { let expected = hashes.pop_front(); assert_eq!(expected, Some(receipt.receipt.transaction_hash)); assert_eq!(expected, result.fullblock.block.transactions.hashes().nth(i)); }); } assert!(hashes.is_empty()); } #[tokio::test(flavor = "multi_thread")] async fn ots_search_transactions_before() { let (api, handle) = spawn(NodeConfig::test()).await; let provider = handle.http_provider(); let sender = handle.dev_accounts().next().unwrap(); let mut hashes = vec![]; for i in 0..7 { let tx = TransactionRequest::default().to(Address::random()).value(U256::from(100)).nonce(i); let tx = WithOtherFields::new(tx); let receipt = provider.send_transaction(tx).await.unwrap().get_receipt().await.unwrap(); hashes.push(receipt.transaction_hash); } let page_size = 2; let mut block = 0; for i in 0..4 { let result = api.ots_search_transactions_before(sender, block, page_size).await.unwrap(); assert_eq!(result.first_page, i == 0); assert_eq!(result.last_page, i == 3); // check each individual hash result.txs.iter().for_each(|tx| { assert_eq!(hashes.pop(), Some(tx.tx_hash())); }); block = result.txs.last().unwrap().block_number.unwrap(); } assert!(hashes.is_empty()); } #[tokio::test(flavor = "multi_thread")] async fn ots_search_transactions_after() { let (api, handle) = spawn(NodeConfig::test()).await; let provider = handle.http_provider(); let sender = handle.dev_accounts().next().unwrap(); let mut hashes = VecDeque::new(); for i in 0..7 { let tx = TransactionRequest::default().to(Address::random()).value(U256::from(100)).nonce(i); let tx = WithOtherFields::new(tx); let receipt = provider.send_transaction(tx).await.unwrap().get_receipt().await.unwrap(); hashes.push_front(receipt.transaction_hash); } let page_size = 2; let mut block = 0; for i in 0..4 { let result = api.ots_search_transactions_after(sender, block, page_size).await.unwrap(); assert_eq!(result.first_page, i == 3); assert_eq!(result.last_page, i == 0); // check each individual hash result.txs.iter().rev().for_each(|tx| { assert_eq!(hashes.pop_back(), Some(tx.tx_hash())); }); block = result.txs.first().unwrap().block_number.unwrap(); } assert!(hashes.is_empty()); } #[tokio::test(flavor = "multi_thread")] async fn ots_get_transaction_by_sender_and_nonce() { let (api, handle) = spawn(NodeConfig::test()).await; let provider = handle.http_provider(); let sender = handle.dev_accounts().next().unwrap(); let tx1 = WithOtherFields::new( TransactionRequest::default() .from(sender) .to(Address::random()) .value(U256::from(100)) .nonce(0), ); let tx2 = WithOtherFields::new( TransactionRequest::default() .from(sender) .to(Address::random()) .value(U256::from(100)) .nonce(1), ); let receipt1 = provider.send_transaction(tx1).await.unwrap().get_receipt().await.unwrap(); let receipt2 = provider.send_transaction(tx2).await.unwrap().get_receipt().await.unwrap(); let result1 = api.ots_get_transaction_by_sender_and_nonce(sender, U256::from(0)).await.unwrap().unwrap(); let result2 = api.ots_get_transaction_by_sender_and_nonce(sender, U256::from(1)).await.unwrap().unwrap(); assert_eq!(result1, receipt1.transaction_hash); assert_eq!(result2, receipt2.transaction_hash); } #[tokio::test(flavor = "multi_thread")] async fn ots_get_contract_creator() { let (api, handle) = spawn(NodeConfig::test()).await; let provider = handle.http_provider(); let sender = handle.dev_accounts().next().unwrap(); let receipt = Multicall::deploy_builder(&provider).send().await.unwrap().get_receipt().await.unwrap(); let contract_address = receipt.contract_address.unwrap(); let creator = api.ots_get_contract_creator(contract_address).await.unwrap().unwrap(); assert_eq!(creator.creator, sender); assert_eq!(creator.hash, receipt.transaction_hash); } ================================================ FILE: crates/anvil/tests/it/proof.rs ================================================ //! tests for `eth_getProof` use alloy_primitives::{Address, B256, Bytes, U256, address, fixed_bytes}; use anvil::{NodeConfig, eth::EthApi, spawn}; use foundry_primitives::FoundryNetwork; use std::{collections::BTreeMap, str::FromStr}; async fn verify_account_proof( api: &EthApi, address: Address, proof: impl IntoIterator, ) { let expected_proof = proof.into_iter().map(Bytes::from_str).collect::, _>>().unwrap(); let proof = api.get_proof(address, Vec::new(), None).await.unwrap(); assert_eq!(proof.account_proof, expected_proof); } async fn verify_storage_proof( api: &EthApi, address: Address, slot: B256, proof: impl IntoIterator, ) { let expected_proof = proof.into_iter().map(Bytes::from_str).collect::, _>>().unwrap(); let proof = api.get_proof(address, vec![slot], None).await.unwrap(); assert_eq!(proof.storage_proof[0].proof, expected_proof); } #[tokio::test(flavor = "multi_thread")] async fn test_account_proof() { let (api, _handle) = spawn(NodeConfig::empty_state()).await; api.anvil_set_balance( address!("0x2031f89b3ea8014eb51a78c316e42af3e0d7695f"), U256::from(45000000000000000000_u128), ) .await .unwrap(); api.anvil_set_balance(address!("0x33f0fc440b8477fcfbe9d0bf8649e7dea9baedb2"), U256::from(1)) .await .unwrap(); api.anvil_set_balance( address!("0x62b0dd4aab2b1a0a04e279e2b828791a10755528"), U256::from(1100000000000000000_u128), ) .await .unwrap(); api.anvil_set_balance( address!("0x1ed9b1dd266b607ee278726d324b855a093394a6"), U256::from(120000000000000000_u128), ) .await .unwrap(); // Note: proof values account for EIP-2935 history storage contract deployed at genesis. verify_account_proof(&api, address!("0x2031f89b3ea8014eb51a78c316e42af3e0d7695f"), [ "0xf851808080808080a0347892f3eeb6af4d4574ad0b89706247951bbbece83ef27efc46eb96436598b5808080a07e35bed15a14b4072a0929310da6a26e34d7017a82cca3589d7d0badb53de2e3808080808080", "0xe217a040f916999be583c572cc4dd369ec53b0a99f7de95f13880cf203d98f935ed1b3", "0xf87180a04fb9bab4bb88c062f32452b7c94c8f64d07b5851d44a39f1e32ba4b1829fdbfb8080808080a0b61eeb2eb82808b73c4ad14140a2836689f4ab8445d69dd40554eaf1fce34bc080808080808080a0dea230ff2026e65de419288183a340125b04b8405cc61627b3b4137e2260a1e880", "0xf8719f31355ec1c8f7e26bb3ccbcb0b75d870d15846c0b98e5cc452db46c37faea40b84ff84d80890270801d946c940000a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a0c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", ]).await; verify_account_proof(&api, address!("0x33f0fc440b8477fcfbe9d0bf8649e7dea9baedb2"), [ "0xf851808080808080a0347892f3eeb6af4d4574ad0b89706247951bbbece83ef27efc46eb96436598b5808080a07e35bed15a14b4072a0929310da6a26e34d7017a82cca3589d7d0badb53de2e3808080808080", "0xe217a040f916999be583c572cc4dd369ec53b0a99f7de95f13880cf203d98f935ed1b3", "0xf87180a04fb9bab4bb88c062f32452b7c94c8f64d07b5851d44a39f1e32ba4b1829fdbfb8080808080a0b61eeb2eb82808b73c4ad14140a2836689f4ab8445d69dd40554eaf1fce34bc080808080808080a0dea230ff2026e65de419288183a340125b04b8405cc61627b3b4137e2260a1e880", "0xe48200d3a0ef957210bca5b9b402d614eb8408c88cfbf4913eb6ab83ca233c8b8f0e626b54", "0xf851808080a02743a5addaf4cf9b8c0c073e1eaa555deaaf8c41cb2b41958e88624fa45c2d908080808080a0bfbf6937911dfb88113fecdaa6bde822e4e99dae62489fcf61a91cb2f36793d680808080808080", "0xf8679e207781e762f3577784bab7491fcc43e291ce5a356b9bc517ac52eed3a37ab846f8448001a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a0c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", ]).await; verify_account_proof(&api, address!("0x62b0dd4aab2b1a0a04e279e2b828791a10755528"), [ "0xf851808080808080a0347892f3eeb6af4d4574ad0b89706247951bbbece83ef27efc46eb96436598b5808080a07e35bed15a14b4072a0929310da6a26e34d7017a82cca3589d7d0badb53de2e3808080808080", "0xe217a040f916999be583c572cc4dd369ec53b0a99f7de95f13880cf203d98f935ed1b3", "0xf87180a04fb9bab4bb88c062f32452b7c94c8f64d07b5851d44a39f1e32ba4b1829fdbfb8080808080a0b61eeb2eb82808b73c4ad14140a2836689f4ab8445d69dd40554eaf1fce34bc080808080808080a0dea230ff2026e65de419288183a340125b04b8405cc61627b3b4137e2260a1e880", "0xf8709f3936599f93b769acf90c7178fd2ddcac1b5b4bc9949ee5a04b7e0823c2446eb84ef84c80880f43fc2c04ee0000a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a0c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", ]).await; verify_account_proof(&api, address!("0x1ed9b1dd266b607ee278726d324b855a093394a6"), [ "0xf851808080808080a0347892f3eeb6af4d4574ad0b89706247951bbbece83ef27efc46eb96436598b5808080a07e35bed15a14b4072a0929310da6a26e34d7017a82cca3589d7d0badb53de2e3808080808080", "0xe217a040f916999be583c572cc4dd369ec53b0a99f7de95f13880cf203d98f935ed1b3", "0xf87180a04fb9bab4bb88c062f32452b7c94c8f64d07b5851d44a39f1e32ba4b1829fdbfb8080808080a0b61eeb2eb82808b73c4ad14140a2836689f4ab8445d69dd40554eaf1fce34bc080808080808080a0dea230ff2026e65de419288183a340125b04b8405cc61627b3b4137e2260a1e880", "0xe48200d3a0ef957210bca5b9b402d614eb8408c88cfbf4913eb6ab83ca233c8b8f0e626b54", "0xf851808080a02743a5addaf4cf9b8c0c073e1eaa555deaaf8c41cb2b41958e88624fa45c2d908080808080a0bfbf6937911dfb88113fecdaa6bde822e4e99dae62489fcf61a91cb2f36793d680808080808080", "0xf86f9e207a32b8ab5eb4b043c65b1f00c93f517bc8883c5cd31baf8e8a279475e3b84ef84c808801aa535d3d0c0000a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a0c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", ]).await; } #[tokio::test(flavor = "multi_thread")] async fn test_storage_proof() { let target = address!("0x1ed9b1dd266b607ee278726d324b855a093394a6"); let (api, _handle) = spawn(NodeConfig::empty_state()).await; let storage: BTreeMap = serde_json::from_str(include_str!("../../test-data/storage_sample.json")).unwrap(); for (key, value) in storage { api.anvil_set_storage_at(target, key, value).await.unwrap(); } verify_storage_proof(&api, target, fixed_bytes!("0000000000000000000000000000000000000000000000000000000000000022"), [ "0xf9019180a0aafd5b14a6edacd149e110ba6776a654f2dbffca340902be933d011113f2750380a0a502c93b1918c4c6534d4593ae03a5a23fa10ebc30ffb7080b297bff2446e42da02eb2bf45fd443bd1df8b6f9c09726a4c6252a0f7896a131a081e39a7f644b38980a0a9cf7f673a0bce76fd40332afe8601542910b48dea44e93933a3e5e930da5d19a0ddf79db0a36d0c8134ba143bcb541cd4795a9a2bae8aca0ba24b8d8963c2a77da0b973ec0f48f710bf79f63688485755cbe87f9d4c68326bb83c26af620802a80ea0f0855349af6bf84afc8bca2eda31c8ef8c5139be1929eeb3da4ba6b68a818cb0a0c271e189aeeb1db5d59d7fe87d7d6327bbe7cfa389619016459196497de3ccdea0e7503ba5799e77aa31bbe1310c312ca17b2c5bcc8fa38f266675e8f154c2516ba09278b846696d37213ab9d20a5eb42b03db3173ce490a2ef3b2f3b3600579fc63a0e9041059114f9c910adeca12dbba1fef79b2e2c8899f2d7213cd22dfe4310561a047c59da56bb2bf348c9dd2a2e8f5538a92b904b661cfe54a4298b85868bbe4858080", "0xf85180a0776aa456ba9c5008e03b82b841a9cf2fc1e8578cfacd5c9015804eae315f17fb80808080808080808080808080a072e3e284d47badbb0a5ca1421e1179d3ea90cc10785b26b74fb8a81f0f9e841880", "0xf843a020035b26e3e9eee00e0d72fd1ee8ddca6894550dca6916ea2ac6baa90d11e510a1a0f5a5fd42d16a20302798ef6ed309979b43003d2320d9f0e8ea9831a92759fb4b" ]).await; verify_storage_proof(&api, target, fixed_bytes!("0000000000000000000000000000000000000000000000000000000000000023"), [ "0xf9019180a0aafd5b14a6edacd149e110ba6776a654f2dbffca340902be933d011113f2750380a0a502c93b1918c4c6534d4593ae03a5a23fa10ebc30ffb7080b297bff2446e42da02eb2bf45fd443bd1df8b6f9c09726a4c6252a0f7896a131a081e39a7f644b38980a0a9cf7f673a0bce76fd40332afe8601542910b48dea44e93933a3e5e930da5d19a0ddf79db0a36d0c8134ba143bcb541cd4795a9a2bae8aca0ba24b8d8963c2a77da0b973ec0f48f710bf79f63688485755cbe87f9d4c68326bb83c26af620802a80ea0f0855349af6bf84afc8bca2eda31c8ef8c5139be1929eeb3da4ba6b68a818cb0a0c271e189aeeb1db5d59d7fe87d7d6327bbe7cfa389619016459196497de3ccdea0e7503ba5799e77aa31bbe1310c312ca17b2c5bcc8fa38f266675e8f154c2516ba09278b846696d37213ab9d20a5eb42b03db3173ce490a2ef3b2f3b3600579fc63a0e9041059114f9c910adeca12dbba1fef79b2e2c8899f2d7213cd22dfe4310561a047c59da56bb2bf348c9dd2a2e8f5538a92b904b661cfe54a4298b85868bbe4858080", "0xf8518080808080a0d546c4ca227a267d29796643032422374624ed109b3d94848c5dc06baceaee76808080808080a027c48e210ccc6e01686be2d4a199d35f0e1e8df624a8d3a17c163be8861acd6680808080", "0xf843a0207b2b5166478fd4318d2acc6cc2c704584312bdd8781b32d5d06abda57f4230a1a0db56114e00fdd4c1f85c892bf35ac9a89289aaecb1ebd0a96cde606a748b5d71" ]).await; verify_storage_proof(&api, target, fixed_bytes!("0000000000000000000000000000000000000000000000000000000000000024"), [ "0xf9019180a0aafd5b14a6edacd149e110ba6776a654f2dbffca340902be933d011113f2750380a0a502c93b1918c4c6534d4593ae03a5a23fa10ebc30ffb7080b297bff2446e42da02eb2bf45fd443bd1df8b6f9c09726a4c6252a0f7896a131a081e39a7f644b38980a0a9cf7f673a0bce76fd40332afe8601542910b48dea44e93933a3e5e930da5d19a0ddf79db0a36d0c8134ba143bcb541cd4795a9a2bae8aca0ba24b8d8963c2a77da0b973ec0f48f710bf79f63688485755cbe87f9d4c68326bb83c26af620802a80ea0f0855349af6bf84afc8bca2eda31c8ef8c5139be1929eeb3da4ba6b68a818cb0a0c271e189aeeb1db5d59d7fe87d7d6327bbe7cfa389619016459196497de3ccdea0e7503ba5799e77aa31bbe1310c312ca17b2c5bcc8fa38f266675e8f154c2516ba09278b846696d37213ab9d20a5eb42b03db3173ce490a2ef3b2f3b3600579fc63a0e9041059114f9c910adeca12dbba1fef79b2e2c8899f2d7213cd22dfe4310561a047c59da56bb2bf348c9dd2a2e8f5538a92b904b661cfe54a4298b85868bbe4858080", "0xf85180808080a030263404acfee103d0b1019053ff3240fce433c69b709831673285fa5887ce4c80808080808080a0f8f1fbb1f7b482d9860480feebb83ff54a8b6ec1ead61cc7d2f25d7c01659f9c80808080", "0xf843a020d332d19b93bcabe3cce7ca0c18a052f57e5fd03b4758a09f30f5ddc4b22ec4a1a0c78009fdf07fc56a11f122370658a353aaa542ed63e44c4bc15ff4cd105ab33c", ]).await; verify_storage_proof(&api, target, fixed_bytes!("0000000000000000000000000000000000000000000000000000000000000100"), [ "0xf9019180a0aafd5b14a6edacd149e110ba6776a654f2dbffca340902be933d011113f2750380a0a502c93b1918c4c6534d4593ae03a5a23fa10ebc30ffb7080b297bff2446e42da02eb2bf45fd443bd1df8b6f9c09726a4c6252a0f7896a131a081e39a7f644b38980a0a9cf7f673a0bce76fd40332afe8601542910b48dea44e93933a3e5e930da5d19a0ddf79db0a36d0c8134ba143bcb541cd4795a9a2bae8aca0ba24b8d8963c2a77da0b973ec0f48f710bf79f63688485755cbe87f9d4c68326bb83c26af620802a80ea0f0855349af6bf84afc8bca2eda31c8ef8c5139be1929eeb3da4ba6b68a818cb0a0c271e189aeeb1db5d59d7fe87d7d6327bbe7cfa389619016459196497de3ccdea0e7503ba5799e77aa31bbe1310c312ca17b2c5bcc8fa38f266675e8f154c2516ba09278b846696d37213ab9d20a5eb42b03db3173ce490a2ef3b2f3b3600579fc63a0e9041059114f9c910adeca12dbba1fef79b2e2c8899f2d7213cd22dfe4310561a047c59da56bb2bf348c9dd2a2e8f5538a92b904b661cfe54a4298b85868bbe4858080", "0xf891a090bacef44b189ddffdc5f22edc70fe298c58e5e523e6e1dfdf7dbc6d657f7d1b80a026eed68746028bc369eb456b7d3ee475aa16f34e5eaa0c98fdedb9c59ebc53b0808080a09ce86197173e14e0633db84ce8eea32c5454eebe954779255644b45b717e8841808080a0328c7afb2c58ef3f8c4117a8ebd336f1a61d24591067ed9c5aae94796cac987d808080808080", ]).await; } #[tokio::test(flavor = "multi_thread")] async fn can_get_random_account_proofs() { let (api, _handle) = spawn(NodeConfig::test()).await; for acc in std::iter::repeat_with(Address::random).take(10) { let _ = api .get_proof(acc, Vec::new(), None) .await .unwrap_or_else(|_| panic!("Failed to get proof for {acc:?}")); } } ================================================ FILE: crates/anvil/tests/it/pubsub.rs ================================================ //! tests for subscriptions use crate::utils::{connect_pubsub, connect_pubsub_with_wallet}; use alloy_network::{EthereumWallet, TransactionBuilder}; use alloy_primitives::{Address, U256}; use alloy_provider::Provider; use alloy_pubsub::Subscription; use alloy_rpc_types::{Block as AlloyBlock, Filter, TransactionRequest}; use alloy_serde::WithOtherFields; use alloy_sol_types::sol; use anvil::{NodeConfig, spawn}; use futures::StreamExt; #[tokio::test(flavor = "multi_thread")] async fn test_sub_new_heads() { let (api, handle) = spawn(NodeConfig::test()).await; let provider = connect_pubsub(&handle.ws_endpoint()).await; let blocks = provider.subscribe_blocks().await.unwrap(); // mine a block every 1 seconds api.anvil_set_interval_mining(1).unwrap(); let blocks = blocks.into_stream().take(3).collect::>().await; let block_numbers = blocks.into_iter().map(|b| b.number).collect::>(); assert_eq!(block_numbers, vec![1, 2, 3]); } sol!( #[sol(rpc)] EmitLogs, "test-data/emit_logs.json" ); // FIXME: Use .legacy() in tx when implemented in alloy #[tokio::test(flavor = "multi_thread")] async fn test_sub_logs_legacy() { let (_api, handle) = spawn(NodeConfig::test()).await; let wallet = handle.dev_wallets().next().unwrap(); let provider = connect_pubsub(&handle.ws_endpoint()).await; let msg = "First Message".to_string(); let contract_addr = EmitLogs::deploy_builder(provider.clone(), msg.clone()) .from(wallet.address()) .deploy() .await .unwrap(); let contract = EmitLogs::new(contract_addr, provider.clone()); let val = contract.getValue().call().await.unwrap(); assert_eq!(val, msg); // subscribe to events from the contract let filter = Filter::new().address(contract.address().to_owned()); let logs_sub = provider.subscribe_logs(&filter).await.unwrap(); // send a tx triggering an event // FIXME: Use .legacy() in tx let receipt = contract .setValue("Next Message".to_string()) .send() .await .unwrap() .get_receipt() .await .unwrap(); let mut logs_sub = logs_sub.into_stream(); // get the emitted event let log = logs_sub.next().await.unwrap(); // ensure the log in the receipt is the same as received via subscription stream assert_eq!(receipt.inner.logs()[0], log); } #[tokio::test(flavor = "multi_thread")] async fn test_sub_logs() { let (_api, handle) = spawn(NodeConfig::test()).await; let wallet = handle.dev_wallets().next().unwrap(); let provider = connect_pubsub(&handle.ws_endpoint()).await; let msg = "First Message".to_string(); let contract_addr = EmitLogs::deploy_builder(provider.clone(), msg.clone()) .from(wallet.address()) .deploy() .await .unwrap(); let contract = EmitLogs::new(contract_addr, provider.clone()); let val = contract.getValue().call().await.unwrap(); assert_eq!(val, msg); // subscribe to events from the contract let filter = Filter::new().address(contract.address().to_owned()); let logs_sub = provider.subscribe_logs(&filter).await.unwrap(); // send a tx triggering an event let receipt = contract .setValue("Next Message".to_string()) .send() .await .unwrap() .get_receipt() .await .unwrap(); let mut logs_sub = logs_sub.into_stream(); // get the emitted event let log = logs_sub.next().await.unwrap(); // ensure the log in the receipt is the same as received via subscription stream assert_eq!(receipt.inner.logs()[0], log); } #[tokio::test(flavor = "multi_thread")] async fn test_sub_logs_impersonated() { let (api, handle) = spawn(NodeConfig::test()).await; let wallet = handle.dev_wallets().next().unwrap(); let provider = connect_pubsub_with_wallet(&handle.ws_endpoint(), EthereumWallet::from(wallet.clone())) .await; // impersonate account let impersonate = Address::random(); let funding = U256::from(1e18 as u64); api.anvil_set_balance(impersonate, funding).await.unwrap(); api.anvil_impersonate_account(impersonate).await.unwrap(); let msg = "First Message".to_string(); let contract = EmitLogs::deploy(provider.clone(), msg.clone()).await.unwrap(); let _val = contract.getValue().call().await.unwrap(); // subscribe to events from the impersonated account let filter = Filter::new().address(contract.address().to_owned()); let logs_sub = provider.subscribe_logs(&filter).await.unwrap(); // send a tx triggering an event let data = contract.setValue("Next Message".to_string()); let data = data.calldata().clone(); let tx = TransactionRequest::default().from(impersonate).to(*contract.address()).with_input(data); let tx = WithOtherFields::new(tx); let provider = handle.http_provider(); let receipt = provider.send_transaction(tx).await.unwrap().get_receipt().await.unwrap(); let mut logs_sub = logs_sub.into_stream(); // get the emitted event let log = logs_sub.next().await.unwrap(); // ensure the log in the receipt is the same as received via subscription stream assert_eq!(receipt.inner.inner.logs()[0], log); } // FIXME: Use legacy() in tx when implemented in alloy #[tokio::test(flavor = "multi_thread")] async fn test_filters_legacy() { let (_api, handle) = spawn(NodeConfig::test()).await; let wallet = handle.dev_wallets().next().unwrap(); let provider = connect_pubsub_with_wallet(&handle.ws_endpoint(), EthereumWallet::from(wallet.clone())) .await; let from = wallet.address(); let msg = "First Message".to_string(); // FIXME: Use legacy() in tx when implemented in alloy let contract = EmitLogs::deploy(provider.clone(), msg.clone()).await.unwrap(); let stream = contract.ValueChanged_filter().subscribe().await.unwrap(); // send a tx triggering an event // FIXME: Use legacy() in tx when implemented in alloy let _receipt = contract .setValue("Next Message".to_string()) .send() .await .unwrap() .get_receipt() .await .unwrap(); let mut log = stream.into_stream(); // get the emitted event let (value_changed, _log) = log.next().await.unwrap().unwrap(); assert_eq!(value_changed.author, from); assert_eq!(value_changed.oldValue, "First Message".to_string()); assert_eq!(value_changed.newValue, "Next Message".to_string()); } #[tokio::test(flavor = "multi_thread")] async fn test_filters() { let (_api, handle) = spawn(NodeConfig::test()).await; let wallet = handle.dev_wallets().next().unwrap(); let provider = connect_pubsub_with_wallet(&handle.ws_endpoint(), EthereumWallet::from(wallet.clone())) .await; let from = wallet.address(); let msg = "First Message".to_string(); let contract = EmitLogs::deploy(provider.clone(), msg.clone()).await.unwrap(); let stream = contract.ValueChanged_filter().subscribe().await.unwrap(); // send a tx triggering an event let _receipt = contract .setValue("Next Message".to_string()) .send() .await .unwrap() .get_receipt() .await .unwrap(); let mut log = stream.into_stream(); // get the emitted event let (value_changed, _log) = log.next().await.unwrap().unwrap(); assert_eq!(value_changed.author, from); assert_eq!(value_changed.oldValue, "First Message".to_string()); assert_eq!(value_changed.newValue, "Next Message".to_string()); } #[tokio::test(flavor = "multi_thread")] async fn test_subscriptions() { let (_api, handle) = spawn(NodeConfig::test().with_blocktime(Some(std::time::Duration::from_secs(1)))).await; let provider = connect_pubsub(&handle.ws_endpoint()).await; let sub_id = provider.raw_request("eth_subscribe".into(), ["newHeads"]).await.unwrap(); let stream: Subscription = provider.get_subscription(sub_id).await.unwrap(); let blocks = stream .into_stream() .take(3) .collect::>() .await .into_iter() .map(|b| b.header.number) .collect::>(); assert_eq!(blocks, vec![1, 2, 3]) } #[expect(clippy::disallowed_macros)] #[tokio::test(flavor = "multi_thread")] async fn test_sub_new_heads_fast() { let (api, handle) = spawn(NodeConfig::test()).await; let provider = connect_pubsub(&handle.ws_endpoint()).await; let blocks = provider.subscribe_blocks().await.unwrap(); let mut blocks = blocks.into_stream(); let num = 1000u64; let mut block_numbers = Vec::new(); for _ in 0..num { api.mine_one().await; let block_number = blocks.next().await.unwrap().number; block_numbers.push(block_number); } println!("Collected {} blocks", block_numbers.len()); let numbers = (1..=num).collect::>(); assert_eq!(block_numbers, numbers); } ================================================ FILE: crates/anvil/tests/it/revert.rs ================================================ use crate::abi::VendingMachine; use alloy_network::TransactionBuilder; use alloy_primitives::{U256, bytes}; use alloy_provider::Provider; use alloy_rpc_types::TransactionRequest; use alloy_serde::WithOtherFields; use alloy_sol_types::sol; use anvil::{NodeConfig, spawn}; #[tokio::test(flavor = "multi_thread")] async fn test_deploy_reverting() { let (_api, handle) = spawn(NodeConfig::test()).await; let provider = handle.http_provider(); let sender = handle.dev_accounts().next().unwrap(); let code = bytes!("5f5ffd"); // PUSH0 PUSH0 REVERT let tx = TransactionRequest::default().from(sender).with_deploy_code(code); let tx = WithOtherFields::new(tx); // Calling/estimating gas fails early. let err = provider.call(tx.clone()).await.unwrap_err(); let s = err.to_string(); assert!(s.contains("execution reverted"), "{s:?}"); // Sending the transaction is successful but reverts on chain. let tx = provider.send_transaction(tx).await.unwrap(); let receipt = tx.get_receipt().await.unwrap(); assert!(!receipt.inner.inner.status()); } #[tokio::test(flavor = "multi_thread")] async fn test_revert_messages() { sol!( #[sol(rpc, bytecode = "608080604052346025575f80546001600160a01b031916600117905560b69081602a8239f35b5f80fdfe60808060405260043610156011575f80fd5b5f3560e01c635b9fdc30146023575f80fd5b34607c575f366003190112607c575f546001600160a01b03163303604c576020604051607b8152f35b62461bcd60e51b815260206004820152600b60248201526a08585d5d1a1bdc9a5e995960aa1b6044820152606490fd5b5f80fdfea2646970667358221220f593e5ccd46935f623185de62a72d9f1492d8d15075a111b0fa4d7e16acf4a7064736f6c63430008190033")] contract Contract { address private owner; constructor() { owner = address(1); } modifier onlyOwner() { require(msg.sender == owner, "!authorized"); _; } #[derive(Debug)] function getSecret() public onlyOwner view returns(uint256 secret) { return 123; } } ); let (_api, handle) = spawn(NodeConfig::test()).await; let provider = handle.http_provider(); let contract = Contract::deploy(&provider).await.unwrap(); let err = contract.getSecret().call().await.unwrap_err(); let s = err.to_string(); assert!(s.contains("!authorized"), "{s:?}"); } #[tokio::test(flavor = "multi_thread")] async fn test_solc_revert_example() { let (_api, handle) = spawn(NodeConfig::test()).await; let sender = handle.dev_accounts().next().unwrap(); let provider = handle.http_provider(); let contract = VendingMachine::deploy(&provider).await.unwrap(); let err = contract.buy(U256::from(100)).value(U256::from(1)).from(sender).call().await.unwrap_err(); let s = err.to_string(); assert!(s.contains("Not enough Ether provided."), "{s:?}"); } // #[tokio::test(flavor = "multi_thread")] async fn test_another_revert_message() { sol!( #[sol(rpc, bytecode = "6080806040523460135760d7908160188239f35b5f80fdfe60808060405260043610156011575f80fd5b5f3560e01c9081633fb5c1cb14604d5750638381f58a14602f575f80fd5b346049575f36600319011260495760205f54604051908152f35b5f80fd5b346049576020366003190112604957600435908115606a57505f55005b62461bcd60e51b81526020600482015260126024820152712932bb32b93a29ba3934b733a337b7a130b960711b6044820152606490fdfea2646970667358221220314bf8261cc467619137c071584f8d3bd8d9d97bf2846c138c0567040cf9828a64736f6c63430008190033")] contract Contract { uint256 public number; #[derive(Debug)] function setNumber(uint256 num) public { require(num != 0, "RevertStringFooBar"); number = num; } } ); let (_api, handle) = spawn(NodeConfig::test()).await; let provider = handle.http_provider(); let contract = Contract::deploy(&provider).await.unwrap(); let err = contract.setNumber(U256::from(0)).call().await.unwrap_err(); let s = err.to_string(); assert!(s.contains("RevertStringFooBar"), "{s:?}"); } #[tokio::test(flavor = "multi_thread")] async fn test_solc_revert_custom_errors() { sol!( #[sol(rpc, bytecode = "608080604052346013576081908160188239f35b5f80fdfe60808060405260043610156011575f80fd5b5f3560e01c63e57207e6146023575f80fd5b346047575f3660031901126047576373ea2a7f60e01b815260016004820152602490fd5b5f80fdfea26469706673582212202a8d69545801394af36c56ca229b52ae0b22d7b8f938b107dca8ebbf655464f764736f6c63430008190033")] contract Contract { error AddressRevert(address); #[derive(Debug)] function revertAddress() public { revert AddressRevert(address(1)); } } ); let (_api, handle) = spawn(NodeConfig::test()).await; let provider = handle.http_provider(); let contract = Contract::deploy(&provider).await.unwrap(); let err = contract.revertAddress().call().await.unwrap_err(); let s = err.to_string(); assert!(s.contains("execution reverted"), "{s:?}"); } ================================================ FILE: crates/anvil/tests/it/sign.rs ================================================ use crate::utils::http_provider_with_signer; use alloy_dyn_abi::TypedData; use alloy_network::EthereumWallet; use alloy_primitives::{Address, U256}; use alloy_provider::Provider; use alloy_rpc_types::TransactionRequest; use alloy_serde::WithOtherFields; use alloy_signer::Signer; use anvil::{NodeConfig, spawn}; #[tokio::test(flavor = "multi_thread")] async fn can_sign_typed_data() { let (api, _handle) = spawn(NodeConfig::test()).await; let json = serde_json::json!( { "types": { "EIP712Domain": [ { "name": "name", "type": "string" }, { "name": "version", "type": "string" }, { "name": "chainId", "type": "uint256" }, { "name": "verifyingContract", "type": "address" } ], "Person": [ { "name": "name", "type": "string" }, { "name": "wallet", "type": "address" } ], "Mail": [ { "name": "from", "type": "Person" }, { "name": "to", "type": "Person" }, { "name": "contents", "type": "string" } ] }, "primaryType": "Mail", "domain": { "name": "Ether Mail", "version": "1", "chainId": 1, "verifyingContract": "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC" }, "message": { "from": { "name": "Cow", "wallet": "0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826" }, "to": { "name": "Bob", "wallet": "0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB" }, "contents": "Hello, Bob!" } }); let typed_data: TypedData = serde_json::from_value(json).unwrap(); // `curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","method": "eth_signTypedData_v4", "params": ["0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", {"types":{"EIP712Domain":[{"name":"name","type":"string"},{"name":"version","type":"string"},{"name":"chainId","type":"uint256"},{"name":"verifyingContract","type":"address"}],"Person":[{"name":"name","type":"string"},{"name":"wallet","type":"address"}],"Mail":[{"name":"from","type":"Person"},{"name":"to","type":"Person"},{"name":"contents","type":"string"}]},"primaryType":"Mail","domain":{"name":"Ether Mail","version":"1","chainId":1,"verifyingContract":"0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC"},"message":{"from":{"name":"Cow","wallet":"0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826"},"to":{"name":"Bob","wallet":"0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB"},"contents":"Hello, Bob!"}}],"id":67}' http://localhost:8545` let signature = api .sign_typed_data_v4( "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266".parse().unwrap(), &typed_data, ) .await .unwrap(); assert_eq!( signature, "0x6ea8bb309a3401225701f3565e32519f94a0ea91a5910ce9229fe488e773584c0390416a2190d9560219dab757ecca2029e63fa9d1c2aebf676cc25b9f03126a1b".to_string() ); } // #[tokio::test(flavor = "multi_thread")] async fn can_sign_typed_data_os() { let (api, _handle) = spawn(NodeConfig::test()).await; let json = serde_json::json!( { "types": { "EIP712Domain": [ { "name": "name", "type": "string" }, { "name": "version", "type": "string" }, { "name": "chainId", "type": "uint256" }, { "name": "verifyingContract", "type": "address" } ], "OrderComponents": [ { "name": "offerer", "type": "address" }, { "name": "zone", "type": "address" }, { "name": "offer", "type": "OfferItem[]" }, { "name": "consideration", "type": "ConsiderationItem[]" }, { "name": "orderType", "type": "uint8" }, { "name": "startTime", "type": "uint256" }, { "name": "endTime", "type": "uint256" }, { "name": "zoneHash", "type": "bytes32" }, { "name": "salt", "type": "uint256" }, { "name": "conduitKey", "type": "bytes32" }, { "name": "counter", "type": "uint256" } ], "OfferItem": [ { "name": "itemType", "type": "uint8" }, { "name": "token", "type": "address" }, { "name": "identifierOrCriteria", "type": "uint256" }, { "name": "startAmount", "type": "uint256" }, { "name": "endAmount", "type": "uint256" } ], "ConsiderationItem": [ { "name": "itemType", "type": "uint8" }, { "name": "token", "type": "address" }, { "name": "identifierOrCriteria", "type": "uint256" }, { "name": "startAmount", "type": "uint256" }, { "name": "endAmount", "type": "uint256" }, { "name": "recipient", "type": "address" } ] }, "primaryType": "OrderComponents", "domain": { "name": "Seaport", "version": "1.1", "chainId": "1", "verifyingContract": "0x00000000006c3852cbEf3e08E8dF289169EdE581" }, "message": { "offerer": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", "offer": [ { "itemType": "3", "token": "0xA604060890923Ff400e8c6f5290461A83AEDACec", "identifierOrCriteria": "110194434039389003190498847789203126033799499726478230611233094448886344768909", "startAmount": "1", "endAmount": "1" } ], "consideration": [ { "itemType": "0", "token": "0x0000000000000000000000000000000000000000", "identifierOrCriteria": "0", "startAmount": "487500000000000000", "endAmount": "487500000000000000", "recipient": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" }, { "itemType": "0", "token": "0x0000000000000000000000000000000000000000", "identifierOrCriteria": "0", "startAmount": "12500000000000000", "endAmount": "12500000000000000", "recipient": "0x8De9C5A032463C561423387a9648c5C7BCC5BC90" } ], "startTime": "1658645591", "endTime": "1659250386", "orderType": "3", "zone": "0x004C00500000aD104D7DBd00e3ae0A5C00560C00", "zoneHash": "0x0000000000000000000000000000000000000000000000000000000000000000", "salt": "16178208897136618", "conduitKey": "0x0000007b02230091a7ed01230072f7006a004d60a8d4e71d599b8104250f0000", "totalOriginalConsiderationItems": "2", "counter": "0" } } ); let typed_data: TypedData = serde_json::from_value(json).unwrap(); // `curl -X POST http://localhost:8545 -d '{"jsonrpc": "2.0", "method": "eth_signTypedData_v4", "params": ["0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266", {"types":{"EIP712Domain":[{"name":"name","type":"string"},{"name":"version","type":"string"},{"name":"chainId","type":"uint256"},{"name":"verifyingContract","type":"address"}],"OrderComponents":[{"name":"offerer","type":"address"},{"name":"zone","type":"address"},{"name":"offer","type":"OfferItem[]"},{"name":"consideration","type":"ConsiderationItem[]"},{"name":"orderType","type":"uint8"},{"name":"startTime","type":"uint256"},{"name":"endTime","type":"uint256"},{"name":"zoneHash","type":"bytes32"},{"name":"salt","type":"uint256"},{"name":"conduitKey","type":"bytes32"},{"name":"counter","type":"uint256"}],"OfferItem":[{"name":"itemType","type":"uint8"},{"name":"token","type":"address"},{"name":"identifierOrCriteria","type":"uint256"},{"name":"startAmount","type":"uint256"},{"name":"endAmount","type":"uint256"}],"ConsiderationItem":[{"name":"itemType","type":"uint8"},{"name":"token","type":"address"},{"name":"identifierOrCriteria","type":"uint256"},{"name":"startAmount","type":"uint256"},{"name":"endAmount","type":"uint256"},{"name":"recipient","type":"address"}]},"primaryType":"OrderComponents","domain":{"name":"Seaport","version":"1.1","chainId":"1","verifyingContract":"0x00000000006c3852cbEf3e08E8dF289169EdE581"},"message":{"offerer":"0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266","offer":[{"itemType":"3","token":"0xA604060890923Ff400e8c6f5290461A83AEDACec","identifierOrCriteria":"110194434039389003190498847789203126033799499726478230611233094448886344768909","startAmount":"1","endAmount":"1"}],"consideration":[{"itemType":"0","token":"0x0000000000000000000000000000000000000000","identifierOrCriteria":"0","startAmount":"487500000000000000","endAmount":"487500000000000000","recipient":"0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266"},{"itemType":"0","token":"0x0000000000000000000000000000000000000000","identifierOrCriteria":"0","startAmount":"12500000000000000","endAmount":"12500000000000000","recipient":"0x8De9C5A032463C561423387a9648c5C7BCC5BC90"}],"startTime":"1658645591","endTime":"1659250386","orderType":"3","zone":"0x004C00500000aD104D7DBd00e3ae0A5C00560C00","zoneHash":"0x0000000000000000000000000000000000000000000000000000000000000000","salt":"16178208897136618","conduitKey":"0x0000007b02230091a7ed01230072f7006a004d60a8d4e71d599b8104250f0000","totalOriginalConsiderationItems":"2","counter":"0"}}], "id": "1"}' -H "Content-Type: application/json"` let signature = api .sign_typed_data_v4( "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266".parse().unwrap(), &typed_data, ) .await .unwrap(); assert_eq!( signature, "0xedb0fa55ac67e3ca52b6bd6ee3576b193731adc2aff42151f67826932fa9f6191261ebdecc2c650204ff7625752b033293fb67ef5cfca78e16de359200040b761b".to_string() ); } #[tokio::test(flavor = "multi_thread")] async fn can_sign_transaction() { let (api, handle) = spawn(NodeConfig::test()).await; let accounts = handle.dev_wallets().collect::>(); let from = accounts[0].address(); let to = accounts[1].address(); // craft the tx // specify the `from` field so that the client knows which account to use let tx = TransactionRequest::default() .nonce(10) .max_fee_per_gas(100) .max_priority_fee_per_gas(101) .to(to) .value(U256::from(1001u64)) .from(from); let tx = WithOtherFields::new(tx); // sign it via the eth_signTransaction API let signed_tx = api.sign_transaction(tx).await.unwrap(); assert_eq!( signed_tx, "0x02f866827a690a65648252089470997970c51812dc3a010c7d01b50e0d17dc79c88203e980c001a0e4de88aefcf87ccb04466e60de66a83192e46aa26177d5ea35efbfd43fd0ecdca00e3148e0e8e0b9a6f9b329efd6e30c4a461920f3a27497be3dbefaba996601da" ); } #[tokio::test(flavor = "multi_thread")] async fn rejects_different_chain_id() { let (_api, handle) = spawn(NodeConfig::test()).await; let wallet = handle.dev_wallets().next().unwrap().with_chain_id(Some(1)); let provider = http_provider_with_signer(&handle.http_endpoint(), EthereumWallet::from(wallet)); let tx = TransactionRequest::default().to(Address::random()).value(U256::from(100)); let tx = WithOtherFields::new(tx); let res = provider.send_transaction(tx).await; let err = res.unwrap_err(); assert!(err.to_string().contains("does not match the signer's"), "{}", err.to_string()); } #[tokio::test(flavor = "multi_thread")] async fn rejects_invalid_chain_id() { let (_api, handle) = spawn(NodeConfig::test()).await; let wallet = handle.dev_wallets().next().unwrap(); let wallet = wallet.with_chain_id(Some(99u64)); let provider = http_provider_with_signer(&handle.http_endpoint(), EthereumWallet::from(wallet)); let tx = TransactionRequest::default().to(Address::random()).value(U256::from(100u64)); let tx = WithOtherFields::new(tx); let res = provider.send_transaction(tx).await; let _err = res.unwrap_err(); } // #[tokio::test(flavor = "multi_thread")] async fn can_sign_typed_seaport_data() { let (api, _handle) = spawn(NodeConfig::test()).await; let json = serde_json::json!( { "types": { "EIP712Domain": [ { "name": "name", "type": "string" }, { "name": "version", "type": "string" }, { "name": "chainId", "type": "uint256" }, { "name": "verifyingContract", "type": "address" } ], "OrderComponents": [ { "name": "offerer", "type": "address" }, { "name": "zone", "type": "address" }, { "name": "offer", "type": "OfferItem[]" }, { "name": "consideration", "type": "ConsiderationItem[]" }, { "name": "orderType", "type": "uint8" }, { "name": "startTime", "type": "uint256" }, { "name": "endTime", "type": "uint256" }, { "name": "zoneHash", "type": "bytes32" }, { "name": "salt", "type": "uint256" }, { "name": "conduitKey", "type": "bytes32" }, { "name": "counter", "type": "uint256" } ], "OfferItem": [ { "name": "itemType", "type": "uint8" }, { "name": "token", "type": "address" }, { "name": "identifierOrCriteria", "type": "uint256" }, { "name": "startAmount", "type": "uint256" }, { "name": "endAmount", "type": "uint256" } ], "ConsiderationItem": [ { "name": "itemType", "type": "uint8" }, { "name": "token", "type": "address" }, { "name": "identifierOrCriteria", "type": "uint256" }, { "name": "startAmount", "type": "uint256" }, { "name": "endAmount", "type": "uint256" }, { "name": "recipient", "type": "address" } ] }, "primaryType": "OrderComponents", "domain": { "name": "Seaport", "version": "1.1", "chainId": "137", "verifyingContract": "0x00000000006c3852cbEf3e08E8dF289169EdE581" }, "message": { "offerer": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", "offer": [ { "itemType": "3", "token": "0xA604060890923Ff400e8c6f5290461A83AEDACec", "identifierOrCriteria": "110194434039389003190498847789203126033799499726478230611233094448886344768909", "startAmount": "1", "endAmount": "1" } ], "consideration": [ { "itemType": "0", "token": "0x0000000000000000000000000000000000000000", "identifierOrCriteria": "0", "startAmount": "487500000000000000", "endAmount": "487500000000000000", "recipient": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" }, { "itemType": "0", "token": "0x0000000000000000000000000000000000000000", "identifierOrCriteria": "0", "startAmount": "12500000000000000", "endAmount": "12500000000000000", "recipient": "0x8De9C5A032463C561423387a9648c5C7BCC5BC90" } ], "startTime": "1658645591", "endTime": "1659250386", "orderType": "3", "zone": "0x004C00500000aD104D7DBd00e3ae0A5C00560C00", "zoneHash": "0x0000000000000000000000000000000000000000000000000000000000000000", "salt": "16178208897136618", "conduitKey": "0x0000007b02230091a7ed01230072f7006a004d60a8d4e71d599b8104250f0000", "totalOriginalConsiderationItems": "2", "counter": "0" } } ); let typed_data: TypedData = serde_json::from_value(json).unwrap(); // `curl -X POST http://localhost:8545 -d '{"jsonrpc": "2.0", "method": "eth_signTypedData_v4", "params": ["0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266", "{\"types\":{\"EIP712Domain\":[{\"name\":\"name\",\"type\":\"string\"},{\"name\":\"version\",\"type\":\"string\"},{\"name\":\"chainId\",\"type\":\"uint256\"},{\"name\":\"verifyingContract\",\"type\":\"address\"}],\"OrderComponents\":[{\"name\":\"offerer\",\"type\":\"address\"},{\"name\":\"zone\",\"type\":\"address\"},{\"name\":\"offer\",\"type\":\"OfferItem[]\"},{\"name\":\"consideration\",\"type\":\"ConsiderationItem[]\"},{\"name\":\"orderType\",\"type\":\"uint8\"},{\"name\":\"startTime\",\"type\":\"uint256\"},{\"name\":\"endTime\",\"type\":\"uint256\"},{\"name\":\"zoneHash\",\"type\":\"bytes32\"},{\"name\":\"salt\",\"type\":\"uint256\"},{\"name\":\"conduitKey\",\"type\":\"bytes32\"},{\"name\":\"counter\",\"type\":\"uint256\"}],\"OfferItem\":[{\"name\":\"itemType\",\"type\":\"uint8\"},{\"name\":\"token\",\"type\":\"address\"},{\"name\":\"identifierOrCriteria\",\"type\":\"uint256\"},{\"name\":\"startAmount\",\"type\":\"uint256\"},{\"name\":\"endAmount\",\"type\":\"uint256\"}],\"ConsiderationItem\":[{\"name\":\"itemType\",\"type\":\"uint8\"},{\"name\":\"token\",\"type\":\"address\"},{\"name\":\"identifierOrCriteria\",\"type\":\"uint256\"},{\"name\":\"startAmount\",\"type\":\"uint256\"},{\"name\":\"endAmount\",\"type\":\"uint256\"},{\"name\":\"recipient\",\"type\":\"address\"}]},\"primaryType\":\"OrderComponents\",\"domain\":{\"name\":\"Seaport\",\"version\":\"1.1\",\"chainId\":\"137\",\"verifyingContract\":\"0x00000000006c3852cbEf3e08E8dF289169EdE581\"},\"message\":{\"offerer\":\"0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266\",\"offer\":[{\"itemType\":\"3\",\"token\":\"0xA604060890923Ff400e8c6f5290461A83AEDACec\",\"identifierOrCriteria\":\"110194434039389003190498847789203126033799499726478230611233094448886344768909\",\"startAmount\":\"1\",\"endAmount\":\"1\"}],\"consideration\":[{\"itemType\":\"0\",\"token\":\"0x0000000000000000000000000000000000000000\",\"identifierOrCriteria\":\"0\",\"startAmount\":\"487500000000000000\",\"endAmount\":\"487500000000000000\",\"recipient\":\"0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266\"},{\"itemType\":\"0\",\"token\":\"0x0000000000000000000000000000000000000000\",\"identifierOrCriteria\":\"0\",\"startAmount\":\"12500000000000000\",\"endAmount\":\"12500000000000000\",\"recipient\":\"0x8De9C5A032463C561423387a9648c5C7BCC5BC90\"}],\"startTime\":\"1658645591\",\"endTime\":\"1659250386\",\"orderType\":\"3\",\"zone\":\"0x004C00500000aD104D7DBd00e3ae0A5C00560C00\",\"zoneHash\":\"0x0000000000000000000000000000000000000000000000000000000000000000\",\"salt\":\"16178208897136618\",\"conduitKey\":\"0x0000007b02230091a7ed01230072f7006a004d60a8d4e71d599b8104250f0000\",\"totalOriginalConsiderationItems\":\"2\",\"counter\":\"0\"}}"], "id": "1"}' -H "Content-Type: application/json"` let signature = api .sign_typed_data_v4( "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266".parse().unwrap(), &typed_data, ) .await .unwrap(); assert_eq!( signature, "0xed9afe7f377155ee3a42b25b696d79b55d441aeac7790b97a51b54ad0569b9665ea30bf8e8df12d6ee801c4dcb85ecfb8b23a6f7ae166d5af9acac9befb905451c".to_string() ); } ================================================ FILE: crates/anvil/tests/it/simulate.rs ================================================ //! general eth api tests use alloy_primitives::{TxKind, U256, address}; use alloy_rpc_types::{ BlockOverrides, request::TransactionRequest, simulate::{SimBlock, SimulatePayload}, state::{AccountOverride, StateOverridesBuilder}, }; use anvil::{NodeConfig, spawn}; use foundry_test_utils::rpc; #[tokio::test(flavor = "multi_thread")] async fn test_fork_simulate_v1() { crate::init_tracing(); let (api, _) = spawn(NodeConfig::test().with_eth_rpc_url(Some(rpc::next_http_archive_rpc_url()))).await; let block_overrides = Some(BlockOverrides { base_fee: Some(U256::from(9)), ..Default::default() }); let account_override = AccountOverride { balance: Some(U256::from(999999999999u64)), ..Default::default() }; let state_overrides = Some( StateOverridesBuilder::with_capacity(1) .append(address!("0xc000000000000000000000000000000000000001"), account_override) .build(), ); let tx_request = TransactionRequest { from: Some(address!("0xc000000000000000000000000000000000000001")), to: Some(TxKind::from(address!("0xc000000000000000000000000000000000000001"))), value: Some(U256::from(1)), ..Default::default() }; let payload = SimulatePayload { block_state_calls: vec![SimBlock { block_overrides, state_overrides, calls: vec![tx_request], }], trace_transfers: true, validation: false, return_full_transactions: true, }; let _res = api.simulate_v1(payload, None).await.unwrap(); } ================================================ FILE: crates/anvil/tests/it/state.rs ================================================ //! general eth api tests use crate::abi::Greeter; use alloy_network::{ReceiptResponse, TransactionBuilder}; use alloy_primitives::{Bytes, U256, Uint, address, b256, utils::Unit}; use alloy_provider::Provider; use alloy_rpc_types::{BlockId, TransactionRequest}; use alloy_serde::WithOtherFields; use anvil::{NodeConfig, eth::backend::db::SerializableState, spawn}; use foundry_test_utils::rpc::next_http_archive_rpc_url; use revm::{ context_interface::block::BlobExcessGasAndPrice, primitives::eip4844::BLOB_BASE_FEE_UPDATE_FRACTION_PRAGUE, }; use serde_json::json; use std::str::FromStr; #[tokio::test(flavor = "multi_thread")] async fn can_load_state() { let tmp = tempfile::tempdir().unwrap(); let state_file = tmp.path().join("state.json"); let (api, _handle) = spawn(NodeConfig::test()).await; api.mine_one().await; api.mine_one().await; let num = api.block_number().unwrap(); let state = api.serialized_state(false).await.unwrap(); foundry_common::fs::write_json_file(&state_file, &state).unwrap(); let (api, _handle) = spawn(NodeConfig::test().with_init_state_path(state_file)).await; let num2 = api.block_number().unwrap(); // Ref: https://github.com/foundry-rs/foundry/issues/9017 // Check responses of eth_blockNumber and eth_getBlockByNumber don't deviate after loading state let num_from_tag = api .block_by_number(alloy_eips::BlockNumberOrTag::Latest) .await .unwrap() .unwrap() .header .number; assert_eq!(num, num2); assert_eq!(num, U256::from(num_from_tag)); } // #[tokio::test(flavor = "multi_thread")] async fn finalized_block_hash_consistent_after_load_state() { use alloy_eips::BlockNumberOrTag; let tmp = tempfile::tempdir().unwrap(); let state_file = tmp.path().join("state.json"); let (api, _handle) = spawn(NodeConfig::test()).await; api.mine_one().await; // Get the original genesis block hash let original_genesis = api.block_by_number(BlockNumberOrTag::Number(0)).await.unwrap().unwrap(); let original_genesis_hash = original_genesis.header.hash; let state = api.serialized_state(false).await.unwrap(); foundry_common::fs::write_json_file(&state_file, &state).unwrap(); // Load state with a different genesis timestamp. // The new instance will create its own genesis block with a different timestamp, // but then load_state should overwrite it. The bug is that genesis_hash field isn't updated. let (api, _handle) = spawn( NodeConfig::test() .with_genesis_timestamp(Some(original_genesis.header.timestamp + 1000)) .with_init_state_path(state_file), ) .await; // Query finalized block - should return genesis (block 0) since best_number is small let finalized_block = api.block_by_number(BlockNumberOrTag::Finalized).await.unwrap().unwrap(); let finalized_hash = finalized_block.header.hash; let finalized_number = finalized_block.header.number; // Query block by the finalized block's number directly let block_by_number = api.block_by_number(BlockNumberOrTag::Number(finalized_number)).await.unwrap().unwrap(); let block_by_number_hash = block_by_number.header.hash; // Verify the loaded genesis matches the original assert_eq!( block_by_number_hash, original_genesis_hash, "Loaded genesis should match original genesis hash" ); // Both finalized and block 0 should return the same hash assert_eq!( finalized_hash, block_by_number_hash, "Finalized block hash should match block queried by number" ); // Also verify Earliest block tag returns consistent hash let earliest_block = api.block_by_number(BlockNumberOrTag::Earliest).await.unwrap().unwrap(); assert_eq!( earliest_block.header.hash, original_genesis_hash, "Earliest block hash should match original genesis hash" ); } #[tokio::test(flavor = "multi_thread")] async fn can_load_existing_state_legacy() { let state_file = "test-data/state-dump-legacy.json"; let (api, _handle) = spawn(NodeConfig::test().with_init_state_path(state_file)).await; let block_number = api.block_number().unwrap(); assert_eq!(block_number, Uint::from(2)); } #[tokio::test(flavor = "multi_thread")] async fn can_load_existing_state_legacy_stress() { let state_file = "test-data/state-dump-legacy-stress.json"; let (api, _handle) = spawn(NodeConfig::test().with_init_state_path(state_file)).await; let block_number = api.block_number().unwrap(); assert_eq!(block_number, Uint::from(5)); } #[tokio::test(flavor = "multi_thread")] async fn can_load_existing_state() { let state_file = "test-data/state-dump.json"; let (api, _handle) = spawn(NodeConfig::test().with_init_state_path(state_file)).await; let block_number = api.block_number().unwrap(); assert_eq!(block_number, Uint::from(2)); } #[tokio::test(flavor = "multi_thread")] async fn test_make_sure_historical_state_is_not_cleared_on_dump() { let tmp = tempfile::tempdir().unwrap(); let state_file = tmp.path().join("state.json"); let (api, handle) = spawn(NodeConfig::test()).await; let provider = handle.http_provider(); let greeter = Greeter::deploy(&provider, "Hello".to_string()).await.unwrap(); let address = greeter.address(); let _tx = greeter .setGreeting("World!".to_string()) .send() .await .unwrap() .get_receipt() .await .unwrap(); api.mine_one().await; let ser_state = api.serialized_state(true).await.unwrap(); foundry_common::fs::write_json_file(&state_file, &ser_state).unwrap(); let block_number = api.block_number().unwrap(); assert_eq!(block_number, Uint::from(3)); // Makes sure historical states of the new instance are not cleared. let code = provider.get_code_at(*address).block_id(BlockId::number(2)).await.unwrap(); assert_ne!(code, Bytes::new()); } #[tokio::test(flavor = "multi_thread")] async fn can_preserve_historical_states_between_dump_and_load() { let tmp = tempfile::tempdir().unwrap(); let state_file = tmp.path().join("state.json"); let (api, handle) = spawn(NodeConfig::test()).await; let provider = handle.http_provider(); let greeter = Greeter::deploy(&provider, "Hello".to_string()).await.unwrap(); let address = greeter.address(); let deploy_blk_num = provider.get_block_number().await.unwrap(); let tx = greeter .setGreeting("World!".to_string()) .send() .await .unwrap() .get_receipt() .await .unwrap(); let change_greeting_blk_num = tx.block_number.unwrap(); api.mine_one().await; 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 block_number = api.block_number().unwrap(); assert_eq!(block_number, Uint::from(3)); let provider = handle.http_provider(); let greeter = Greeter::new(*address, provider); let greeting_at_init = 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(); assert_eq!(greeting_after_change, "World!"); } // #[tokio::test(flavor = "multi_thread")] async fn test_fork_load_state() { let (api, handle) = spawn( NodeConfig::test() .with_eth_rpc_url(Some(next_http_archive_rpc_url())) .with_fork_block_number(Some(21070682u64)), ) .await; let bob = address!("0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266"); let alice = address!("0x9276449EaC5b4f7Bc17cFC6700f7BeeB86F9bCd0"); let provider = handle.http_provider(); let init_nonce_bob = provider.get_transaction_count(bob).await.unwrap(); let init_balance_alice = provider.get_balance(alice).await.unwrap(); 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(); assert!(receipt.status()); let serialized_state = api.serialized_state(false).await.unwrap(); let state_dump_block = api.block_number().unwrap(); let (api, handle) = spawn( NodeConfig::test() .with_eth_rpc_url(Some(next_http_archive_rpc_url())) .with_fork_block_number(Some(21070686u64)) // Forked chain has moved forward .with_init_state(Some(serialized_state)), ) .await; // Ensure the initial block number is the fork_block_number and not the state_dump_block let block_number = api.block_number().unwrap(); assert_eq!(block_number, U256::from(21070686u64)); assert_ne!(block_number, state_dump_block); let provider = handle.http_provider(); let restart_nonce_bob = provider.get_transaction_count(bob).await.unwrap(); let restart_balance_alice = provider.get_balance(alice).await.unwrap(); assert_eq!(init_nonce_bob + 1, restart_nonce_bob); assert_eq!(init_balance_alice + value, restart_balance_alice); // Send another tx to check if the state is preserved 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(); assert!(receipt.status()); let nonce_bob = provider.get_transaction_count(bob).await.unwrap(); let balance_alice = provider.get_balance(alice).await.unwrap(); let tx = TransactionRequest::default() .with_to(alice) .with_value(value) .with_from(bob) .with_nonce(nonce_bob); let tx = WithOtherFields::new(tx); let receipt = provider.send_transaction(tx).await.unwrap().get_receipt().await.unwrap(); assert!(receipt.status()); let latest_nonce_bob = provider.get_transaction_count(bob).await.unwrap(); let latest_balance_alice = provider.get_balance(alice).await.unwrap(); assert_eq!(nonce_bob + 1, latest_nonce_bob); assert_eq!(balance_alice + value, latest_balance_alice); } // #[tokio::test(flavor = "multi_thread")] async fn test_fork_load_state_with_greater_state_block() { let (api, _handle) = spawn( NodeConfig::test() .with_eth_rpc_url(Some(next_http_archive_rpc_url())) .with_fork_block_number(Some(21070682u64)), ) .await; api.mine_one().await; let block_number = api.block_number().unwrap(); let serialized_state = api.serialized_state(false).await.unwrap(); assert_eq!(serialized_state.best_block_number, Some(block_number.to::())); let (api, _handle) = spawn( NodeConfig::test() .with_eth_rpc_url(Some(next_http_archive_rpc_url())) .with_fork_block_number(Some(21070682u64)) // Forked chain has moved forward .with_init_state(Some(serialized_state)), ) .await; let new_block_number = api.block_number().unwrap(); 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); } // #[tokio::test(flavor = "multi_thread")] async fn test_backward_compatibility_deserialization_v1_2() { let old_format = r#"{ "block": { "number": "0x5", "coinbase": "0x1234567890123456789012345678901234567890", "timestamp": "0x688c83b5", "gas_limit": "0x1c9c380", "basefee": "0x3b9aca00", "difficulty": "0x0", "prevrandao": "0xecc5f0af8ff6b65c14bfdac55ba9db870d89482eb2b87200c6d7e7cd3a3a5ad5", "blob_excess_gas_and_price": { "excess_blob_gas": 0, "blob_gasprice": 1 } }, "accounts": {}, "best_block_number": "0x5", "blocks": [], "transactions": [] }"#; let state: SerializableState = serde_json::from_str(old_format).unwrap(); assert!(state.block.is_some()); let block_env = state.block.unwrap(); assert_eq!(block_env.number, U256::from(5)); // Verify coinbase was converted to beneficiary assert_eq!(block_env.beneficiary, address!("0x1234567890123456789012345678901234567890")); // New format with beneficiary and numeric values let new_format = r#"{ "block": { "number": 6, "beneficiary": "0x1234567890123456789012345678901234567891", "timestamp": 1751619509, "gas_limit": 30000000, "basefee": 1000000000, "difficulty": "0x0", "prevrandao": "0xecc5f0af8ff6b65c14bfdac55ba9db870d89482eb2b87200c6d7e7cd3a3a5ad5", "blob_excess_gas_and_price": { "excess_blob_gas": 0, "blob_gasprice": 1 } }, "accounts": {}, "best_block_number": 6, "blocks": [], "transactions": [] }"#; let state: SerializableState = serde_json::from_str(new_format).unwrap(); assert!(state.block.is_some()); let block_env = state.block.unwrap(); assert_eq!(block_env.number, U256::from(6)); assert_eq!(block_env.beneficiary, address!("0x1234567890123456789012345678901234567891")); } // #[tokio::test(flavor = "multi_thread")] async fn test_backward_compatibility_mixed_formats_deserialization_v1_2() { let mixed_format = json!({ "block": { "number": "0x3", "coinbase": "0x1111111111111111111111111111111111111111", "timestamp": 1751619509, "gas_limit": "0x1c9c380", "basefee": 1000000000, "difficulty": "0x0", "prevrandao": "0xecc5f0af8ff6b65c14bfdac55ba9db870d89482eb2b87200c6d7e7cd3a3a5ad5", "blob_excess_gas_and_price": { "excess_blob_gas": 0, "blob_gasprice": 1 } }, "accounts": {}, "best_block_number": 3, "blocks": [], "transactions": [] }); let state: SerializableState = serde_json::from_str(&mixed_format.to_string()).unwrap(); let block_env = state.block.unwrap(); assert_eq!(block_env.number, U256::from(3)); assert_eq!(block_env.beneficiary, address!("0x1111111111111111111111111111111111111111")); assert_eq!(block_env.timestamp, U256::from(1751619509)); assert_eq!(block_env.gas_limit, 0x1c9c380); assert_eq!(block_env.basefee, 1_000_000_000); assert_eq!(block_env.difficulty, U256::ZERO); assert_eq!( block_env.prevrandao.unwrap(), b256!("ecc5f0af8ff6b65c14bfdac55ba9db870d89482eb2b87200c6d7e7cd3a3a5ad5") ); let blob = block_env.blob_excess_gas_and_price.unwrap(); assert_eq!(blob.excess_blob_gas, 0); assert_eq!(blob.blob_gasprice, 1); assert_eq!(state.best_block_number, Some(3)); } // #[tokio::test(flavor = "multi_thread")] async fn test_backward_compatibility_optional_fields_deserialization_v1_2() { let partial_old_format = json!({ "block": { "number": "0x1", "coinbase": "0x0000000000000000000000000000000000000000", "timestamp": "0x688c83b5", "gas_limit": "0x1c9c380", "basefee": "0x3b9aca00", "difficulty": "0x0", "prevrandao": "0xecc5f0af8ff6b65c14bfdac55ba9db870d89482eb2b87200c6d7e7cd3a3a5ad5" // Missing blob_excess_gas_and_price - should be None }, "accounts": {}, "best_block_number": "0x1" // Missing blocks and transactions arrays - should default to empty }); let state: SerializableState = serde_json::from_str(&partial_old_format.to_string()).unwrap(); let block_env = state.block.unwrap(); assert_eq!(block_env.number, U256::from(1)); assert_eq!(block_env.beneficiary, address!("0x0000000000000000000000000000000000000000")); assert_eq!(block_env.timestamp, U256::from(0x688c83b5)); assert_eq!(block_env.gas_limit, 0x1c9c380); assert_eq!(block_env.basefee, 0x3b9aca00); assert_eq!(block_env.difficulty, U256::ZERO); assert_eq!( block_env.prevrandao.unwrap(), b256!("ecc5f0af8ff6b65c14bfdac55ba9db870d89482eb2b87200c6d7e7cd3a3a5ad5") ); assert_eq!( block_env.blob_excess_gas_and_price, Some(BlobExcessGasAndPrice::new(0, BLOB_BASE_FEE_UPDATE_FRACTION_PRAGUE)) ); assert_eq!(state.best_block_number, Some(1)); assert!(state.blocks.is_empty()); assert!(state.transactions.is_empty()); } // #[tokio::test(flavor = "multi_thread")] async fn test_backward_compatibility_state_dump_deserialization_v1_2() { let tmp = tempfile::tempdir().unwrap(); let old_state_file = tmp.path().join("old_state.json"); // A simple state dump with a single block containing one transaction of a Counter contract // deployment. let old_state_json = json!({ "block": { "number": "0x1", "coinbase": "0x0000000000000000000000000000000000000001", "timestamp": "0x688c83b5", "gas_limit": "0x1c9c380", "basefee": "0x3b9aca00", "difficulty": "0x0", "prevrandao": "0xecc5f0af8ff6b65c14bfdac55ba9db870d89482eb2b87200c6d7e7cd3a3a5ad5", "blob_excess_gas_and_price": { "excess_blob_gas": 0, "blob_gasprice": 1 } }, "accounts": { "0x0000000000000000000000000000000000000000": { "nonce": 0, "balance": "0x26481", "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": {} }, "0x5fbdb2315678afecb367f032d93f642f64180aa3": { "nonce": 1, "balance": "0x0", "code": "0x608060405234801561000f575f5ffd5b506004361061003f575f3560e01c80633fb5c1cb146100435780638381f58a1461005f578063d09de08a1461007d575b5f5ffd5b61005d600480360381019061005891906100e4565b610087565b005b610067610090565b604051610074919061011e565b60405180910390f35b610085610095565b005b805f8190555050565b5f5481565b5f5f8154809291906100a690610164565b9190505550565b5f5ffd5b5f819050919050565b6100c3816100b1565b81146100cd575f5ffd5b50565b5f813590506100de816100ba565b92915050565b5f602082840312156100f9576100f86100ad565b5b5f610106848285016100d0565b91505092915050565b610118816100b1565b82525050565b5f6020820190506101315f83018461010f565b92915050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b5f61016e826100b1565b91507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff82036101a05761019f610137565b5b60018201905091905056fea264697066735822122040b6a3cd3ec8f890002f39a8719ebee029ba9bac3d7fa9d581d4712cfe9ffec264736f6c634300081e0033", "storage": {} }, "0x70997970c51812dc3a010c7d01b50e0d17dc79c8": { "nonce": 0, "balance": "0x21e19e0c9bab2400000", "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": "0x21e19e03b1e9e55d17f", "code": "0x", "storage": {} } }, "best_block_number": "0x1", "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": "0x688c83b0", "extraData": "0x", "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000", "nonce": "0x0000000000000000", "baseFeePerGas": "0x3b9aca00", "withdrawalsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", "blobGasUsed": "0x0", "excessBlobGas": "0x0", "parentBeaconBlockRoot": "0x0000000000000000000000000000000000000000000000000000000000000000" }, "transactions": [], "ommers": [] }, { "header": { "parentHash": "0x25097583380d90c4ac42b454ed7d2f59450ed3a16fdcf7f7bd93295aa126a901", "sha3Uncles": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", "miner": "0x0000000000000000000000000000000000000000", "stateRoot": "0x6e005b459ac9acefa5f47fd2d7ff8ca81a91794fdc5f7fbc3e2faeeaefe5d516", "transactionsRoot": "0x59f0457ec18e2181c186f49d9ac911b33b5f4f55db5c494022147346bcfc9837", "receiptsRoot": "0x88ac48b910f796aab7407814203b3a15a04a812f387e92efeccc92a2ecf809da", "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "difficulty": "0x0", "number": "0x1", "gasLimit": "0x1c9c380", "gasUsed": "0x26481", "timestamp": "0x688c83b5", "extraData": "0x", "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000", "nonce": "0x0000000000000000", "baseFeePerGas": "0x3b9aca00", "withdrawalsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", "blobGasUsed": "0x0", "excessBlobGas": "0x0", "parentBeaconBlockRoot": "0x0000000000000000000000000000000000000000000000000000000000000000" }, "transactions": [ { "transaction": { "type": "0x2", "chainId": "0x7a69", "nonce": "0x0", "gas": "0x31c41", "maxFeePerGas": "0x77359401", "maxPriorityFeePerGas": "0x1", "to": null, "value": "0x0", "accessList": [], "input": "0x6080604052348015600e575f5ffd5b506101e18061001c5f395ff3fe608060405234801561000f575f5ffd5b506004361061003f575f3560e01c80633fb5c1cb146100435780638381f58a1461005f578063d09de08a1461007d575b5f5ffd5b61005d600480360381019061005891906100e4565b610087565b005b610067610090565b604051610074919061011e565b60405180910390f35b610085610095565b005b805f8190555050565b5f5481565b5f5f8154809291906100a690610164565b9190505550565b5f5ffd5b5f819050919050565b6100c3816100b1565b81146100cd575f5ffd5b50565b5f813590506100de816100ba565b92915050565b5f602082840312156100f9576100f86100ad565b5b5f610106848285016100d0565b91505092915050565b610118816100b1565b82525050565b5f6020820190506101315f83018461010f565b92915050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b5f61016e826100b1565b91507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff82036101a05761019f610137565b5b60018201905091905056fea264697066735822122040b6a3cd3ec8f890002f39a8719ebee029ba9bac3d7fa9d581d4712cfe9ffec264736f6c634300081e0033", "r": "0xa7398e28ca9a56b423cab87aeb3612378bac9c5684aaf778a78943f2637fd731", "s": "0x583511da658f564253c8c0f9ee1820ef370f23556be504b304ac1292f869d9a0", "yParity": "0x0", "v": "0x0", "hash": "0x9e4846328caa09cbe8086d11b7e115adf70390e79ff203d8e5f37785c2a890be" }, "impersonated_sender": null } ], "ommers": [] } ], "transactions": [ { "info": { "transaction_hash": "0x9e4846328caa09cbe8086d11b7e115adf70390e79ff203d8e5f37785c2a890be", "transaction_index": 0, "from": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266", "to": null, "contract_address": "0x5fbdb2315678afecb367f032d93f642f64180aa3", "traces": [ { "parent": null, "children": [], "idx": 0, "trace": { "depth": 0, "success": true, "caller": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266", "address": "0x5fbdb2315678afecb367f032d93f642f64180aa3", "maybe_precompile": false, "selfdestruct_address": null, "selfdestruct_refund_target": null, "selfdestruct_transferred_value": null, "kind": "CREATE", "value": "0x0", "data": "0x6080604052348015600e575f5ffd5b506101e18061001c5f395ff3fe608060405234801561000f575f5ffd5b506004361061003f575f3560e01c80633fb5c1cb146100435780638381f58a1461005f578063d09de08a1461007d575b5f5ffd5b61005d600480360381019061005891906100e4565b610087565b005b610067610090565b604051610074919061011e565b60405180910390f35b610085610095565b005b805f8190555050565b5f5481565b5f5f8154809291906100a690610164565b9190505550565b5f5ffd5b5f819050919050565b6100c3816100b1565b81146100cd575f5ffd5b50565b5f813590506100de816100ba565b92915050565b5f602082840312156100f9576100f86100ad565b5b5f610106848285016100d0565b91505092915050565b610118816100b1565b82525050565b5f6020820190506101315f83018461010f565b92915050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b5f61016e826100b1565b91507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff82036101a05761019f610137565b5b60018201905091905056fea264697066735822122040b6a3cd3ec8f890002f39a8719ebee029ba9bac3d7fa9d581d4712cfe9ffec264736f6c634300081e0033", "output": "0x608060405234801561000f575f5ffd5b506004361061003f575f3560e01c80633fb5c1cb146100435780638381f58a1461005f578063d09de08a1461007d575b5f5ffd5b61005d600480360381019061005891906100e4565b610087565b005b610067610090565b604051610074919061011e565b60405180910390f35b610085610095565b005b805f8190555050565b5f5481565b5f5f8154809291906100a690610164565b9190505550565b5f5ffd5b5f819050919050565b6100c3816100b1565b81146100cd575f5ffd5b50565b5f813590506100de816100ba565b92915050565b5f602082840312156100f9576100f86100ad565b5b5f610106848285016100d0565b91505092915050565b610118816100b1565b82525050565b5f6020820190506101315f83018461010f565b92915050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b5f61016e826100b1565b91507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff82036101a05761019f610137565b5b60018201905091905056fea264697066735822122040b6a3cd3ec8f890002f39a8719ebee029ba9bac3d7fa9d581d4712cfe9ffec264736f6c634300081e0033", "gas_used": 96345, "gas_limit": 143385, "gas_refund_counter": 0, "status": "Return", "steps": [], "decoded": null }, "logs": [], "ordering": [] } ], "exit": "Return", "out": "0x608060405234801561000f575f5ffd5b506004361061003f575f3560e01c80633fb5c1cb146100435780638381f58a1461005f578063d09de08a1461007d575b5f5ffd5b61005d600480360381019061005891906100e4565b610087565b005b610067610090565b604051610074919061011e565b60405180910390f35b610085610095565b005b805f8190555050565b5f5481565b5f5f8154809291906100a690610164565b9190505550565b5f5ffd5b5f819050919050565b6100c3816100b1565b81146100cd575f5ffd5b50565b5f813590506100de816100ba565b92915050565b5f602082840312156100f9576100f86100ad565b5b5f610106848285016100d0565b91505092915050565b610118816100b1565b82525050565b5f6020820190506101315f83018461010f565b92915050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b5f61016e826100b1565b91507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff82036101a05761019f610137565b5b60018201905091905056fea264697066735822122040b6a3cd3ec8f890002f39a8719ebee029ba9bac3d7fa9d581d4712cfe9ffec264736f6c634300081e0033", "nonce": 0, "gas_used": 156801 }, "receipt": { "type": "0x2", "status": "0x1", "cumulativeGasUsed": "0x26481", "logs": [], "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" }, "block_hash": "0x313ea0d32d662434a55a20d7c58544e6baaea421b6eccf4b68392dec2a76d771", "block_number": 1 } ], "historical_states": null }); // Write the old state to file. foundry_common::fs::write_json_file(&old_state_file, &old_state_json).unwrap(); // Test deserializing the old state dump directly. let deserialized_state: SerializableState = serde_json::from_value(old_state_json).unwrap(); // Verify the old state was loaded correctly with `coinbase` to `beneficiary` conversion. let block_env = deserialized_state.block.unwrap(); assert_eq!(block_env.number, U256::from(1)); assert_eq!(block_env.beneficiary, address!("0000000000000000000000000000000000000001")); assert_eq!(block_env.gas_limit, 0x1c9c380); assert_eq!(block_env.basefee, 0x3b9aca00); // Verify best_block_number hex string parsing. assert_eq!(deserialized_state.best_block_number, Some(1)); // Verify account data was preserved. assert_eq!(deserialized_state.accounts.len(), 13); // Test specific accounts from the old dump. let deployer_addr = "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266".parse().unwrap(); let deployer_account = deserialized_state.accounts.get(&deployer_addr).unwrap(); assert_eq!(deployer_account.nonce, 1); assert_eq!(deployer_account.balance, U256::from_str("0x21e19e03b1e9e55d17f").unwrap()); // Test contract account. let contract_addr = "0x5fbdb2315678afecb367f032d93f642f64180aa3".parse().unwrap(); let contract_account = deserialized_state.accounts.get(&contract_addr).unwrap(); assert_eq!(contract_account.nonce, 1); assert_eq!(contract_account.balance, U256::ZERO); assert!(!contract_account.code.is_empty()); // Verify blocks and transactions are preserved. assert_eq!(deserialized_state.blocks.len(), 2); assert_eq!(deserialized_state.transactions.len(), 1); // Test that Anvil can load this old state dump. let (api, _handle) = spawn(NodeConfig::test().with_init_state_path(&old_state_file)).await; // Verify the state was loaded correctly. let block_number = api.block_number().unwrap(); assert_eq!(block_number, U256::from(1)); // Verify account balances are preserved. let provider = _handle.http_provider(); let deployer_balance = provider.get_balance(deployer_addr).await.unwrap(); assert_eq!(deployer_balance, U256::from_str("0x21e19e03b1e9e55d17f").unwrap()); let contract_balance = provider.get_balance(contract_addr).await.unwrap(); assert_eq!(contract_balance, U256::ZERO); // Verify contract code is preserved. let contract_code = provider.get_code_at(contract_addr).await.unwrap(); assert!(!contract_code.is_empty()); } ================================================ FILE: crates/anvil/tests/it/traces.rs ================================================ use std::collections::HashMap; use crate::{ abi::{Multicall, SimpleStorage}, fork::fork_config, utils::http_provider_with_signer, }; use alloy_eips::BlockId; use alloy_network::{EthereumWallet, TransactionBuilder}; use alloy_primitives::{ Address, Bytes, U256, hex::{self, FromHex}, }; use alloy_provider::{ Provider, ext::{DebugApi, TraceApi}, }; use alloy_rpc_types::{ TransactionRequest, state::StateOverride, trace::{ filter::{TraceFilter, TraceFilterMode}, geth::{ AccountState, CallConfig, GethDebugBuiltInTracerType, GethDebugTracerType, GethDebugTracingCallOptions, GethDebugTracingOptions, GethTrace, PreStateConfig, PreStateFrame, }, parity::{Action, ChangedType, LocalizedTransactionTrace, TraceType}, }, }; use alloy_serde::WithOtherFields; use alloy_sol_types::sol; use anvil::{NodeConfig, spawn}; use foundry_evm::hardfork::EthereumHardfork; #[tokio::test(flavor = "multi_thread")] async fn test_get_transfer_parity_traces() { let (_api, handle) = spawn(NodeConfig::test()).await; let provider = handle.ws_provider(); let accounts = handle.dev_wallets().collect::>(); let from = accounts[0].address(); let to = accounts[1].address(); let amount = handle.genesis_balance().checked_div(U256::from(2u64)).unwrap(); // specify the `from` field so that the client knows which account to use let tx = TransactionRequest::default().to(to).value(amount).from(from); let tx = WithOtherFields::new(tx); // broadcast it via the eth_sendTransaction API let tx = provider.send_transaction(tx).await.unwrap().get_receipt().await.unwrap(); let traces = provider.trace_transaction(tx.transaction_hash).await.unwrap(); assert!(!traces.is_empty()); match traces[0].trace.action { Action::Call(ref call) => { assert_eq!(call.from, from); assert_eq!(call.to, to); assert_eq!(call.value, amount); } _ => unreachable!("unexpected action"), } let num = provider.get_block_number().await.unwrap(); let block_traces = provider.trace_block(num.into()).await.unwrap(); assert!(!block_traces.is_empty()); assert_eq!(traces, block_traces); } sol!( #[sol(rpc, bytecode = "0x6080604052348015600f57600080fd5b50336000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555060a48061005e6000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c806375fc8e3c14602d575b600080fd5b60336035565b005b60008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16fffea26469706673582212205006867290df97c54f2df1cb94fc081197ab670e2adf5353071d2ecce1d694b864736f6c634300080d0033")] contract SuicideContract { address payable private owner; constructor() public { owner = payable(msg.sender); } function goodbye() public { selfdestruct(owner); } } ); #[tokio::test(flavor = "multi_thread")] async fn test_parity_suicide_trace() { let (_api, handle) = spawn(NodeConfig::test().with_hardfork(Some(EthereumHardfork::Shanghai.into()))).await; let provider = handle.ws_provider(); let wallets = handle.dev_wallets().collect::>(); let owner = wallets[0].address(); let destructor = wallets[1].address(); let contract_addr = SuicideContract::deploy_builder(provider.clone()).from(owner).deploy().await.unwrap(); let contract = SuicideContract::new(contract_addr, provider.clone()); let call = contract.goodbye().from(destructor); let call = call.send().await.unwrap(); let tx = call.get_receipt().await.unwrap(); let traces = handle.http_provider().trace_transaction(tx.transaction_hash).await.unwrap(); assert!(!traces.is_empty()); assert!(traces[1].trace.action.is_selfdestruct()); } sol!( #[sol(rpc, bytecode = "0x6080604052348015600f57600080fd5b50336000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555060a48061005e6000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c806375fc8e3c14602d575b600080fd5b60336035565b005b60008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16fffea26469706673582212205006867290df97c54f2df1cb94fc081197ab670e2adf5353071d2ecce1d694b864736f6c634300080d0033")] contract DebugTraceContract { address payable private owner; constructor() public { owner = payable(msg.sender); } function goodbye() public { selfdestruct(owner); } } ); #[tokio::test(flavor = "multi_thread")] async fn test_transfer_debug_trace_call() { let (_api, handle) = spawn(NodeConfig::test()).await; let wallets = handle.dev_wallets().collect::>(); let deployer: EthereumWallet = wallets[0].clone().into(); let provider = http_provider_with_signer(&handle.http_endpoint(), deployer); let contract_addr = DebugTraceContract::deploy_builder(provider.clone()) .from(wallets[0].clone().address()) .deploy() .await .unwrap(); let caller: EthereumWallet = wallets[1].clone().into(); let caller_provider = http_provider_with_signer(&handle.http_endpoint(), caller); let contract = DebugTraceContract::new(contract_addr, caller_provider); let call = contract.goodbye().from(wallets[1].address()); let calldata = call.calldata().to_owned(); let tx = TransactionRequest::default() .from(wallets[1].address()) .to(*contract.address()) .with_input(calldata); let traces = handle .http_provider() .debug_trace_call( WithOtherFields::new(tx), BlockId::latest(), GethDebugTracingCallOptions::default(), ) .await .unwrap(); match traces { GethTrace::Default(default_frame) => { assert!(!default_frame.failed); } _ => { unreachable!() } } } #[tokio::test(flavor = "multi_thread")] async fn test_call_tracer_debug_trace_call() { let (_api, handle) = spawn(NodeConfig::test()).await; let wallets = handle.dev_wallets().collect::>(); let deployer: EthereumWallet = wallets[0].clone().into(); let provider = http_provider_with_signer(&handle.http_endpoint(), deployer); let multicall_contract = Multicall::deploy(&provider).await.unwrap(); let simple_storage_contract = SimpleStorage::deploy(&provider, "init value".to_string()).await.unwrap(); let set_value = simple_storage_contract.setValue("bar".to_string()); let set_value_calldata = set_value.calldata(); let internal_call_tx_builder = multicall_contract.aggregate(vec![Multicall::Call { target: *simple_storage_contract.address(), callData: set_value_calldata.to_owned(), }]); let internal_call_tx_calldata = internal_call_tx_builder.calldata().to_owned(); // calling SimpleStorage contract through Multicall should result in an internal call let internal_call_tx = TransactionRequest::default() .from(wallets[1].address()) .to(*multicall_contract.address()) .with_input(internal_call_tx_calldata); let internal_call_tx_traces = handle .http_provider() .debug_trace_call( WithOtherFields::new(internal_call_tx.clone()), BlockId::latest(), GethDebugTracingCallOptions::default().with_tracing_options( GethDebugTracingOptions::default() .with_tracer(GethDebugTracerType::from(GethDebugBuiltInTracerType::CallTracer)) .with_call_config(CallConfig::default().with_log()), ), ) .await .unwrap(); match internal_call_tx_traces { GethTrace::CallTracer(call_frame) => { assert!(call_frame.calls.len() == 1); assert!( call_frame.calls.first().unwrap().to.unwrap() == *simple_storage_contract.address() ); assert!(call_frame.calls.first().unwrap().logs.len() == 1); } _ => { unreachable!() } } // only_top_call option - should not return any internal calls let internal_call_only_top_call_tx_traces = handle .http_provider() .debug_trace_call( WithOtherFields::new(internal_call_tx.clone()), BlockId::latest(), GethDebugTracingCallOptions::default().with_tracing_options( GethDebugTracingOptions::default() .with_tracer(GethDebugTracerType::from(GethDebugBuiltInTracerType::CallTracer)) .with_call_config(CallConfig::default().with_log().only_top_call()), ), ) .await .unwrap(); match internal_call_only_top_call_tx_traces { GethTrace::CallTracer(call_frame) => { assert!(call_frame.calls.is_empty()); } _ => { unreachable!() } } // directly calling the SimpleStorage contract should not result in any internal calls let direct_call_tx = TransactionRequest::default() .from(wallets[1].address()) .to(*simple_storage_contract.address()) .with_input(set_value_calldata.to_owned()); let direct_call_tx_traces = handle .http_provider() .debug_trace_call( WithOtherFields::new(direct_call_tx), BlockId::latest(), GethDebugTracingCallOptions::default().with_tracing_options( GethDebugTracingOptions::default() .with_tracer(GethDebugTracerType::from(GethDebugBuiltInTracerType::CallTracer)) .with_call_config(CallConfig::default().with_log()), ), ) .await .unwrap(); match direct_call_tx_traces { GethTrace::CallTracer(call_frame) => { assert!(call_frame.calls.is_empty()); assert!(call_frame.to.unwrap() == *simple_storage_contract.address()); assert!(call_frame.logs.len() == 1); } _ => { unreachable!() } } } #[tokio::test(flavor = "multi_thread")] async fn test_debug_trace_call_state_override() { let (_api, handle) = spawn(NodeConfig::test()).await; let wallets = handle.dev_wallets().collect::>(); let tx = TransactionRequest::default() .from(wallets[1].address()) .to("0x1234567890123456789012345678901234567890".parse().unwrap()); let override_json = r#"{ "0x1234567890123456789012345678901234567890": { "balance": "0x01", "code": "0x30315f5260205ff3" } }"#; let state_override: StateOverride = serde_json::from_str(override_json).unwrap(); let tx_traces = handle .http_provider() .debug_trace_call( WithOtherFields::new(tx.clone()), BlockId::latest(), GethDebugTracingCallOptions::default() .with_tracing_options(GethDebugTracingOptions::default()) .with_state_overrides(state_override), ) .await .unwrap(); match tx_traces { GethTrace::Default(trace_res) => { assert_eq!( trace_res.return_value, Bytes::from_hex("0000000000000000000000000000000000000000000000000000000000000001") .unwrap() ); } _ => { unreachable!() } } } // #[tokio::test(flavor = "multi_thread")] async fn test_trace_address_fork() { let (api, handle) = spawn(fork_config().with_fork_block_number(Some(15291050u64))).await; let provider = handle.http_provider(); let input = hex::decode("43bcfab60000000000000000000000006b175474e89094c44da98b954eedeac495271d0f0000000000000000000000000000000000000000000000e0bd811c8769a824b00000000000000000000000000000000000000000000000e0ae9925047d8440b60000000000000000000000002e4777139254ff76db957e284b186a4507ff8c67").unwrap(); let from: Address = "0x2e4777139254ff76db957e284b186a4507ff8c67".parse().unwrap(); let to: Address = "0xe2f2a5c287993345a840db3b0845fbc70f5935a5".parse().unwrap(); let tx = TransactionRequest::default() .to(to) .from(from) .with_input::(input.into()) .with_gas_limit(300_000); let tx = WithOtherFields::new(tx); api.anvil_impersonate_account(from).await.unwrap(); let tx = provider.send_transaction(tx).await.unwrap().get_receipt().await.unwrap(); let traces = provider.trace_transaction(tx.transaction_hash).await.unwrap(); assert!(!traces.is_empty()); match traces[0].trace.action { Action::Call(ref call) => { assert_eq!(call.from, from); assert_eq!(call.to, to); } _ => unreachable!("unexpected action"), } let json = serde_json::json!([ { "action": { "callType": "call", "from": "0x2e4777139254ff76db957e284b186a4507ff8c67", "gas": "0x262b3", "input": "0x43bcfab60000000000000000000000006b175474e89094c44da98b954eedeac495271d0f0000000000000000000000000000000000000000000000e0bd811c8769a824b00000000000000000000000000000000000000000000000e0ae9925047d8440b60000000000000000000000002e4777139254ff76db957e284b186a4507ff8c67", "to": "0xe2f2a5c287993345a840db3b0845fbc70f5935a5", "value": "0x0" }, "blockHash": "0xa47c8f1d8c284cb614e9c8e10d260b33eae16b1957a83141191bc335838d7e29", "blockNumber": 15291051, "result": { "gasUsed": "0x2131b", "output": "0x0000000000000000000000000000000000000000000000e0e82ca52ec6e6a4d3" }, "subtraces": 1, "traceAddress": [], "transactionHash": "0x3255cce7312e9c4470e1a1883be13718e971f6faafb96199b8bd75e5b7c39e3a", "transactionPosition": 19, "type": "call" }, { "action": { "callType": "delegatecall", "from": "0xe2f2a5c287993345a840db3b0845fbc70f5935a5", "gas": "0x23d88", "input": "0x43bcfab60000000000000000000000006b175474e89094c44da98b954eedeac495271d0f0000000000000000000000000000000000000000000000e0bd811c8769a824b00000000000000000000000000000000000000000000000e0ae9925047d8440b60000000000000000000000002e4777139254ff76db957e284b186a4507ff8c67", "to": "0x15b2838cd28cc353afbe59385db3f366d8945aee", "value": "0x0" }, "blockHash": "0xa47c8f1d8c284cb614e9c8e10d260b33eae16b1957a83141191bc335838d7e29", "blockNumber": 15291051, "result": { "gasUsed": "0x1f6e1", "output": "0x0000000000000000000000000000000000000000000000e0e82ca52ec6e6a4d3" }, "subtraces": 2, "traceAddress": [0], "transactionHash": "0x3255cce7312e9c4470e1a1883be13718e971f6faafb96199b8bd75e5b7c39e3a", "transactionPosition": 19, "type": "call" }, { "action": { "callType": "staticcall", "from": "0xe2f2a5c287993345a840db3b0845fbc70f5935a5", "gas": "0x192ed", "input": "0x50494dc000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000e0b1ff65617f654b2f00000000000000000000000000000000000000000000000000000000000061a800000000000000000000000000000000000000000000000000b1a2bc2ec5000000000000000000000000000000000000000000000000000006f05b59d3b2000000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000005f5e1000000000000000000000000000000000000000000000414ec22973db48fd3a3370000000000000000000000000000000000000000000000056bc75e2d6310000000000000000000000000000000000000000000000000000000000a7314a9ba5c0000000000000000000000000000000000000000000000000000000005f5e100000000000000000000000000000000000000000000095f783edc5a5dabcb4ba70000000000000000000000000000000000000000000000056bc75e2d6310000000000000000000000000000000000000000000000000000000000a3f42df4dab", "to": "0xca480d596e6717c95a62a4dc1bd4fbd7b7e7d705", "value": "0x0" }, "blockHash": "0xa47c8f1d8c284cb614e9c8e10d260b33eae16b1957a83141191bc335838d7e29", "blockNumber": 15291051, "result": { "gasUsed": "0x661a", "output": "0x0000000000000000000000000000000000000000000000e0e82ca52ec6e6a4d3" }, "subtraces": 0, "traceAddress": [0, 0], "transactionHash": "0x3255cce7312e9c4470e1a1883be13718e971f6faafb96199b8bd75e5b7c39e3a", "transactionPosition": 19, "type": "call" }, { "action": { "callType": "delegatecall", "from": "0xe2f2a5c287993345a840db3b0845fbc70f5935a5", "gas": "0xd2dc", "input": "0x4e331a540000000000000000000000000000000000000000000000e0e82ca52ec6e6a4d30000000000000000000000006b175474e89094c44da98b954eedeac495271d0f000000000000000000000000a2a3cae63476891ab2d640d9a5a800755ee79d6e000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000005f5e100000000000000000000000000000000000000000000095f783edc5a5dabcb4ba70000000000000000000000002e4777139254ff76db957e284b186a4507ff8c6700000000000000000000000000000000000000000000f7be2b91f8a2e2df496e", "to": "0x1e91f826fa8aa4fa4d3f595898af3a64dd188848", "value": "0x0" }, "blockHash": "0xa47c8f1d8c284cb614e9c8e10d260b33eae16b1957a83141191bc335838d7e29", "blockNumber": 15291051, "result": { "gasUsed": "0x7617", "output": "0x" }, "subtraces": 2, "traceAddress": [0, 1], "transactionHash": "0x3255cce7312e9c4470e1a1883be13718e971f6faafb96199b8bd75e5b7c39e3a", "transactionPosition": 19, "type": "call" }, { "action": { "callType": "staticcall", "from": "0xe2f2a5c287993345a840db3b0845fbc70f5935a5", "gas": "0xbf50", "input": "0x70a08231000000000000000000000000a2a3cae63476891ab2d640d9a5a800755ee79d6e", "to": "0x6b175474e89094c44da98b954eedeac495271d0f", "value": "0x0" }, "blockHash": "0xa47c8f1d8c284cb614e9c8e10d260b33eae16b1957a83141191bc335838d7e29", "blockNumber": 15291051, "result": { "gasUsed": "0xa2a", "output": "0x0000000000000000000000000000000000000000000020fe99f8898600d94750" }, "subtraces": 0, "traceAddress": [0, 1, 0], "transactionHash": "0x3255cce7312e9c4470e1a1883be13718e971f6faafb96199b8bd75e5b7c39e3a", "transactionPosition": 19, "type": "call" }, { "action": { "callType": "call", "from": "0xe2f2a5c287993345a840db3b0845fbc70f5935a5", "gas": "0xa92a", "input": "0xa4e285950000000000000000000000002e4777139254ff76db957e284b186a4507ff8c670000000000000000000000006b175474e89094c44da98b954eedeac495271d0f0000000000000000000000000000000000000000000000e0e82ca52ec6e6a4d3", "to": "0xa2a3cae63476891ab2d640d9a5a800755ee79d6e", "value": "0x0" }, "blockHash": "0xa47c8f1d8c284cb614e9c8e10d260b33eae16b1957a83141191bc335838d7e29", "blockNumber": 15291051, "result": { "gasUsed": "0x4ed3", "output": "0x" }, "subtraces": 1, "traceAddress": [0, 1, 1], "transactionHash": "0x3255cce7312e9c4470e1a1883be13718e971f6faafb96199b8bd75e5b7c39e3a", "transactionPosition": 19, "type": "call" }, { "action": { "callType": "call", "from": "0xa2a3cae63476891ab2d640d9a5a800755ee79d6e", "gas": "0x8c90", "input": "0xa9059cbb0000000000000000000000002e4777139254ff76db957e284b186a4507ff8c670000000000000000000000000000000000000000000000e0e82ca52ec6e6a4d3", "to": "0x6b175474e89094c44da98b954eedeac495271d0f", "value": "0x0" }, "blockHash": "0xa47c8f1d8c284cb614e9c8e10d260b33eae16b1957a83141191bc335838d7e29", "blockNumber": 15291051, "result": { "gasUsed": "0x2b42", "output": "0x0000000000000000000000000000000000000000000000000000000000000001" }, "subtraces": 0, "traceAddress": [0, 1, 1, 0], "transactionHash": "0x3255cce7312e9c4470e1a1883be13718e971f6faafb96199b8bd75e5b7c39e3a", "transactionPosition": 19, "type": "call" } ]); let expected_traces: Vec = serde_json::from_value(json).unwrap(); // test matching traceAddress traces.into_iter().zip(expected_traces).for_each(|(a, b)| { assert_eq!(a.trace.trace_address, b.trace.trace_address); assert_eq!(a.trace.subtraces, b.trace.subtraces); match (a.trace.action, b.trace.action) { (Action::Call(a), Action::Call(b)) => { assert_eq!(a.from, b.from); assert_eq!(a.to, b.to); } _ => unreachable!("unexpected action"), } }) } // // #[tokio::test(flavor = "multi_thread")] async fn test_trace_address_fork2() { let (api, handle) = spawn(fork_config().with_fork_block_number(Some(15314401u64))).await; let provider = handle.http_provider(); let input = hex::decode("30000003000000000000000000000000adda1059a6c6c102b0fa562b9bb2cb9a0de5b1f4000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a300000004fffffffffffffffffffffffffffffffffffffffffffff679dc91ecfe150fb980c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2f4d2888d29d722226fafa5d9b24f9164c092421e000bb8000000000000004319b52bf08b65295d49117e790000000000000000000000000000000000000000000000008b6d9e8818d6141f000000000000000000000000000000000000000000000000000000086a23af210000000000000000000000000000000000000000000000000000000000").unwrap(); let from: Address = "0xa009fa1ac416ec02f6f902a3a4a584b092ae6123".parse().unwrap(); let to: Address = "0x99999999d116ffa7d76590de2f427d8e15aeb0b8".parse().unwrap(); let tx = TransactionRequest::default() .to(to) .from(from) .with_input::(input.into()) .with_gas_limit(350_000); let tx = WithOtherFields::new(tx); api.anvil_impersonate_account(from).await.unwrap(); let tx = provider.send_transaction(tx).await.unwrap().get_receipt().await.unwrap(); let status = tx.inner.inner.inner.receipt.status.coerce_status(); assert!(status); let traces = provider.trace_transaction(tx.transaction_hash).await.unwrap(); assert!(!traces.is_empty()); match traces[0].trace.action { Action::Call(ref call) => { assert_eq!(call.from, from); assert_eq!(call.to, to); } _ => unreachable!("unexpected action"), } let json = serde_json::json!([ { "action": { "from": "0xa009fa1ac416ec02f6f902a3a4a584b092ae6123", "callType": "call", "gas": "0x4fabc", "input": "0x30000003000000000000000000000000adda1059a6c6c102b0fa562b9bb2cb9a0de5b1f4000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a300000004fffffffffffffffffffffffffffffffffffffffffffff679dc91ecfe150fb980c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2f4d2888d29d722226fafa5d9b24f9164c092421e000bb8000000000000004319b52bf08b65295d49117e790000000000000000000000000000000000000000000000008b6d9e8818d6141f000000000000000000000000000000000000000000000000000000086a23af210000000000000000000000000000000000000000000000000000000000", "to": "0x99999999d116ffa7d76590de2f427d8e15aeb0b8", "value": "0x0" }, "blockHash": "0xf689ba7749648b8c5c8f5eedd73001033f0aed7ea50b7c81048ad1533b8d3d73", "blockNumber": 15314402, "result": { "gasUsed": "0x1d51b", "output": "0x" }, "subtraces": 1, "traceAddress": [], "transactionHash": "0x2d951c5c95d374263ca99ad9c20c9797fc714330a8037429a3aa4c83d456f845", "transactionPosition": 289, "type": "call" }, { "action": { "from": "0x99999999d116ffa7d76590de2f427d8e15aeb0b8", "callType": "delegatecall", "gas": "0x4d594", "input": "0x00000004fffffffffffffffffffffffffffffffffffffffffffff679dc91ecfe150fb980c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2f4d2888d29d722226fafa5d9b24f9164c092421e000bb8000000000000004319b52bf08b65295d49117e790000000000000000000000000000000000000000000000008b6d9e8818d6141f000000000000000000000000000000000000000000000000000000086a23af21", "to": "0xadda1059a6c6c102b0fa562b9bb2cb9a0de5b1f4", "value": "0x0" }, "blockHash": "0xf689ba7749648b8c5c8f5eedd73001033f0aed7ea50b7c81048ad1533b8d3d73", "blockNumber": 15314402, "result": { "gasUsed": "0x1c35f", "output": "0x" }, "subtraces": 3, "traceAddress": [0], "transactionHash": "0x2d951c5c95d374263ca99ad9c20c9797fc714330a8037429a3aa4c83d456f845", "transactionPosition": 289, "type": "call" }, { "action": { "from": "0x99999999d116ffa7d76590de2f427d8e15aeb0b8", "callType": "call", "gas": "0x4b6d6", "input": "0x16b2da82000000000000000000000000000000000000000000000000000000086a23af21", "to": "0xd1663cfb8ceaf22039ebb98914a8c98264643710", "value": "0x0" }, "blockHash": "0xf689ba7749648b8c5c8f5eedd73001033f0aed7ea50b7c81048ad1533b8d3d73", "blockNumber": 15314402, "result": { "gasUsed": "0xd6d", "output": "0x0000000000000000000000000000000000000000000000000000000000000000" }, "subtraces": 0, "traceAddress": [0, 0], "transactionHash": "0x2d951c5c95d374263ca99ad9c20c9797fc714330a8037429a3aa4c83d456f845", "transactionPosition": 289, "type": "call" }, { "action": { "from": "0x99999999d116ffa7d76590de2f427d8e15aeb0b8", "callType": "staticcall", "gas": "0x49c35", "input": "0x3850c7bd", "to": "0x4b5ab61593a2401b1075b90c04cbcdd3f87ce011", "value": "0x0" }, "blockHash": "0xf689ba7749648b8c5c8f5eedd73001033f0aed7ea50b7c81048ad1533b8d3d73", "blockNumber": 15314402, "result": { "gasUsed": "0xa88", "output": "0x000000000000000000000000000000000000004319b52bf08b65295d49117e7900000000000000000000000000000000000000000000000000000000000148a0000000000000000000000000000000000000000000000000000000000000010e000000000000000000000000000000000000000000000000000000000000012c000000000000000000000000000000000000000000000000000000000000012c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001" }, "subtraces": 0, "traceAddress": [0, 1], "transactionHash": "0x2d951c5c95d374263ca99ad9c20c9797fc714330a8037429a3aa4c83d456f845", "transactionPosition": 289, "type": "call" }, { "action": { "from": "0x99999999d116ffa7d76590de2f427d8e15aeb0b8", "callType": "call", "gas": "0x48d01", "input": "0x128acb0800000000000000000000000099999999d116ffa7d76590de2f427d8e15aeb0b80000000000000000000000000000000000000000000000000000000000000001fffffffffffffffffffffffffffffffffffffffffffff679dc91ecfe150fb98000000000000000000000000000000000000000000000000000000001000276a400000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000002bc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2f4d2888d29d722226fafa5d9b24f9164c092421e000bb8000000000000000000000000000000000000000000", "to": "0x4b5ab61593a2401b1075b90c04cbcdd3f87ce011", "value": "0x0" }, "blockHash": "0xf689ba7749648b8c5c8f5eedd73001033f0aed7ea50b7c81048ad1533b8d3d73", "blockNumber": 15314402, "result": { "gasUsed": "0x18c20", "output": "0x0000000000000000000000000000000000000000000000008b5116525f9edc3efffffffffffffffffffffffffffffffffffffffffffff679dc91ecfe150fb980" }, "subtraces": 4, "traceAddress": [0, 2], "transactionHash": "0x2d951c5c95d374263ca99ad9c20c9797fc714330a8037429a3aa4c83d456f845", "transactionPosition": 289, "type": "call" }, { "action": { "from": "0x4b5ab61593a2401b1075b90c04cbcdd3f87ce011", "callType": "call", "gas": "0x3802a", "input": "0xa9059cbb00000000000000000000000099999999d116ffa7d76590de2f427d8e15aeb0b8000000000000000000000000000000000000000000000986236e1301eaf04680", "to": "0xf4d2888d29d722226fafa5d9b24f9164c092421e", "value": "0x0" }, "blockHash": "0xf689ba7749648b8c5c8f5eedd73001033f0aed7ea50b7c81048ad1533b8d3d73", "blockNumber": 15314402, "result": { "gasUsed": "0x31b6", "output": "0x0000000000000000000000000000000000000000000000000000000000000001" }, "subtraces": 0, "traceAddress": [0, 2, 0], "transactionHash": "0x2d951c5c95d374263ca99ad9c20c9797fc714330a8037429a3aa4c83d456f845", "transactionPosition": 289, "type": "call" }, { "action": { "from": "0x4b5ab61593a2401b1075b90c04cbcdd3f87ce011", "callType": "staticcall", "gas": "0x34237", "input": "0x70a082310000000000000000000000004b5ab61593a2401b1075b90c04cbcdd3f87ce011", "to": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", "value": "0x0" }, "blockHash": "0xf689ba7749648b8c5c8f5eedd73001033f0aed7ea50b7c81048ad1533b8d3d73", "blockNumber": 15314402, "result": { "gasUsed": "0x9e6", "output": "0x000000000000000000000000000000000000000000000091cda6c1ce33e53b89" }, "subtraces": 0, "traceAddress": [0, 2, 1], "transactionHash": "0x2d951c5c95d374263ca99ad9c20c9797fc714330a8037429a3aa4c83d456f845", "transactionPosition": 289, "type": "call" }, { "action": { "from": "0x4b5ab61593a2401b1075b90c04cbcdd3f87ce011", "callType": "call", "gas": "0x3357e", "input": "0xfa461e330000000000000000000000000000000000000000000000008b5116525f9edc3efffffffffffffffffffffffffffffffffffffffffffff679dc91ecfe150fb9800000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000002bc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2f4d2888d29d722226fafa5d9b24f9164c092421e000bb8000000000000000000000000000000000000000000", "to": "0x99999999d116ffa7d76590de2f427d8e15aeb0b8", "value": "0x0" }, "blockHash": "0xf689ba7749648b8c5c8f5eedd73001033f0aed7ea50b7c81048ad1533b8d3d73", "blockNumber": 15314402, "result": { "gasUsed": "0x2e8b", "output": "0x" }, "subtraces": 1, "traceAddress": [0, 2, 2], "transactionHash": "0x2d951c5c95d374263ca99ad9c20c9797fc714330a8037429a3aa4c83d456f845", "transactionPosition": 289, "type": "call" }, { "action": { "from": "0x99999999d116ffa7d76590de2f427d8e15aeb0b8", "callType": "call", "gas": "0x324db", "input": "0xa9059cbb0000000000000000000000004b5ab61593a2401b1075b90c04cbcdd3f87ce0110000000000000000000000000000000000000000000000008b5116525f9edc3e", "to": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", "value": "0x0" }, "blockHash": "0xf689ba7749648b8c5c8f5eedd73001033f0aed7ea50b7c81048ad1533b8d3d73", "blockNumber": 15314402, "result": { "gasUsed": "0x2a6e", "output": "0x0000000000000000000000000000000000000000000000000000000000000001" }, "subtraces": 0, "traceAddress": [0, 2, 2, 0], "transactionHash": "0x2d951c5c95d374263ca99ad9c20c9797fc714330a8037429a3aa4c83d456f845", "transactionPosition": 289, "type": "call" }, { "action": { "from": "0x4b5ab61593a2401b1075b90c04cbcdd3f87ce011", "callType": "staticcall", "gas": "0x30535", "input": "0x70a082310000000000000000000000004b5ab61593a2401b1075b90c04cbcdd3f87ce011", "to": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", "value": "0x0" }, "blockHash": "0xf689ba7749648b8c5c8f5eedd73001033f0aed7ea50b7c81048ad1533b8d3d73", "blockNumber": 15314402, "result": { "gasUsed": "0x216", "output": "0x00000000000000000000000000000000000000000000009258f7d820938417c7" }, "subtraces": 0, "traceAddress": [0, 2, 3], "transactionHash": "0x2d951c5c95d374263ca99ad9c20c9797fc714330a8037429a3aa4c83d456f845", "transactionPosition": 289, "type": "call" } ]); let expected_traces: Vec = serde_json::from_value(json).unwrap(); // test matching traceAddress traces.into_iter().zip(expected_traces).for_each(|(a, b)| { assert_eq!(a.trace.trace_address, b.trace.trace_address); assert_eq!(a.trace.subtraces, b.trace.subtraces); match (a.trace.action, b.trace.action) { (Action::Call(a), Action::Call(b)) => { assert_eq!(a.from, b.from); assert_eq!(a.to, b.to); } _ => unreachable!("unexpected action"), } }) } #[tokio::test(flavor = "multi_thread")] async fn test_trace_filter() { let (api, handle) = spawn(NodeConfig::test()).await; let provider = handle.ws_provider(); let accounts = handle.dev_wallets().collect::>(); let from = accounts[0].address(); let to = accounts[1].address(); let from_two = accounts[2].address(); let to_two = accounts[3].address(); // Test default block ranges. // From will be earliest, to will be best/latest let tracer = TraceFilter { from_block: None, to_block: None, from_address: vec![], to_address: vec![], mode: TraceFilterMode::Intersection, after: None, count: None, }; for i in 0..=5 { let tx = TransactionRequest::default().to(to).value(U256::from(i)).from(from); let tx = WithOtherFields::new(tx); provider.send_transaction(tx).await.unwrap().get_receipt().await.unwrap(); } let traces = api.trace_filter(tracer).await.unwrap(); assert_eq!(traces.len(), 6); // Test filtering by address let tracer = TraceFilter { from_block: Some(provider.get_block_number().await.unwrap()), to_block: None, from_address: vec![from_two], to_address: vec![to_two], mode: TraceFilterMode::Intersection, after: None, count: None, }; for i in 0..=5 { let tx = TransactionRequest::default().to(to).value(U256::from(i)).from(from); let tx = WithOtherFields::new(tx); provider.send_transaction(tx).await.unwrap().get_receipt().await.unwrap(); let tx = TransactionRequest::default().to(to_two).value(U256::from(i)).from(from_two); let tx = WithOtherFields::new(tx); provider.send_transaction(tx).await.unwrap().get_receipt().await.unwrap(); } let traces = api.trace_filter(tracer).await.unwrap(); assert_eq!(traces.len(), 6); // Test for the following actions: // Create (deploy the contract) // Call (goodbye function) // SelfDestruct (side-effect of goodbye) let contract_addr = SuicideContract::deploy_builder(provider.clone()).from(from).deploy().await.unwrap(); let contract = SuicideContract::new(contract_addr, provider.clone()); // Test TraceActions let tracer = TraceFilter { from_block: Some(provider.get_block_number().await.unwrap()), to_block: None, from_address: vec![from, contract_addr], to_address: vec![], // Leave as 0 address mode: TraceFilterMode::Union, after: None, count: None, }; // Execute call let call = contract.goodbye().from(from); let call = call.send().await.unwrap(); call.get_receipt().await.unwrap(); // Mine transactions to filter against for i in 0..=5 { let tx = TransactionRequest::default().to(to_two).value(U256::from(i)).from(from_two); let tx = WithOtherFields::new(tx); provider.send_transaction(tx).await.unwrap().get_receipt().await.unwrap(); } let traces = api.trace_filter(tracer).await.unwrap(); assert_eq!(traces.len(), 3); // Test Range Error let latest = provider.get_block_number().await.unwrap(); let tracer = TraceFilter { from_block: Some(latest), to_block: Some(latest + 301), from_address: vec![], to_address: vec![], mode: TraceFilterMode::Union, after: None, count: None, }; let traces = api.trace_filter(tracer).await; assert!(traces.is_err()); // Test same from and to block is valid let latest = provider.get_block_number().await.unwrap(); let tracer = TraceFilter { from_block: Some(latest), to_block: Some(latest), from_address: vec![], to_address: vec![], mode: TraceFilterMode::Union, after: None, count: None, }; let traces = api.trace_filter(tracer).await; assert!(traces.is_ok()); // Test invalid block range let latest = provider.get_block_number().await.unwrap(); let tracer = TraceFilter { from_block: Some(latest + 10), to_block: Some(latest), from_address: vec![], to_address: vec![], mode: TraceFilterMode::Union, after: None, count: None, }; let traces = api.trace_filter(tracer).await; assert!(traces.is_err()); // Test after and count let tracer = TraceFilter { from_block: Some(provider.get_block_number().await.unwrap()), to_block: None, from_address: vec![], to_address: vec![], mode: TraceFilterMode::Union, after: Some(3), count: Some(5), }; for i in 0..=10 { let tx = TransactionRequest::default().to(to).value(U256::from(i)).from(from); let tx = WithOtherFields::new(tx); provider.send_transaction(tx).await.unwrap().get_receipt().await.unwrap(); } let traces = api.trace_filter(tracer).await.unwrap(); assert_eq!(traces.len(), 5); } #[cfg(feature = "js-tracer")] #[tokio::test(flavor = "multi_thread")] async fn test_call_tracer_debug_trace_call_js_tracer() { let (api, handle) = spawn(NodeConfig::test()).await; let wallets = handle.dev_wallets().collect::>(); let deployer: EthereumWallet = wallets[0].clone().into(); let provider = http_provider_with_signer(&handle.http_endpoint(), deployer); let multicall_contract = Multicall::deploy(&provider).await.unwrap(); let simple_storage_contract = SimpleStorage::deploy(&provider, "init value".to_string()).await.unwrap(); let set_value = simple_storage_contract.setValue("bar".to_string()); let set_value_calldata = set_value.calldata(); let internal_call_tx_builder = multicall_contract.aggregate(vec![Multicall::Call { target: *simple_storage_contract.address(), callData: set_value_calldata.to_owned(), }]); let internal_call_tx_calldata = internal_call_tx_builder.calldata().to_owned(); let internal_call_tx = TransactionRequest::default() .from(wallets[1].address()) .to(*multicall_contract.address()) .with_input(internal_call_tx_calldata); let js_tracer_code = r#" { data: [], step: function(log) { var op = log.op.toString(); if (op === "SLOAD") { this.data.push(log.getPC() + ": SLOAD " + log.contract.getAddress() + ":" + log.stack.peek(0)); this.data.push(" Result: " + log.stack.peek(0)); } else if (op === "SSTORE") { this.data.push(log.getPC() + ": SSTORE " + log.contract.getAddress() + ":" + log.stack.peek(1) + " <- " + log.stack.peek(0)); } }, result: function() { return this.data; }, fault: function(log) {} } "#; let result = api .debug_trace_call( WithOtherFields::new(internal_call_tx), Some(BlockId::latest()), GethDebugTracingCallOptions::default() .with_tracing_options(GethDebugTracingOptions::js_tracer(js_tracer_code)), ) .await .unwrap(); let expected = vec![ "547: SLOAD 231,241,114,94,119,52,206,40,143,131,103,225,187,20,62,144,187,63,5,18:0", " Result: 0", "1907: SLOAD 231,241,114,94,119,52,206,40,143,131,103,225,187,20,62,144,187,63,5,18:1", " Result: 1", "772: SLOAD 231,241,114,94,119,52,206,40,143,131,103,225,187,20,62,144,187,63,5,18:1", " Result: 1", "835: SSTORE 231,241,114,94,119,52,206,40,143,131,103,225,187,20,62,144,187,63,5,18:44498830125527143464827115118378702402016761369235290884359940707316142178310 <- 1", "919: SSTORE 231,241,114,94,119,52,206,40,143,131,103,225,187,20,62,144,187,63,5,18:0 <- 80084422859880547211683076133703299733277748156566366325829078699459944778998", "712: SLOAD 231,241,114,94,119,52,206,40,143,131,103,225,187,20,62,144,187,63,5,18:0", " Result: 0", "765: SSTORE 231,241,114,94,119,52,206,40,143,131,103,225,187,20,62,144,187,63,5,18:546584486846459126461364135121053344201067465379 <- 0", ]; let actual: Vec = result .try_into_json_value() .ok() .and_then(|val| val.as_array().cloned()) .map(|arr| arr.into_iter().filter_map(|v| v.as_str().map(|s| s.to_string())).collect()) .unwrap_or_default(); assert_eq!(actual, expected); } #[cfg(feature = "js-tracer")] #[tokio::test(flavor = "multi_thread")] async fn test_debug_trace_transaction_js_tracer() { let node_config = NodeConfig::test().with_hardfork(Some(EthereumHardfork::Prague.into())); let (api, handle) = spawn(node_config).await; let provider = crate::utils::http_provider(&handle.http_endpoint()); let wallets = handle.dev_wallets().collect::>(); let from = wallets[0].address(); api.anvil_add_balance(from, U256::MAX).await.unwrap(); api.anvil_add_balance(wallets[1].address(), U256::MAX).await.unwrap(); let multicall_contract = Multicall::deploy(&provider).await.unwrap(); let simple_storage_contract = SimpleStorage::deploy(&provider, "init value".to_string()).await.unwrap(); let set_value = simple_storage_contract.setValue("bar".to_string()); let set_value_calldata = set_value.calldata(); let internal_call_tx_builder = multicall_contract.aggregate(vec![Multicall::Call { target: *simple_storage_contract.address(), callData: set_value_calldata.to_owned(), }]); let internal_call_tx_calldata = internal_call_tx_builder.calldata().to_owned(); let internal_call_tx = TransactionRequest::default() .from(wallets[1].address()) .to(*multicall_contract.address()) .with_input(internal_call_tx_calldata) .with_gas_limit(1_000_000) .with_max_fee_per_gas(100_000_000_000) .with_max_priority_fee_per_gas(100_000_000_000); let receipt = provider .send_transaction(internal_call_tx.into()) .await .unwrap() .get_receipt() .await .unwrap(); let js_tracer_code = r#" { data: [], step: function(log) { var op = log.op.toString(); var pc = log.getPC(); var addr = log.contract.getAddress(); if (op === "SLOAD") { this.data.push(pc + ": SLOAD " + addr + ":" + log.stack.peek(0)); this.data.push(" Result: " + log.stack.peek(0)); } else if (op === "SSTORE") { this.data.push(pc + ": SSTORE " + addr + ":" + log.stack.peek(1) + " <- " + log.stack.peek(0)); } }, result: function() { return this.data; }, fault: function(log) {} } "#; let expected = vec![ "547: SLOAD 231,241,114,94,119,52,206,40,143,131,103,225,187,20,62,144,187,63,5,18:0", " Result: 0", "1907: SLOAD 231,241,114,94,119,52,206,40,143,131,103,225,187,20,62,144,187,63,5,18:1", " Result: 1", "772: SLOAD 231,241,114,94,119,52,206,40,143,131,103,225,187,20,62,144,187,63,5,18:1", " Result: 1", "835: SSTORE 231,241,114,94,119,52,206,40,143,131,103,225,187,20,62,144,187,63,5,18:44498830125527143464827115118378702402016761369235290884359940707316142178310 <- 1", "919: SSTORE 231,241,114,94,119,52,206,40,143,131,103,225,187,20,62,144,187,63,5,18:0 <- 80084422859880547211683076133703299733277748156566366325829078699459944778998", "712: SLOAD 231,241,114,94,119,52,206,40,143,131,103,225,187,20,62,144,187,63,5,18:0", " Result: 0", "765: SSTORE 231,241,114,94,119,52,206,40,143,131,103,225,187,20,62,144,187,63,5,18:546584486846459126461364135121053344201067465379 <- 0", ]; let result = api .debug_trace_transaction( receipt.transaction_hash, GethDebugTracingOptions::js_tracer(js_tracer_code), ) .await .unwrap(); let actual: Vec = result .try_into_json_value() .ok() .and_then(|val| val.as_array().cloned()) .map(|arr| arr.into_iter().filter_map(|v| v.as_str().map(|s| s.to_string())).collect()) .unwrap_or_default(); assert_eq!(actual, expected); } #[tokio::test(flavor = "multi_thread")] async fn test_call_tracer_debug_trace_call_pre_state_tracer() { let (api, handle) = spawn(NodeConfig::test()).await; let wallets = handle.dev_wallets().collect::>(); let deployer: EthereumWallet = wallets[0].clone().into(); let provider = http_provider_with_signer(&handle.http_endpoint(), deployer); let multicall_contract = Multicall::deploy(&provider).await.unwrap(); let simple_storage_contract = SimpleStorage::deploy(&provider, "init value".to_string()).await.unwrap(); let set_value = simple_storage_contract.setValue("bar".to_string()); let set_value_calldata = set_value.calldata(); let internal_call_tx_builder = multicall_contract.aggregate(vec![Multicall::Call { target: *simple_storage_contract.address(), callData: set_value_calldata.to_owned(), }]); let internal_call_tx_calldata = internal_call_tx_builder.calldata().to_owned(); let internal_call_tx = TransactionRequest::default() .from(wallets[1].address()) .to(*multicall_contract.address()) .with_input(internal_call_tx_calldata); let result = api .debug_trace_call( WithOtherFields::new(internal_call_tx), Some(BlockId::latest()), GethDebugTracingCallOptions::default().with_tracing_options( GethDebugTracingOptions::prestate_tracer(PreStateConfig::default()), ), ) .await .unwrap(); let expected = r#" { "0x0000000000000000000000000000000000000000": { "balance": "0x12670f" }, "0x5fbdb2315678afecb367f032d93f642f64180aa3": { "balance": "0x0", "nonce": 1 }, "0x70997970c51812dc3a010c7d01b50e0d17dc79c8": { "balance": "0x56bc75e2d63100000" }, "0xe7f1725e7734ce288f8367e1bb143e90bb3f0512": { "balance": "0x0", "nonce": 1, "storage": { "0x0000000000000000000000000000000000000000000000000000000000000000": "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000001": "0x696e69742076616c756500000000000000000000000000000000000000000014", "0xb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf6": "0x0000000000000000000000000000000000000000000000000000000000000000" } } } "#; let expected: HashMap = serde_json::from_str(expected).unwrap(); match result { GethTrace::PreStateTracer(PreStateFrame::Default(pre_state_mode)) => { for (addr, acc) in pre_state_mode.0 { let expected_acc = expected.get(&addr).unwrap(); assert_eq!(acc.balance, expected_acc.balance); assert_eq!(acc.nonce, expected_acc.nonce); let expected_storage = &expected_acc.storage; for (slot, value) in acc.storage { assert_eq!(value, *expected_storage.get(&slot).unwrap()) } } } _ => unreachable!(), } } #[tokio::test(flavor = "multi_thread")] async fn test_debug_trace_transaction_pre_state_tracer() { let node_config = NodeConfig::test().with_hardfork(Some(EthereumHardfork::Prague.into())); let (api, handle) = spawn(node_config).await; let provider = crate::utils::http_provider(&handle.http_endpoint()); let wallets = handle.dev_wallets().collect::>(); let from = wallets[0].address(); api.anvil_add_balance(from, U256::MAX).await.unwrap(); api.anvil_add_balance(wallets[1].address(), U256::MAX).await.unwrap(); let multicall_contract = Multicall::deploy(&provider).await.unwrap(); let simple_storage_contract = SimpleStorage::deploy(&provider, "init value".to_string()).await.unwrap(); let set_value = simple_storage_contract.setValue("bar".to_string()); let set_value_calldata = set_value.calldata(); let internal_call_tx_builder = multicall_contract.aggregate(vec![Multicall::Call { target: *simple_storage_contract.address(), callData: set_value_calldata.to_owned(), }]); let internal_call_tx_calldata = internal_call_tx_builder.calldata().to_owned(); let internal_call_tx = TransactionRequest::default() .from(wallets[1].address()) .to(*multicall_contract.address()) .with_input(internal_call_tx_calldata) .with_gas_limit(1_000_000) .with_max_fee_per_gas(100_000_000_000) .with_max_priority_fee_per_gas(100_000_000_000); let receipt = provider .send_transaction(internal_call_tx.into()) .await .unwrap() .get_receipt() .await .unwrap(); let result = api .debug_trace_transaction( receipt.transaction_hash, GethDebugTracingOptions::prestate_tracer(PreStateConfig::default()), ) .await .unwrap(); let expected = r#" { "0x0000000000000000000000000000000000000000": { "balance": "1206031000000000" }, "0x5fbdb2315678afecb367f032d93f642f64180aa3": { "balance": "0x0", "nonce": 1 }, "0x70997970c51812dc3a010c7d01b50e0d17dc79c8": { "balance": "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" }, "0xe7f1725e7734ce288f8367e1bb143e90bb3f0512": { "balance": "0x0", "nonce": 1, "storage": { "0x0000000000000000000000000000000000000000000000000000000000000000": "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000001": "0x696e69742076616c756500000000000000000000000000000000000000000014", "0xb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf6": "0x0000000000000000000000000000000000000000000000000000000000000000" } } } "#; let expected: HashMap = serde_json::from_str(expected).unwrap(); match result { GethTrace::PreStateTracer(PreStateFrame::Default(pre_state_mode)) => { for (addr, acc) in pre_state_mode.0 { let expected_acc = expected.get(&addr).unwrap(); assert_eq!(acc.balance, expected_acc.balance); assert_eq!(acc.nonce, expected_acc.nonce); let expected_storage = &expected_acc.storage; for (slot, value) in acc.storage { assert_eq!(value, *expected_storage.get(&slot).unwrap()) } } } _ => unreachable!(), } } #[tokio::test(flavor = "multi_thread")] async fn test_trace_replay_block_transactions_local() { let (api, handle) = spawn(NodeConfig::test()).await; let provider = handle.http_provider(); api.anvil_set_auto_mine(false).await.unwrap(); let accounts = handle.dev_wallets().collect::>(); let from = accounts[0].address(); let to = accounts[1].address(); let amount = U256::from(1000000u64); // Send first transaction let tx1 = TransactionRequest::default().to(to).value(amount).from(from); let tx1 = WithOtherFields::new(tx1); let pending_tx1 = provider.send_transaction(tx1).await.unwrap(); // Send second transaction with different value let tx2 = TransactionRequest::default().to(to).value(amount).from(from); let tx2 = WithOtherFields::new(tx2); let pending_tx2 = provider.send_transaction(tx2).await.unwrap(); api.mine_one().await; let receipt1 = pending_tx1.get_receipt().await.unwrap(); let receipt2 = pending_tx2.get_receipt().await.unwrap(); let block_number = receipt2.block_number.unwrap(); // Replay the block transactions with call trace type // Pass block number as hex string as per Ethereum RPC spec let results = api .trace_replay_block_transactions( block_number.into(), vec![TraceType::Trace, TraceType::VmTrace, TraceType::StateDiff].into_iter().collect(), ) .await .unwrap(); // Verify we have traces for both transactions assert_eq!(results.len(), 2, "Should have traces for 2 transactions"); // Verify first transaction hash matches assert_eq!(results[0].transaction_hash, receipt1.transaction_hash); // Verify second transaction hash matches assert_eq!(results[1].transaction_hash, receipt2.transaction_hash); // Verify trace types are present and accurate for result in results { let full_trace = &result.full_trace; // Verify Trace (call trace) is present and accurate assert!(!full_trace.trace.is_empty(), "Trace should not be empty"); let first_trace = &full_trace.trace[0]; match &first_trace.action { Action::Call(call) => { assert_eq!(call.from, from, "Call from address should match"); assert_eq!(call.to, to, "Call to address should match"); } _ => panic!("Expected Call action, got {:?}", first_trace.action), } // Verify VmTrace is present assert!(full_trace.vm_trace.is_some(), "VmTrace should be present when requested"); // Verify StateDiff is present assert!(full_trace.state_diff.is_some(), "StateDiff should be present when requested"); // Verify balance change is correct in state diff let ChangedType:: { from, to } = full_trace.state_diff.as_ref().unwrap().get(&to).unwrap().balance.as_changed().unwrap(); assert_eq!( to.checked_sub(*from).unwrap(), amount, "Incorrect balance change in state diff" ); } } ================================================ FILE: crates/anvil/tests/it/transaction.rs ================================================ use crate::{ abi::{Greeter, Multicall, SimpleStorage}, utils::{connect_pubsub, http_provider_with_signer}, }; use alloy_consensus::Transaction; use alloy_network::{EthereumWallet, ReceiptResponse, TransactionBuilder, TransactionResponse}; use alloy_primitives::{Address, Bytes, FixedBytes, U256, address, hex, map::B256HashSet}; use alloy_provider::{Provider, WsConnect}; use alloy_rpc_types::{ AccessList, AccessListItem, BlockId, BlockNumberOrTag, BlockOverrides, BlockTransactions, TransactionRequest, state::{AccountOverride, EvmOverrides, StateOverride, StateOverridesBuilder}, }; use alloy_serde::WithOtherFields; use alloy_sol_types::SolValue; use anvil::{NodeConfig, spawn}; use eyre::Ok; use foundry_evm::hardfork::EthereumHardfork; use futures::{FutureExt, StreamExt, future::join_all}; use revm::primitives::eip7825::TX_GAS_LIMIT_CAP; use std::{str::FromStr, time::Duration}; use tokio::time::timeout; #[tokio::test(flavor = "multi_thread")] async fn can_transfer_eth() { let (_api, handle) = spawn(NodeConfig::test()).await; let provider = handle.http_provider(); let accounts = handle.dev_wallets().collect::>(); let from = accounts[0].address(); let to = accounts[1].address(); let nonce = provider.get_transaction_count(from).await.unwrap(); assert!(nonce == 0); let balance_before = provider.get_balance(to).await.unwrap(); let amount = handle.genesis_balance().checked_div(U256::from(2u64)).unwrap(); // craft the tx // specify the `from` field so that the client knows which account to use let tx = TransactionRequest::default().to(to).value(amount).from(from); let tx = WithOtherFields::new(tx); // broadcast it via the eth_sendTransaction API let tx = provider.send_transaction(tx).await.unwrap(); let tx = tx.get_receipt().await.unwrap(); assert_eq!(tx.block_number, Some(1)); assert_eq!(tx.transaction_index, Some(0)); let nonce = provider.get_transaction_count(from).await.unwrap(); assert_eq!(nonce, 1); let to_balance = provider.get_balance(to).await.unwrap(); assert_eq!(balance_before.saturating_add(amount), to_balance); } #[tokio::test(flavor = "multi_thread")] async fn can_order_transactions() { let (api, handle) = spawn(NodeConfig::test()).await; let provider = handle.http_provider(); // disable automine api.anvil_set_auto_mine(false).await.unwrap(); let accounts = handle.dev_wallets().collect::>(); let from = accounts[0].address(); let to = accounts[1].address(); let amount = handle.genesis_balance().checked_div(U256::from(2u64)).unwrap(); let gas_price = provider.get_gas_price().await.unwrap(); // craft the tx with lower price let mut tx = TransactionRequest::default().to(to).from(from).value(amount); tx.set_gas_price(gas_price); let tx = WithOtherFields::new(tx); let tx_lower = provider.send_transaction(tx).await.unwrap(); // craft the tx with higher price let mut tx = TransactionRequest::default().to(from).from(to).value(amount); tx.set_gas_price(gas_price + 1); let tx = WithOtherFields::new(tx); let tx_higher = provider.send_transaction(tx).await.unwrap(); // manually mine the block with the transactions api.mine_one().await; let higher_price = tx_higher.get_receipt().await.unwrap().transaction_hash; let lower_price = tx_lower.get_receipt().await.unwrap().transaction_hash; // get the block, await receipts let block = provider.get_block(BlockId::latest()).await.unwrap().unwrap(); assert_eq!(block.transactions, BlockTransactions::Hashes(vec![higher_price, lower_price])) } #[tokio::test(flavor = "multi_thread")] async fn can_respect_nonces() { let (api, handle) = spawn(NodeConfig::test()).await; let provider = handle.http_provider(); let accounts = handle.dev_wallets().collect::>(); let from = accounts[0].address(); let to = accounts[1].address(); let nonce = provider.get_transaction_count(from).await.unwrap(); let amount = handle.genesis_balance().checked_div(U256::from(3u64)).unwrap(); let tx = TransactionRequest::default().to(to).value(amount).from(from).nonce(nonce + 1); let tx = WithOtherFields::new(tx); // send the transaction with higher nonce than on chain let higher_pending_tx = provider.send_transaction(tx).await.unwrap(); // ensure the listener for ready transactions times out let mut listener = api.new_ready_transactions(); let res = timeout(Duration::from_millis(1500), listener.next()).await; res.unwrap_err(); let tx = TransactionRequest::default().to(to).value(amount).from(from).nonce(nonce); let tx = WithOtherFields::new(tx); // send with the actual nonce which is mined immediately let tx = provider.send_transaction(tx).await.unwrap(); let tx = tx.get_receipt().await.unwrap(); // this will unblock the currently pending tx let higher_tx = higher_pending_tx.get_receipt().await.unwrap(); // Awaits endlessly here due to alloy/#389 let block = provider.get_block(1.into()).await.unwrap().unwrap(); assert_eq!(2, block.transactions.len()); assert_eq!( BlockTransactions::Hashes(vec![tx.transaction_hash, higher_tx.transaction_hash]), block.transactions ); } #[tokio::test(flavor = "multi_thread")] async fn can_replace_transaction() { let (api, handle) = spawn(NodeConfig::test()).await; // disable auto mining api.anvil_set_auto_mine(false).await.unwrap(); let provider = handle.http_provider(); let accounts = handle.dev_wallets().collect::>(); let from = accounts[0].address(); let to = accounts[1].address(); let nonce = provider.get_transaction_count(from).await.unwrap(); let gas_price = provider.get_gas_price().await.unwrap(); let amount = handle.genesis_balance().checked_div(U256::from(3u64)).unwrap(); let tx = TransactionRequest::default().to(to).value(amount).from(from).nonce(nonce); let mut tx = WithOtherFields::new(tx); tx.set_gas_price(gas_price); // send transaction with lower gas price let _lower_priced_pending_tx = provider.send_transaction(tx.clone()).await.unwrap(); tx.set_gas_price(gas_price + 1); // send the same transaction with higher gas price let higher_priced_pending_tx = provider.send_transaction(tx).await.unwrap(); let higher_tx_hash = *higher_priced_pending_tx.tx_hash(); // mine exactly one block api.mine_one().await; let block = provider.get_block(1.into()).await.unwrap().unwrap(); assert_eq!(block.transactions.len(), 1); assert_eq!(BlockTransactions::Hashes(vec![higher_tx_hash]), block.transactions); // verify the higher priced transaction was included let higher_priced_receipt = higher_priced_pending_tx.get_receipt().await.unwrap(); assert_eq!(higher_priced_receipt.transaction_hash, higher_tx_hash); // verify only one transaction was included in the block (lower priced was replaced) assert_eq!(1, block.transactions.len()); assert_eq!( BlockTransactions::Hashes(vec![higher_priced_receipt.transaction_hash]), block.transactions ); } #[tokio::test(flavor = "multi_thread")] async fn can_reject_too_high_gas_limits() { let (api, handle) = spawn(NodeConfig::test()).await; let provider = handle.http_provider(); let accounts = handle.dev_wallets().collect::>(); let from = accounts[0].address(); let to = accounts[1].address(); let gas_limit = api.gas_limit().to::(); let amount = handle.genesis_balance().checked_div(U256::from(3u64)).unwrap(); let tx = TransactionRequest::default().to(to).value(amount).from(from).with_gas_limit(gas_limit); let mut tx = WithOtherFields::new(tx); // send transaction with the exact gas limit let pending = provider.send_transaction(tx.clone()).await.unwrap(); let pending_receipt = pending.get_receipt().await; assert!(pending_receipt.is_ok()); tx.set_gas_limit(gas_limit + 1); // send transaction with higher gas limit let pending = provider.send_transaction(tx.clone()).await; assert!(pending.is_err()); let err = pending.unwrap_err(); assert!(err.to_string().contains("gas too high")); api.anvil_set_balance(from, U256::MAX).await.unwrap(); tx.set_gas_limit(gas_limit); let pending = provider.send_transaction(tx).await; let _ = pending.unwrap(); } // #[tokio::test(flavor = "multi_thread")] async fn can_mine_large_gas_limit() { let (_, handle) = spawn(NodeConfig::test().disable_block_gas_limit(true)).await; let provider = handle.http_provider(); let accounts = handle.dev_wallets().collect::>(); let from = accounts[0].address(); let to = accounts[1].address(); let gas_limit = anvil::DEFAULT_GAS_LIMIT; let amount = handle.genesis_balance().checked_div(U256::from(3u64)).unwrap(); let tx = TransactionRequest::default().to(to).value(amount).from(from).with_gas_limit(gas_limit); // send transaction with higher gas limit let pending = provider.send_transaction(WithOtherFields::new(tx)).await.unwrap(); let _resp = pending.get_receipt().await.unwrap(); } #[tokio::test(flavor = "multi_thread")] async fn can_reject_underpriced_replacement() { let (api, handle) = spawn(NodeConfig::test()).await; // disable auto mining api.anvil_set_auto_mine(false).await.unwrap(); let provider = handle.http_provider(); let accounts = handle.dev_wallets().collect::>(); let from = accounts[0].address(); let to = accounts[1].address(); let nonce = provider.get_transaction_count(from).await.unwrap(); let gas_price = provider.get_gas_price().await.unwrap(); let amount = handle.genesis_balance().checked_div(U256::from(3u64)).unwrap(); let tx = TransactionRequest::default().to(to).value(amount).from(from).nonce(nonce); let mut tx = WithOtherFields::new(tx); tx.set_gas_price(gas_price + 1); // send transaction with higher gas price let higher_priced_pending_tx = provider.send_transaction(tx.clone()).await.unwrap(); tx.set_gas_price(gas_price); // send the same transaction with lower gas price let lower_priced_pending_tx = provider.send_transaction(tx).await; let replacement_err = lower_priced_pending_tx.unwrap_err(); assert!(replacement_err.to_string().contains("replacement transaction underpriced")); // mine exactly one block api.mine_one().await; let higher_priced_receipt = higher_priced_pending_tx.get_receipt().await.unwrap(); // ensure that only the higher priced tx was mined let block = provider.get_block(1.into()).await.unwrap().unwrap(); assert_eq!(1, block.transactions.len()); assert_eq!( BlockTransactions::Hashes(vec![higher_priced_receipt.transaction_hash]), block.transactions ); } #[tokio::test(flavor = "multi_thread")] async fn can_deploy_greeter_http() { let (_api, handle) = spawn(NodeConfig::test()).await; let wallet = handle.dev_wallets().next().unwrap(); let signer: EthereumWallet = wallet.clone().into(); let alloy_provider = http_provider_with_signer(&handle.http_endpoint(), signer); let alloy_greeter_addr = Greeter::deploy_builder(alloy_provider.clone(), "Hello World!".to_string()) // .legacy() unimplemented! in alloy .deploy() .await .unwrap(); let alloy_greeter = Greeter::new(alloy_greeter_addr, alloy_provider); let greeting = alloy_greeter.greet().call().await.unwrap(); assert_eq!("Hello World!", greeting); } #[tokio::test(flavor = "multi_thread")] async fn can_deploy_and_mine_manually() { let (api, handle) = spawn(NodeConfig::test()).await; // can mine in auto-mine mode api.evm_mine(None).await.unwrap(); // disable auto mine api.anvil_set_auto_mine(false).await.unwrap(); // can mine in manual mode api.evm_mine(None).await.unwrap(); let provider = handle.http_provider(); let wallet = handle.dev_wallets().next().unwrap(); let from = wallet.address(); let greeter_builder = Greeter::deploy_builder(provider.clone(), "Hello World!".to_string()).from(from); let greeter_calldata = greeter_builder.calldata(); let tx = TransactionRequest::default().from(from).with_input(greeter_calldata.to_owned()); let tx = WithOtherFields::new(tx); let tx = provider.send_transaction(tx).await.unwrap(); // mine block with tx manually api.evm_mine(None).await.unwrap(); let receipt = tx.get_receipt().await.unwrap(); 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); let set_greeting = greeter_contract.setGreeting("Another Message".to_string()); let tx = set_greeting.send().await.unwrap(); // mine block manually api.evm_mine(None).await.unwrap(); let _tx = tx.get_receipt().await.unwrap(); let greeting = greeter_contract.greet().call().await.unwrap(); assert_eq!("Another Message", greeting); } #[tokio::test(flavor = "multi_thread")] async fn can_mine_automatically() { let (api, handle) = spawn(NodeConfig::test()).await; let provider = handle.http_provider(); // disable auto mine api.anvil_set_auto_mine(false).await.unwrap(); let wallet = handle.dev_wallets().next().unwrap(); let greeter_builder = Greeter::deploy_builder(provider.clone(), "Hello World!".to_string()) .from(wallet.address()); let greeter_calldata = greeter_builder.calldata(); let tx = TransactionRequest::default() .from(wallet.address()) .with_input(greeter_calldata.to_owned()); let tx = WithOtherFields::new(tx); let sent_tx = provider.send_transaction(tx).await.unwrap(); // re-enable auto mine api.anvil_set_auto_mine(true).await.unwrap(); let receipt = sent_tx.get_receipt().await.unwrap(); assert_eq!(receipt.block_number, Some(1)); } #[tokio::test(flavor = "multi_thread")] async fn can_call_greeter_historic() { let (api, handle) = spawn(NodeConfig::test()).await; let provider = handle.http_provider(); let wallet = handle.dev_wallets().next().unwrap(); let greeter_addr = Greeter::deploy_builder(provider.clone(), "Hello World!".to_string()) .from(wallet.address()) .deploy() .await .unwrap(); let greeter_contract = Greeter::new(greeter_addr, provider.clone()); let greeting = greeter_contract.greet().call().await.unwrap(); assert_eq!("Hello World!", greeting); let block_number = provider.get_block_number().await.unwrap(); let _receipt = greeter_contract .setGreeting("Another Message".to_string()) .send() .await .unwrap() .get_receipt() .await .unwrap(); let greeting = greeter_contract.greet().call().await.unwrap(); assert_eq!("Another Message", greeting); // min api.mine_one().await; // returns previous state let greeting = greeter_contract.greet().block(BlockId::Number(block_number.into())).call().await.unwrap(); assert_eq!("Hello World!", greeting); } #[tokio::test(flavor = "multi_thread")] async fn can_deploy_greeter_ws() { let (_api, handle) = spawn(NodeConfig::test()).await; let provider = handle.ws_provider(); let wallet = handle.dev_wallets().next().unwrap(); let greeter_addr = Greeter::deploy_builder(provider.clone(), "Hello World!".to_string()) .from(wallet.address()) // .legacy() unimplemented! in alloy .deploy() .await .unwrap(); let greeter_contract = Greeter::new(greeter_addr, provider.clone()); let greeting = greeter_contract.greet().call().await.unwrap(); assert_eq!("Hello World!", greeting); } #[tokio::test(flavor = "multi_thread")] async fn can_deploy_get_code() { let (_api, handle) = spawn(NodeConfig::test()).await; let provider = handle.ws_provider(); let wallet = handle.dev_wallets().next().unwrap(); let greeter_addr = Greeter::deploy_builder(provider.clone(), "Hello World!".to_string()) .from(wallet.address()) .deploy() .await .unwrap(); let code = provider.get_code_at(greeter_addr).await.unwrap(); assert!(!code.as_ref().is_empty()); } #[tokio::test(flavor = "multi_thread")] async fn get_blocktimestamp_works() { let (api, handle) = spawn(NodeConfig::test()).await; let provider = handle.http_provider(); let contract = Multicall::deploy(provider.clone()).await.unwrap(); 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(); assert_eq!(timestamp.to::(), latest_block.header.timestamp); // repeat call same result 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(); assert_eq!(timestamp, U256::from(next_timestamp)); // repeat call same result let timestamp = contract.getCurrentBlockTimestamp().block(BlockId::pending()).call().await.unwrap(); assert_eq!(timestamp, U256::from(next_timestamp)); } #[tokio::test(flavor = "multi_thread")] async fn call_past_state() { let (api, handle) = spawn(NodeConfig::test()).await; let provider = handle.http_provider(); let wallet = handle.dev_wallets().next().unwrap(); let contract_addr = SimpleStorage::deploy_builder(provider.clone(), "initial value".to_string()) .from(wallet.address()) .deploy() .await .unwrap(); let contract = SimpleStorage::new(contract_addr, provider.clone()); let deployed_block = provider.get_block_number().await.unwrap(); let value = contract.getValue().call().await.unwrap(); assert_eq!(value, "initial value"); let gas_price = api.gas_price(); let set_tx = contract.setValue("hi".to_string()).gas_price(gas_price + 1); let _receipt = set_tx.send().await.unwrap().get_receipt().await.unwrap(); // assert new value let value = contract.getValue().call().await.unwrap(); assert_eq!(value, "hi"); // assert previous value let value = contract.getValue().block(BlockId::Number(deployed_block.into())).call().await.unwrap(); 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, "initial value"); } #[tokio::test(flavor = "multi_thread")] async fn can_handle_multiple_concurrent_transfers_with_same_nonce() { let (_api, handle) = spawn(NodeConfig::test()).await; let provider = handle.ws_provider(); let accounts = handle.dev_wallets().collect::>(); let from = accounts[0].address(); let to = accounts[1].address(); let nonce = provider.get_transaction_count(from).await.unwrap(); // explicitly set the nonce let tx = TransactionRequest::default() .to(to) .value(U256::from(100)) .from(from) .nonce(nonce) .with_gas_limit(21000); let tx = WithOtherFields::new(tx); let mut tasks = Vec::new(); for _ in 0..10 { let tx = tx.clone(); let provider = provider.clone(); let task = tokio::task::spawn(async move { provider.send_transaction(tx).await.unwrap().get_receipt().await }); tasks.push(task); } // only one succeeded let successful_tx = join_all(tasks).await.into_iter().filter(|res| res.as_ref().is_ok()).count(); assert_eq!(successful_tx, 1); assert_eq!(provider.get_transaction_count(from).await.unwrap(), 1u64); } #[tokio::test(flavor = "multi_thread")] async fn can_handle_multiple_concurrent_deploys_with_same_nonce() { let (_api, handle) = spawn(NodeConfig::test()).await; let provider = handle.ws_provider(); let wallet = handle.dev_wallets().next().unwrap(); let from = wallet.address(); let nonce = provider.get_transaction_count(from).await.unwrap(); let mut tasks = Vec::new(); let greeter = Greeter::deploy_builder(provider.clone(), "Hello World!".to_string()); let greeter_calldata = greeter.calldata(); let tx = TransactionRequest::default() .from(from) .with_input(greeter_calldata.to_owned()) .nonce(nonce) .with_gas_limit(300_000); let tx = WithOtherFields::new(tx); for _ in 0..10 { let provider = provider.clone(); let tx = tx.clone(); let task = tokio::task::spawn(async move { Ok(provider.send_transaction(tx).await?.get_receipt().await.unwrap()) }); tasks.push(task); } // only one succeeded let successful_tx = join_all(tasks).await.into_iter().filter(|res| res.as_ref().unwrap().is_ok()).count(); assert_eq!(successful_tx, 1); assert_eq!(provider.get_transaction_count(from).await.unwrap(), 1u64); } #[tokio::test(flavor = "multi_thread")] async fn can_handle_multiple_concurrent_transactions_with_same_nonce() { let (_api, handle) = spawn(NodeConfig::test()).await; let provider = handle.ws_provider(); let wallet = handle.dev_wallets().next().unwrap(); let from = wallet.address(); let greeter_contract = Greeter::deploy(provider.clone(), "Hello World!".to_string()).await.unwrap(); let nonce = provider.get_transaction_count(from).await.unwrap(); let mut tasks = Vec::new(); let deploy = Greeter::deploy_builder(provider.clone(), "Hello World!".to_string()); let deploy_calldata = deploy.calldata(); let deploy_tx = TransactionRequest::default() .from(from) .with_input(deploy_calldata.to_owned()) .nonce(nonce) .with_gas_limit(300_000); let deploy_tx = WithOtherFields::new(deploy_tx); let set_greeting = greeter_contract.setGreeting("Hello".to_string()); let set_greeting_calldata = set_greeting.calldata(); let set_greeting_tx = TransactionRequest::default() .from(from) .with_input(set_greeting_calldata.to_owned()) .nonce(nonce) .with_gas_limit(300_000); let set_greeting_tx = WithOtherFields::new(set_greeting_tx); for idx in 0..10 { let provider = provider.clone(); let task = if idx % 2 == 0 { let tx = deploy_tx.clone(); tokio::task::spawn(async move { Ok(provider.send_transaction(tx).await?.get_receipt().await.unwrap()) }) } else { let tx = set_greeting_tx.clone(); tokio::task::spawn(async move { Ok(provider.send_transaction(tx).await?.get_receipt().await.unwrap()) }) }; tasks.push(task); } // only one succeeded let successful_tx = join_all(tasks).await.into_iter().filter(|res| res.as_ref().unwrap().is_ok()).count(); assert_eq!(successful_tx, 1); assert_eq!(provider.get_transaction_count(from).await.unwrap(), nonce + 1); } #[tokio::test(flavor = "multi_thread")] async fn can_get_pending_transaction() { let (api, handle) = spawn(NodeConfig::test()).await; // disable auto mining so we can check if we can return pending tx from the mempool api.anvil_set_auto_mine(false).await.unwrap(); let provider = handle.http_provider(); let from = handle.dev_wallets().next().unwrap().address(); let tx = TransactionRequest::default().from(from).value(U256::from(1337)).to(Address::random()); let tx = WithOtherFields::new(tx); let tx = provider.send_transaction(tx).await.unwrap(); let pending = provider.get_transaction_by_hash(*tx.tx_hash()).await; assert!(pending.is_ok()); api.mine_one().await; let mined = provider.get_transaction_by_hash(*tx.tx_hash()).await.unwrap().unwrap(); 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; // first test the pending tx, disable auto mine api.anvil_set_auto_mine(false).await.unwrap(); let provider = handle.http_provider(); let from = handle.dev_wallets().next().unwrap().address(); let tx = TransactionRequest::default().from(from).value(U256::from(1488)).to(Address::random()); let tx = WithOtherFields::new(tx); let tx = provider.send_transaction(tx).await.unwrap(); let res1 = api.raw_transaction(*tx.tx_hash()).await; assert!(res1.is_ok()); api.mine_one().await; let res2 = api.raw_transaction(*tx.tx_hash()).await; assert_eq!(res1.unwrap(), res2.unwrap()); } #[tokio::test(flavor = "multi_thread")] async fn test_first_nonce_is_zero() { let (api, handle) = spawn(NodeConfig::test()).await; api.anvil_set_auto_mine(false).await.unwrap(); let provider = handle.http_provider(); let from = handle.dev_wallets().next().unwrap().address(); let nonce = provider.get_transaction_count(from).block_id(BlockId::pending()).await.unwrap(); assert_eq!(nonce, 0); } #[tokio::test(flavor = "multi_thread")] async fn can_handle_different_sender_nonce_calculation() { let (api, handle) = spawn(NodeConfig::test()).await; api.anvil_set_auto_mine(false).await.unwrap(); let provider = handle.http_provider(); let accounts = handle.dev_wallets().collect::>(); let from_first = accounts[0].address(); let from_second = accounts[1].address(); let tx_count = 10u64; // send a bunch of tx to the mempool and check nonce is returned correctly for idx in 1..=tx_count { let tx_from_first = TransactionRequest::default() .from(from_first) .value(U256::from(1337u64)) .to(Address::random()); let tx_from_first = WithOtherFields::new(tx_from_first); let _tx = provider.send_transaction(tx_from_first).await.unwrap(); let nonce_from_first = provider.get_transaction_count(from_first).block_id(BlockId::pending()).await.unwrap(); assert_eq!(nonce_from_first, idx); let tx_from_second = TransactionRequest::default() .from(from_second) .value(U256::from(1337u64)) .to(Address::random()); let tx_from_second = WithOtherFields::new(tx_from_second); let _tx = provider.send_transaction(tx_from_second).await.unwrap(); let nonce_from_second = provider.get_transaction_count(from_second).block_id(BlockId::pending()).await.unwrap(); assert_eq!(nonce_from_second, idx); } } #[tokio::test(flavor = "multi_thread")] async fn includes_pending_tx_for_transaction_count() { let (api, handle) = spawn(NodeConfig::test()).await; api.anvil_set_auto_mine(false).await.unwrap(); let provider = handle.http_provider(); let from = handle.dev_wallets().next().unwrap().address(); let tx_count = 10u64; // send a bunch of tx to the mempool and check nonce is returned correctly for idx in 1..=tx_count { let tx = TransactionRequest::default().from(from).value(U256::from(1337)).to(Address::random()); let tx = WithOtherFields::new(tx); let _tx = provider.send_transaction(tx).await.unwrap(); let nonce = provider.get_transaction_count(from).block_id(BlockId::pending()).await.unwrap(); assert_eq!(nonce, idx); } api.mine_one().await; let nonce = provider.get_transaction_count(from).block_id(BlockId::pending()).await.unwrap(); assert_eq!(nonce, tx_count); } #[tokio::test(flavor = "multi_thread")] async fn can_get_historic_info() { let (_api, handle) = spawn(NodeConfig::test()).await; let provider = handle.http_provider(); let accounts = handle.dev_wallets().collect::>(); let from = accounts[0].address(); let to = accounts[1].address(); let amount = handle.genesis_balance().checked_div(U256::from(2u64)).unwrap(); let tx = TransactionRequest::default().to(to).value(amount).from(from); let tx = WithOtherFields::new(tx); let tx = provider.send_transaction(tx).await.unwrap(); let _ = tx.get_receipt().await.unwrap(); let nonce_pre = provider.get_transaction_count(from).block_id(BlockId::number(0)).await.unwrap(); let nonce_post = provider.get_transaction_count(from).await.unwrap(); assert!(nonce_pre < nonce_post); let balance_pre = provider.get_balance(from).block_id(BlockId::number(0)).await.unwrap(); let balance_post = provider.get_balance(from).await.unwrap(); assert!(balance_post < balance_pre); let to_balance = provider.get_balance(to).await.unwrap(); assert_eq!(balance_pre.saturating_add(amount), to_balance); } // #[tokio::test(flavor = "multi_thread")] async fn test_tx_receipt() { let (_api, handle) = spawn(NodeConfig::test()).await; let wallet = handle.dev_wallets().next().unwrap(); let provider = handle.http_provider(); let tx = TransactionRequest::default().to(Address::random()).value(U256::from(1337)); let tx = WithOtherFields::new(tx); let tx = provider.send_transaction(tx).await.unwrap().get_receipt().await.unwrap(); assert!(tx.to.is_some()); let greeter_deploy = Greeter::deploy_builder(provider.clone(), "Hello World!".to_string()); let greeter_calldata = greeter_deploy.calldata(); let tx = TransactionRequest::default() .from(wallet.address()) .with_input(greeter_calldata.to_owned()); let tx = WithOtherFields::new(tx); let tx = provider.send_transaction(tx).await.unwrap().get_receipt().await.unwrap(); // `to` field is none if it's a contract creation transaction: https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_gettransactionreceipt assert!(tx.to.is_none()); assert!(tx.contract_address.is_some()); } // #[tokio::test(flavor = "multi_thread")] async fn test_reverted_contract_creation_has_contract_address() { let (_api, handle) = spawn(NodeConfig::test()).await; let provider = handle.http_provider(); let wallet = handle.dev_wallets().next().unwrap(); // Init code that immediately reverts: PUSH1 0x00 PUSH1 0x00 REVERT (0x60006000fd) let reverting_init_code = hex!("60006000fd"); let tx = TransactionRequest::default() .from(wallet.address()) .with_input(reverting_init_code.to_vec()); let tx = WithOtherFields::new(tx); let receipt = provider.send_transaction(tx).await.unwrap().get_receipt().await.unwrap(); // Transaction should have reverted assert!(!receipt.status()); // `to` field should be none (contract creation) assert!(receipt.to.is_none()); // `contractAddress` should still be set even though the transaction reverted // This matches geth's behavior: https://github.com/ethereum/go-ethereum/issues/27937 assert!( receipt.contract_address.is_some(), "contractAddress should be set for reverted contract creation" ); // Verify the computed address is correct (sender.create(nonce)) let expected_addr = wallet.address().create(0); assert_eq!(receipt.contract_address, Some(expected_addr)); } #[tokio::test(flavor = "multi_thread")] async fn can_stream_pending_transactions() { let (_api, handle) = spawn(NodeConfig::test().with_blocktime(Some(Duration::from_secs(2)))).await; let num_txs = 5; let provider = handle.http_provider(); let ws_provider = connect_pubsub(&handle.ws_endpoint()).await; let accounts = provider.get_accounts().await.unwrap(); let tx = TransactionRequest::default().from(accounts[0]).to(accounts[0]).value(U256::from(1e18)); let mut sending = futures::future::join_all( std::iter::repeat_n(tx.clone(), num_txs) .enumerate() .map(|(nonce, tx)| tx.nonce(nonce as u64)) .map(|tx| async { let tx = WithOtherFields::new(tx); provider.send_transaction(tx).await.unwrap().get_receipt().await.unwrap() }), ) .fuse(); let mut watch_tx_stream = provider .watch_pending_transactions() .await .unwrap() .into_stream() .flat_map(futures::stream::iter) .take(num_txs) .fuse(); let mut sub_tx_stream = ws_provider .subscribe_pending_transactions() .await .unwrap() .into_stream() .take(num_txs) .fuse(); let mut sent = None; let mut watch_received = Vec::with_capacity(num_txs); let mut sub_received = Vec::with_capacity(num_txs); loop { futures::select! { txs = sending => { sent = Some(txs) }, tx = watch_tx_stream.next() => { if let Some(tx) = tx { watch_received.push(tx); } }, tx = sub_tx_stream.next() => { if let Some(tx) = tx { sub_received.push(tx); } }, complete => unreachable!(), }; if watch_received.len() == num_txs && sub_received.len() == num_txs && let Some(sent) = &sent { assert_eq!(sent.len(), watch_received.len()); let sent_txs = sent.iter().map(|tx| tx.transaction_hash).collect::(); assert_eq!(sent_txs, watch_received.iter().copied().collect()); assert_eq!(sent_txs, sub_received.iter().copied().collect()); break; } } } #[tokio::test(flavor = "multi_thread")] async fn test_tx_access_list() { /// returns a String representation of the AccessList, with sorted /// keys (address) and storage slots fn access_list_to_sorted_string(a: AccessList) -> String { let mut a = a.0; a.sort_by_key(|v| v.address); let a = a .iter_mut() .map(|v| { v.storage_keys.sort(); (v.address, std::mem::take(&mut v.storage_keys)) }) .collect::>(); format!("{a:?}") } /// asserts that the two access lists are equal, by comparing their sorted /// string representation fn assert_access_list_eq(a: AccessList, b: AccessList) { assert_eq!(access_list_to_sorted_string(a), access_list_to_sorted_string(b)) } // We want to test a couple of things: // - When calling a contract with no storage read/write, it shouldn't be in the AL // - When a contract calls a contract, the latter one should be in the AL // - No precompiles should be in the AL // - The sender shouldn't be in the AL let (_api, handle) = spawn(NodeConfig::test()).await; let provider = handle.http_provider(); let sender = Address::random(); let other_acc = Address::random(); let multicall = Multicall::deploy(provider.clone()).await.unwrap(); let simple_storage = SimpleStorage::deploy(provider.clone(), "foo".to_string()).await.unwrap(); // when calling `setValue` on SimpleStorage, both the `lastSender` and `_value` storages are // modified The `_value` is a `string`, so the storage slots here (small string) are `0x1` // and `keccak(0x1)` let set_value = simple_storage.setValue("bar".to_string()); let set_value_calldata = set_value.calldata(); let set_value_tx = TransactionRequest::default() .from(sender) .to(*simple_storage.address()) .with_input(set_value_calldata.to_owned()); let set_value_tx = WithOtherFields::new(set_value_tx); let access_list = provider.create_access_list(&set_value_tx).await.unwrap(); // let set_value_tx = simple_storage.set_value("bar".to_string()).from(sender).tx; // let access_list = client.create_access_list(&set_value_tx, None).await.unwrap(); assert_access_list_eq( access_list.access_list, AccessList::from(vec![AccessListItem { address: *simple_storage.address(), storage_keys: vec![ FixedBytes::ZERO, FixedBytes::with_last_byte(1), FixedBytes::from_str( "0xb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf6", ) .unwrap(), ], }]), ); // With a subcall that fetches the balances of an account (`other_acc`), only the address // of this account should be in the Access List let call_tx = multicall.getEthBalance(other_acc); let call_tx_data = call_tx.calldata(); let call_tx = TransactionRequest::default() .from(sender) .to(*multicall.address()) .with_input(call_tx_data.to_owned()); let call_tx = WithOtherFields::new(call_tx); let access_list = provider.create_access_list(&call_tx).await.unwrap(); assert_access_list_eq( access_list.access_list, AccessList::from(vec![AccessListItem { address: other_acc, storage_keys: vec![] }]), ); // With a subcall to another contract, the AccessList should be the same as when calling the // subcontract directly (given that the proxy contract doesn't read/write any state) let subcall_tx = multicall.aggregate(vec![Multicall::Call { target: *simple_storage.address(), callData: set_value_calldata.to_owned(), }]); let subcall_tx_calldata = subcall_tx.calldata(); let subcall_tx = TransactionRequest::default() .from(sender) .to(*multicall.address()) .with_input(subcall_tx_calldata.to_owned()); let subcall_tx = WithOtherFields::new(subcall_tx); let access_list = provider.create_access_list(&subcall_tx).await.unwrap(); assert_access_list_eq( access_list.access_list, // H256::from_uint(&(1u64.into())), AccessList::from(vec![AccessListItem { address: *simple_storage.address(), storage_keys: vec![ FixedBytes::ZERO, FixedBytes::with_last_byte(1), FixedBytes::from_str( "0xb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf6", ) .unwrap(), ], }]), ); } // ensures that the gas estimate is running on pending block by default #[tokio::test(flavor = "multi_thread")] async fn estimates_gas_on_pending_by_default() { let (api, handle) = spawn(NodeConfig::test()).await; // disable auto mine api.anvil_set_auto_mine(false).await.unwrap(); let provider = handle.http_provider(); let wallet = handle.dev_wallets().next().unwrap(); let sender = wallet.address(); let recipient = Address::random(); let tx = TransactionRequest::default().from(sender).to(recipient).value(U256::from(1e18)); let tx = WithOtherFields::new(tx); let _pending = provider.send_transaction(tx).await.unwrap(); let tx = TransactionRequest::default() .from(recipient) .to(sender) .value(U256::from(1e10)) .input(Bytes::from(vec![0x42]).into()); api.estimate_gas(WithOtherFields::new(tx), None, EvmOverrides::default()).await.unwrap(); } #[tokio::test(flavor = "multi_thread")] async fn test_estimate_gas() { 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(recipient) .to(sender) .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, EvmOverrides::default()).await; assert!(error_result.is_err(), "Expected an error due to insufficient funds"); let error_message = error_result.unwrap_err().to_string(); assert!( error_message.contains("Insufficient funds for gas * price + value"), "Error message did not match expected: {error_message}" ); // Setup state override to simulate sufficient funds for the recipient. let addr = recipient; let account_override = AccountOverride { balance: Some(alloy_primitives::U256::from(1e18)), ..Default::default() }; let mut state_override = StateOverride::default(); state_override.insert(addr, account_override); // Estimate gas with state override implying sufficient funds. let gas_estimate = api .estimate_gas(WithOtherFields::new(tx), None, EvmOverrides::new(Some(state_override), None)) .await .expect("Failed to estimate gas with state override"); // Assert the gas estimate meets the expected minimum. 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; let provider = handle.http_provider(); let account = handle.dev_accounts().next().unwrap(); let gas = 21_000u64 - 1; let tx = TransactionRequest::default() .to(Address::random()) .value(U256::from(1337u64)) .from(account) .with_gas_limit(gas); let tx = WithOtherFields::new(tx); let resp = provider.send_transaction(tx).await; let err = resp.unwrap_err().to_string(); assert!(err.contains("intrinsic gas too low")); } // #[tokio::test(flavor = "multi_thread")] async fn can_call_with_high_gas_limit() { let (_api, handle) = spawn(NodeConfig::test().with_gas_limit(Some(100_000_000))).await; let provider = handle.http_provider(); 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); } #[tokio::test(flavor = "multi_thread")] async fn test_reject_eip1559_pre_london() { let (api, handle) = spawn(NodeConfig::test().with_hardfork(Some(EthereumHardfork::Berlin.into()))).await; let provider = handle.http_provider(); let gas_limit = api.gas_limit().to::(); let gas_price = api.gas_price(); let unsupported_call_builder = Greeter::deploy_builder(provider.clone(), "Hello World!".to_string()); let unsupported_calldata = unsupported_call_builder.calldata(); let unsup_tx = TransactionRequest::default() .from(handle.dev_accounts().next().unwrap()) .with_input(unsupported_calldata.to_owned()) .with_gas_limit(gas_limit) .with_max_fee_per_gas(gas_price) .with_max_priority_fee_per_gas(gas_price); let unsup_tx = WithOtherFields::new(unsup_tx); let unsupported = provider.send_transaction(unsup_tx).await.unwrap_err().to_string(); assert!(unsupported.contains("not supported by the current hardfork"), "{unsupported}"); let greeter_contract_addr = Greeter::deploy_builder(provider.clone(), "Hello World!".to_string()) .gas(gas_limit) .gas_price(gas_price) .deploy() .await .unwrap(); let greeter_contract = Greeter::new(greeter_contract_addr, provider); let greeting = greeter_contract.greet().call().await.unwrap(); assert_eq!("Hello World!", greeting); } // https://github.com/foundry-rs/foundry/issues/6931 #[tokio::test(flavor = "multi_thread")] async fn can_mine_multiple_in_block() { let (api, _handle) = spawn(NodeConfig::test()).await; // disable auto mine api.anvil_set_auto_mine(false).await.unwrap(); let tx = TransactionRequest { from: Some("0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266".parse().unwrap()), ..Default::default() }; // broadcast it via the eth_sendTransaction API let first = api.send_transaction(WithOtherFields::new(tx.clone())).await.unwrap(); let second = api.send_transaction(WithOtherFields::new(tx.clone())).await.unwrap(); api.anvil_mine(Some(U256::from(1)), Some(U256::ZERO)).await.unwrap(); let block = api.block_by_number(BlockNumberOrTag::Latest).await.unwrap().unwrap(); let txs = block.transactions.hashes().collect::>(); assert_eq!(txs, vec![first, second]); } // ensures that the gas estimate is running on pending block by default #[tokio::test(flavor = "multi_thread")] async fn can_estimate_gas_prague() { let (api, _handle) = spawn(NodeConfig::test().with_hardfork(Some(EthereumHardfork::Prague.into()))).await; // {"data":"0xcafebabe","from":"0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266","to":" // 0x70997970c51812dc3a010c7d01b50e0d17dc79c8"} let req = TransactionRequest::default() .with_input(hex!("0xcafebabe")) .with_from(address!("0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266")) .with_to(address!("0x70997970c51812dc3a010c7d01b50e0d17dc79c8")); api.estimate_gas(WithOtherFields::new(req), None, EvmOverrides::default()).await.unwrap(); } #[tokio::test(flavor = "multi_thread")] async fn can_send_tx_osaka_valid_with_limit_enabled() { let (_api, handle) = spawn( NodeConfig::test() .enable_tx_gas_limit(true) .with_hardfork(Some(EthereumHardfork::Osaka.into())), ) .await; let provider = handle.http_provider(); let wallet = handle.dev_wallets().next().unwrap(); let sender = wallet.address(); let recipient = Address::random(); let base_tx = TransactionRequest::default().from(sender).to(recipient).value(U256::from(1e18)); // gas limit below the cap is accepted let tx = base_tx.clone().gas_limit(TX_GAS_LIMIT_CAP - 1); let tx = WithOtherFields::new(tx); let pending_tx = provider.send_transaction(tx).await.unwrap(); let tx_receipt = pending_tx.get_receipt().await.unwrap(); assert!(tx_receipt.inner.inner.is_success()); // gas limit at the cap is accepted let tx = base_tx.clone().gas_limit(TX_GAS_LIMIT_CAP); let tx = WithOtherFields::new(tx); let pending_tx = provider.send_transaction(tx).await.unwrap(); let tx_receipt = pending_tx.get_receipt().await.unwrap(); assert!(tx_receipt.inner.inner.is_success()); // gas limit above the cap is rejected let tx = base_tx.clone().gas_limit(TX_GAS_LIMIT_CAP + 1); let tx = WithOtherFields::new(tx); let err = provider.send_transaction(tx).await.unwrap_err().to_string(); assert!( err.contains("intrinsic gas too high -- tx.gas_limit > env.cfg.tx_gas_limit_cap"), "{err}" ); } #[tokio::test(flavor = "multi_thread")] async fn can_send_tx_osaka_valid_with_limit_disabled() { let (_api, handle) = spawn(NodeConfig::test().with_hardfork(Some(EthereumHardfork::Osaka.into()))).await; let provider = handle.http_provider(); let wallet = handle.dev_wallets().next().unwrap(); let sender = wallet.address(); let recipient = Address::random(); let base_tx = TransactionRequest::default().from(sender).to(recipient).value(U256::from(1e18)); // gas limit below the cap is accepted let tx = base_tx.clone().gas_limit(TX_GAS_LIMIT_CAP - 1); let tx = WithOtherFields::new(tx); let pending_tx = provider.send_transaction(tx).await.unwrap(); let tx_receipt = pending_tx.get_receipt().await.unwrap(); assert!(tx_receipt.inner.inner.is_success()); // gas limit at the cap is accepted let tx = base_tx.clone().gas_limit(TX_GAS_LIMIT_CAP); let tx = WithOtherFields::new(tx); let pending_tx = provider.send_transaction(tx).await.unwrap(); let tx_receipt = pending_tx.get_receipt().await.unwrap(); assert!(tx_receipt.inner.inner.is_success()); // gas limit above the cap is accepted when the limit is disabled let tx = base_tx.clone().gas_limit(TX_GAS_LIMIT_CAP + 1); let tx = WithOtherFields::new(tx); let pending_tx = provider.send_transaction(tx).await.unwrap(); let tx_receipt = pending_tx.get_receipt().await.unwrap(); assert!(tx_receipt.inner.inner.is_success()); } #[tokio::test(flavor = "multi_thread")] async fn can_get_tx_by_sender_and_nonce() { let (api, handle) = spawn(NodeConfig::test()).await; let provider = handle.http_provider(); let accounts = handle.dev_wallets().collect::>(); let sender = accounts[0].address(); let recipient = accounts[1].address(); api.anvil_set_auto_mine(false).await.unwrap(); let mut tx_hashes = std::collections::BTreeMap::new(); // send 4 transactions from the same sender with consecutive nonces for i in 0..4 { let tx_request = TransactionRequest::default() .to(recipient) .value(U256::from(1000 + i)) .from(sender) .nonce(i as u64); let tx = WithOtherFields::new(tx_request); let pending_tx = provider.send_transaction(tx).await.unwrap(); tx_hashes.insert(i as u64, *pending_tx.tx_hash()); } // mine all transactions api.mine_one().await; for nonce in 0..4 { let result: Option = provider .client() .request("eth_getTransactionBySenderAndNonce", (sender, U256::from(nonce))) .await .unwrap(); assert!(result.is_some()); let found_tx = result.unwrap(); assert_eq!(found_tx.inner.nonce(), nonce); assert_eq!(found_tx.from(), sender); assert_eq!(found_tx.inner.to(), Some(recipient)); assert_eq!(found_tx.inner.value(), U256::from(1000 + nonce)); assert_eq!(found_tx.inner.tx_hash(), tx_hashes[&nonce]); } let result: Option = provider .client() .request("eth_getTransactionBySenderAndNonce", (sender, U256::from(999))) .await .unwrap(); assert!(result.is_none()); let different_sender = accounts[2].address(); let result: Option = provider .client() .request("eth_getTransactionBySenderAndNonce", (different_sender, U256::from(0))) .await .unwrap(); assert!(result.is_none()); // send a pending transaction with explicit nonce 4 let pending_tx_request = TransactionRequest::default().to(recipient).value(U256::from(5000)).from(sender).nonce(4); let tx = WithOtherFields::new(pending_tx_request); let pending_tx = provider.send_transaction(tx).await.unwrap(); // find the pending transaction with nonce 4 let result: Option = provider .client() .request("eth_getTransactionBySenderAndNonce", (sender, U256::from(4))) .await .unwrap(); assert!(result.is_some()); let found_tx = result.unwrap(); assert_eq!(found_tx.inner.nonce(), 4); assert_eq!(found_tx.inner.tx_hash(), *pending_tx.tx_hash()); api.mine_one().await; let result: Option = provider .client() .request("eth_getTransactionBySenderAndNonce", (sender, U256::from(4))) .await .unwrap(); assert!(result.is_some()); let found_tx = result.unwrap(); assert_eq!(found_tx.inner.nonce(), 4); } ================================================ FILE: crates/anvil/tests/it/txpool.rs ================================================ //! txpool related tests use alloy_network::{ReceiptResponse, TransactionBuilder}; use alloy_primitives::U256; use alloy_provider::{Provider, ext::TxPoolApi}; use alloy_rpc_types::TransactionRequest; use alloy_serde::WithOtherFields; use anvil::{NodeConfig, spawn}; #[tokio::test(flavor = "multi_thread")] async fn geth_txpool() { let (api, handle) = spawn(NodeConfig::test()).await; let provider = handle.http_provider(); api.anvil_set_auto_mine(false).await.unwrap(); let account = provider.get_accounts().await.unwrap().remove(0); let value = U256::from(42); let gas_price = 221435145689u128; let tx = TransactionRequest::default() .with_to(account) .with_from(account) .with_value(value) .with_gas_price(gas_price); let tx = WithOtherFields::new(tx); // send a few transactions for _ in 0..10 { 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 let status = provider.txpool_status().await.unwrap(); assert_eq!(status.pending, 10); assert_eq!(status.queued, 0); let inspect = provider.txpool_inspect().await.unwrap(); assert!(inspect.queued.is_empty()); let summary = inspect.pending.get(&account).unwrap(); for i in 0..10 { let tx_summary = summary.get(&i.to_string()).unwrap(); assert_eq!(tx_summary.gas_price, gas_price); assert_eq!(tx_summary.value, value); assert_eq!(tx_summary.gas, 21000); assert_eq!(tx_summary.to.unwrap(), account); } let content = provider.txpool_content().await.unwrap(); assert!(content.queued.is_empty()); let content = content.pending.get(&account).unwrap(); for nonce in 0..10 { assert!(content.contains_key(&nonce.to_string())); } } // Cf. https://github.com/foundry-rs/foundry/issues/11239 #[tokio::test(flavor = "multi_thread")] async fn accepts_spend_after_funding_when_pool_checks_disabled() { // Spawn with pool balance checks disabled let (api, handle) = spawn(NodeConfig::test().with_disable_pool_balance_checks(true)).await; let provider = handle.http_provider(); // Work with pending pool (no automine) api.anvil_set_auto_mine(false).await.unwrap(); // Funder is a dev account controlled by the node let funder = provider.get_accounts().await.unwrap().remove(0); // Recipient/spender is a random address with zero balance that we'll impersonate let spender = alloy_primitives::Address::random(); api.anvil_set_balance(spender, U256::from(0u64)).await.unwrap(); api.anvil_impersonate_account(spender).await.unwrap(); // Ensure tx1 (funding) has higher gas price so it's mined before tx2 within the same block let gas_price_fund = 2_000_000_000_000u128; // 2_000 gwei let gas_price_spend = 1_000_000_000u128; // 1 gwei let fund_value = U256::from(1_000_000_000_000_000_000u128); // 1 ether // tx1: fund spender from funder let tx1 = TransactionRequest::default() .with_from(funder) .with_to(spender) .with_value(fund_value) .with_gas_price(gas_price_fund); let tx1 = WithOtherFields::new(tx1); // tx2: spender attempts to send value greater than their pre-funding balance (0), // which would normally be rejected by pool balance checks, but should be accepted when disabled let spend_value = fund_value - U256::from(21_000u64) * U256::from(gas_price_spend); let tx2 = TransactionRequest::default() .with_from(spender) .with_to(funder) .with_value(spend_value) .with_gas_price(gas_price_spend); let tx2 = WithOtherFields::new(tx2); // Publish both transactions (funding first, then spend-before-funding-is-mined) let sent1 = provider.send_transaction(tx1).await.unwrap(); let sent2 = provider.send_transaction(tx2).await.unwrap(); // Both should be accepted into the pool (pending) let status = provider.txpool_status().await.unwrap(); assert_eq!(status.pending, 2); assert_eq!(status.queued, 0); // Mine a block and ensure both succeed api.evm_mine(None).await.unwrap(); let receipt1 = sent1.get_receipt().await.unwrap(); let receipt2 = sent2.get_receipt().await.unwrap(); assert!(receipt1.status()); assert!(receipt2.status()); } ================================================ FILE: crates/anvil/tests/it/utils.rs ================================================ use alloy_network::{Ethereum, EthereumWallet}; use alloy_provider::{ Identity, RootProvider, fillers::{ChainIdFiller, FillProvider, GasFiller, JoinFill, NonceFiller, WalletFiller}, }; use foundry_common::provider::{ ProviderBuilder, RetryProvider, RetryProviderWithSigner, get_http_provider, }; pub fn http_provider(http_endpoint: &str) -> RetryProvider { get_http_provider(http_endpoint) } pub fn http_provider_with_signer( http_endpoint: &str, signer: EthereumWallet, ) -> RetryProviderWithSigner { ProviderBuilder::new(http_endpoint) .build_with_wallet(signer) .expect("failed to build Alloy HTTP provider with signer") } pub fn ws_provider_with_signer( ws_endpoint: &str, signer: EthereumWallet, ) -> RetryProviderWithSigner { ProviderBuilder::new(ws_endpoint) .build_with_wallet(signer) .expect("failed to build Alloy WS provider with signer") } /// Currently required to get around pub async fn connect_pubsub(conn_str: &str) -> RootProvider { alloy_provider::ProviderBuilder::default().connect(conn_str).await.unwrap() } type PubsubSigner = FillProvider< JoinFill< JoinFill< Identity, JoinFill< GasFiller, JoinFill< alloy_provider::fillers::BlobGasFiller, JoinFill, >, >, >, WalletFiller, >, RootProvider, Ethereum, >; pub async fn connect_pubsub_with_wallet(conn_str: &str, wallet: EthereumWallet) -> PubsubSigner { alloy_provider::ProviderBuilder::new().wallet(wallet).connect(conn_str).await.unwrap() } pub async fn ipc_provider_with_wallet( ipc_endpoint: &str, wallet: EthereumWallet, ) -> RetryProviderWithSigner { ProviderBuilder::new(ipc_endpoint) .build_with_wallet(wallet) .expect("failed to build Alloy IPC provider with signer") } ================================================ FILE: crates/anvil/tests/it/wsapi.rs ================================================ //! general eth api tests with websocket provider use alloy_primitives::U256; use alloy_provider::Provider; use anvil::{NodeConfig, spawn}; #[tokio::test(flavor = "multi_thread")] async fn can_get_block_number_ws() { let (api, handle) = spawn(NodeConfig::test()).await; let block_num = api.block_number().unwrap(); assert_eq!(block_num, U256::ZERO); let provider = handle.ws_provider(); let num = provider.get_block_number().await.unwrap(); assert_eq!(num, block_num.to::()); } #[tokio::test(flavor = "multi_thread")] async fn can_dev_get_balance_ws() { let (_api, handle) = spawn(NodeConfig::test()).await; let provider = handle.ws_provider(); let genesis_balance = handle.genesis_balance(); for acc in handle.genesis_accounts() { let balance = provider.get_balance(acc).await.unwrap(); assert_eq!(balance, genesis_balance); } } ================================================ FILE: crates/cast/Cargo.toml ================================================ [package] name = "cast" description = "Command-line tool for performing Ethereum RPC calls" version.workspace = true edition.workspace = true rust-version.workspace = true authors.workspace = true license.workspace = true homepage.workspace = true repository.workspace = true [lints] workspace = true [lib] name = "cast" [[bin]] name = "cast" path = "bin/main.rs" [dependencies] # lib foundry-block-explorers.workspace = true foundry-common.workspace = true foundry-compilers.workspace = true foundry-config.workspace = true foundry-debugger.workspace = true foundry-evm.workspace = true foundry-wallets.workspace = true forge-fmt.workspace = true foundry-primitives.workspace = true alloy-chains.workspace = true alloy-consensus = { workspace = true, features = ["serde", "kzg"] } alloy-contract.workspace = true alloy-dyn-abi.workspace = true alloy-json-abi.workspace = true alloy-json-rpc.workspace = true alloy-network.workspace = true alloy-primitives.workspace = true alloy-provider = { workspace = true, features = [ "reqwest", "ws", "ipc", "trace-api", "txpool-api", ] } alloy-rlp.workspace = true alloy-rpc-types = { workspace = true, features = ["eth", "trace"] } alloy-rpc-types-beacon.workspace = true 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"] } alloy-eips.workspace = true tempo-alloy.workspace = true alloy-evm.workspace = true op-alloy-flz.workspace = true op-alloy-network.workspace = true chrono.workspace = true eyre.workspace = true futures.workspace = true revm = { workspace = true, features = ["optional_balance_check"] } rand.workspace = true rand_08.workspace = true rayon.workspace = true serde_json.workspace = true serde.workspace = true # bin foundry-cli.workspace = true clap = { version = "4", features = ["derive", "env", "unicode", "wrap_help"] } clap_complete.workspace = true comfy-table.workspace = true dunce.workspace = true itertools.workspace = true regex = { workspace = true, default-features = false } rpassword = "7" semver.workspace = true tempfile.workspace = true tokio = { workspace = true, features = ["macros", "signal"] } tracing.workspace = true yansi.workspace = true evmole.workspace = true [dev-dependencies] alloy-hardforks.workspace = true alloy-serde.workspace = true dirs.workspace = true anvil.workspace = true foundry-test-utils.workspace = true [features] default = ["jemalloc"] asm-keccak = ["alloy-primitives/asm-keccak"] 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"] turnkey = ["foundry-wallets/turnkey"] isolate-by-default = ["foundry-config/isolate-by-default"] ================================================ FILE: crates/cast/bin/main.rs ================================================ //! The `cast` CLI: a Swiss Army knife for interacting with EVM smart contracts, sending //! transactions and getting chain data. use cast::args::run; #[global_allocator] static ALLOC: foundry_cli::utils::Allocator = foundry_cli::utils::new_allocator(); fn main() { if let Err(err) = run() { let _ = foundry_common::sh_err!("{err:?}"); std::process::exit(1); } } ================================================ FILE: crates/cast/src/args.rs ================================================ use crate::{ Cast, SimpleCast, cmd::erc20::IERC20, opts::{Cast as CastArgs, CastSubcommand, ToBaseArgs}, traces::identifier::SignaturesIdentifier, tx::CastTxSender, }; use alloy_consensus::transaction::Recovered; use alloy_dyn_abi::{DynSolValue, ErrorExt, EventExt}; use alloy_eips::eip7702::SignedAuthorization; use alloy_ens::{ProviderEnsExt, namehash}; use alloy_network::Ethereum; use alloy_primitives::{Address, B256, eip191_hash_message, hex, keccak256}; use alloy_provider::Provider; use alloy_rpc_types::{BlockId, BlockNumberOrTag::Latest}; use clap::{CommandFactory, Parser}; use clap_complete::generate; use eyre::Result; use foundry_cli::{ opts::NetworkVariant, utils::{self, LoadConfig}, }; use foundry_common::{ abi::{get_error, get_event}, fmt::{format_tokens, format_uint_exp, serialize_value_as_json}, fs, provider::ProviderBuilder, selectors::{ ParsedSignatures, SelectorImportData, SelectorKind, decode_calldata, decode_event_topic, decode_function_selector, decode_selectors, import_selectors, parse_signatures, pretty_calldata, }, shell, stdin, }; use op_alloy_network::Optimism; use std::time::Instant; use tempo_alloy::TempoNetwork; /// Run the `cast` command-line interface. pub fn run() -> Result<()> { setup()?; foundry_cli::opts::GlobalArgs::check_markdown_help::(); let args = CastArgs::parse(); args.global.init()?; args.global.tokio_runtime().block_on(run_command(args)) } /// Setup the global logger and other utilities. pub fn setup() -> Result<()> { utils::common_setup(); utils::subscriber(); Ok(()) } /// Run the subcommand. pub async fn run_command(args: CastArgs) -> Result<()> { match args.cmd { // Constants CastSubcommand::MaxInt { r#type } => { sh_println!("{}", SimpleCast::max_int(&r#type)?)?; } CastSubcommand::MinInt { r#type } => { sh_println!("{}", SimpleCast::min_int(&r#type)?)?; } CastSubcommand::MaxUint { r#type } => { sh_println!("{}", SimpleCast::max_int(&r#type)?)?; } CastSubcommand::AddressZero => { sh_println!("{:?}", Address::ZERO)?; } CastSubcommand::HashZero => { sh_println!("{:?}", B256::ZERO)?; } // Conversions & transformations CastSubcommand::FromUtf8 { text } => { let value = stdin::unwrap(text, false)?; sh_println!("{}", SimpleCast::from_utf8(&value))? } CastSubcommand::ToAscii { hexdata } => { let value = stdin::unwrap(hexdata, false)?; sh_println!("{}", SimpleCast::to_ascii(value.trim())?)? } CastSubcommand::ToUtf8 { hexdata } => { let value = stdin::unwrap(hexdata, false)?; sh_println!("{}", SimpleCast::to_utf8(&value)?)? } CastSubcommand::FromFixedPoint { value, decimals } => { let (value, decimals) = stdin::unwrap2(value, decimals)?; sh_println!("{}", SimpleCast::from_fixed_point(&value, &decimals)?)? } CastSubcommand::ToFixedPoint { value, decimals } => { let (value, decimals) = stdin::unwrap2(value, decimals)?; sh_println!("{}", SimpleCast::to_fixed_point(&value, &decimals)?)? } CastSubcommand::ConcatHex { data } => { if data.is_empty() { let s = stdin::read(true)?; sh_println!("{}", SimpleCast::concat_hex(s.split_whitespace()))? } else { sh_println!("{}", SimpleCast::concat_hex(data))? } } CastSubcommand::FromBin => { let hex = stdin::read_bytes(false)?; sh_println!("{}", hex::encode_prefixed(hex))? } CastSubcommand::ToHexdata { input } => { let value = stdin::unwrap_line(input)?; let output = match value { s if s.starts_with('@') => hex::encode(std::env::var(&s[1..])?), s if s.starts_with('/') => hex::encode(fs::read(s)?), s => s.split(':').map(|s| s.trim_start_matches("0x").to_lowercase()).collect(), }; sh_println!("0x{output}")? } CastSubcommand::ToCheckSumAddress { address, chain_id } => { let value = stdin::unwrap_line(address)?; sh_println!("{}", value.to_checksum(chain_id))? } CastSubcommand::ToUint256 { value } => { let value = stdin::unwrap_line(value)?; sh_println!("{}", SimpleCast::to_uint256(&value)?)? } CastSubcommand::ToInt256 { value } => { let value = stdin::unwrap_line(value)?; sh_println!("{}", SimpleCast::to_int256(&value)?)? } CastSubcommand::ToUnit { value, unit } => { let value = stdin::unwrap_line(value)?; sh_println!("{}", SimpleCast::to_unit(&value, &unit)?)? } CastSubcommand::ParseUnits { value, unit } => { let value = stdin::unwrap_line(value)?; sh_println!("{}", SimpleCast::parse_units(&value, unit)?)?; } CastSubcommand::FormatUnits { value, unit } => { let value = stdin::unwrap_line(value)?; sh_println!("{}", SimpleCast::format_units(&value, unit)?)?; } CastSubcommand::FromWei { value, unit } => { let value = stdin::unwrap_line(value)?; sh_println!("{}", SimpleCast::from_wei(&value, &unit)?)? } CastSubcommand::ToWei { value, unit } => { let value = stdin::unwrap_line(value)?; sh_println!("{}", SimpleCast::to_wei(&value, &unit)?)? } CastSubcommand::FromRlp { value, as_int } => { let value = stdin::unwrap_line(value)?; sh_println!("{}", SimpleCast::from_rlp(value, as_int)?)? } CastSubcommand::ToRlp { value } => { let value = stdin::unwrap_line(value)?; sh_println!("{}", SimpleCast::to_rlp(&value)?)? } CastSubcommand::ToHex(ToBaseArgs { value, base_in }) => { let value = stdin::unwrap_line(value)?; sh_println!("{}", SimpleCast::to_base(&value, base_in.as_deref(), "hex")?)? } CastSubcommand::ToDec(ToBaseArgs { value, base_in }) => { let value = stdin::unwrap_line(value)?; sh_println!("{}", SimpleCast::to_base(&value, base_in.as_deref(), "dec")?)? } CastSubcommand::ToBase { base: ToBaseArgs { value, base_in }, base_out } => { let (value, base_out) = stdin::unwrap2(value, base_out)?; sh_println!("{}", SimpleCast::to_base(&value, base_in.as_deref(), &base_out)?)? } CastSubcommand::ToBytes32 { bytes } => { let value = stdin::unwrap_line(bytes)?; sh_println!("{}", SimpleCast::to_bytes32(&value)?)? } CastSubcommand::Pad { data, right, left: _, len } => { let value = stdin::unwrap_line(data)?; sh_println!("{}", SimpleCast::pad(&value, right, len)?)? } CastSubcommand::FormatBytes32String { string } => { let value = stdin::unwrap_line(string)?; sh_println!("{}", SimpleCast::format_bytes32_string(&value)?)? } CastSubcommand::ParseBytes32String { bytes } => { let value = stdin::unwrap_line(bytes)?; sh_println!("{}", SimpleCast::parse_bytes32_string(&value)?)? } CastSubcommand::ParseBytes32Address { bytes } => { let value = stdin::unwrap_line(bytes)?; sh_println!("{}", SimpleCast::parse_bytes32_address(&value)?)? } // ABI encoding & decoding CastSubcommand::DecodeAbi { sig, calldata, input } => { let tokens = SimpleCast::abi_decode(&sig, &calldata, input)?; print_tokens(&tokens); } CastSubcommand::AbiEncode { sig, packed, args } => { if !packed { sh_println!("{}", SimpleCast::abi_encode(&sig, &args)?)? } else { sh_println!("{}", SimpleCast::abi_encode_packed(&sig, &args)?)? } } CastSubcommand::AbiEncodeEvent { sig, args } => { let log_data = SimpleCast::abi_encode_event(&sig, &args)?; for (i, topic) in log_data.topics().iter().enumerate() { sh_println!("[topic{}]: {}", i, topic)?; } if !log_data.data.is_empty() { sh_println!("[data]: {}", hex::encode_prefixed(log_data.data))?; } } CastSubcommand::DecodeCalldata { sig, calldata, file } => { let raw_hex = if let Some(file_path) = file { let contents = fs::read_to_string(&file_path)?; contents.trim().to_string() } else { calldata.unwrap() }; let tokens = SimpleCast::calldata_decode(&sig, &raw_hex, true)?; print_tokens(&tokens); } CastSubcommand::CalldataEncode { sig, args, file } => { let final_args = if let Some(file_path) = file { let contents = fs::read_to_string(file_path)?; contents .lines() .map(str::trim) .filter(|line| !line.is_empty()) .map(String::from) .collect() } else { args }; sh_println!("{}", SimpleCast::calldata_encode(sig, &final_args)?)?; } CastSubcommand::DecodeString { data } => { let tokens = SimpleCast::calldata_decode("Any(string)", &data, true)?; print_tokens(&tokens); } CastSubcommand::DecodeEvent { sig, data } => { let decoded_event = if let Some(event_sig) = sig { let event = get_event(event_sig.as_str())?; event.decode_log_parts(core::iter::once(event.selector()), &hex::decode(data)?)? } else { let data = crate::strip_0x(&data); let selector = data.get(..64).unwrap_or_default(); let selector = selector.parse()?; let identified_event = 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(core::iter::once(selector), &hex::decode(data)?)? } else { eyre::bail!("No matching event signature found for selector `{selector}`") } }; print_tokens(&decoded_event.body); } CastSubcommand::DecodeError { sig, data } => { let error = if let Some(err_sig) = sig { get_error(err_sig.as_str())? } else { let data = crate::strip_0x(&data); let selector = data.get(..8).unwrap_or_default(); let identified_error = SignaturesIdentifier::new(false)?.identify_error(selector.parse()?).await; if let Some(error) = identified_error { let _ = sh_println!("{}", error.signature()); error } else { eyre::bail!("No matching error signature found for selector `{selector}`") } }; let decoded_error = error.decode_error(&hex::decode(data)?)?; print_tokens(&decoded_error.body); } CastSubcommand::Interface(cmd) => cmd.run().await?, CastSubcommand::CreationCode(cmd) => cmd.run().await?, CastSubcommand::ConstructorArgs(cmd) => cmd.run().await?, CastSubcommand::Artifact(cmd) => cmd.run().await?, CastSubcommand::Bind(cmd) => cmd.run().await?, CastSubcommand::B2EPayload(cmd) => cmd.run().await?, CastSubcommand::PrettyCalldata { calldata, offline } => { let calldata = stdin::unwrap_line(calldata)?; sh_println!("{}", pretty_calldata(&calldata, offline).await?)?; } CastSubcommand::Sig { sig, optimize } => { let sig = stdin::unwrap_line(sig)?; match optimize { Some(opt) => { sh_println!("Starting to optimize signature...")?; let start_time = Instant::now(); let (selector, signature) = SimpleCast::get_selector(&sig, opt)?; sh_println!("Successfully generated in {:?}", start_time.elapsed())?; sh_println!("Selector: {selector}")?; sh_println!("Optimized signature: {signature}")?; } None => sh_println!("{}", SimpleCast::get_selector(&sig, 0)?.0)?, } } // Blockchain & RPC queries CastSubcommand::AccessList(cmd) => cmd.run().await?, CastSubcommand::Age { block, rpc } => { let config = rpc.load_config()?; let provider = utils::get_provider(&config)?; sh_println!( "{} UTC", Cast::new(provider).age(block.unwrap_or(BlockId::Number(Latest))).await? )? } CastSubcommand::Balance { block, who, ether, rpc, erc20 } => { let config = rpc.load_config()?; let provider = utils::get_provider(&config)?; let account_addr = who.resolve(&provider).await?; match erc20 { Some(token) => { let balance = IERC20::new(token, &provider) .balanceOf(account_addr) .block(block.unwrap_or_default()) .call() .await?; sh_warn!("--erc20 flag is deprecated, use `cast erc20 balance` instead")?; sh_println!("{}", format_uint_exp(balance))? } None => { let value = Cast::new(&provider).balance(account_addr, block).await?; if ether { sh_println!("{}", SimpleCast::from_wei(&value.to_string(), "eth")?)? } else { sh_println!("{value}")? } } } } CastSubcommand::BaseFee { block, rpc } => { let config = rpc.load_config()?; let provider = utils::get_provider(&config)?; sh_println!( "{}", Cast::new(provider).base_fee(block.unwrap_or(BlockId::Number(Latest))).await? )? } CastSubcommand::Block { block, full, fields, raw, rpc, network } => { let config = rpc.load_config()?; // Can use either --raw or specify raw as a field let output = if raw || fields.contains(&"raw".into()) { match network { Some(NetworkVariant::Optimism) => { let provider = ProviderBuilder::::from_config(&config)?.build()?; Cast::new(&provider) .block_raw(block.unwrap_or(BlockId::Number(Latest)), full) .await? } Some(NetworkVariant::Tempo) => { let provider = ProviderBuilder::::from_config(&config)?.build()?; Cast::new(&provider) .block_raw(block.unwrap_or(BlockId::Number(Latest)), full) .await? } // Ethereum (default) or no --raw flag _ => { let provider = ProviderBuilder::::from_config(&config)?.build()?; Cast::new(&provider) .block_raw(block.unwrap_or(BlockId::Number(Latest)), full) .await? } } } else { let provider = utils::get_provider(&config)?; Cast::new(provider) .block(block.unwrap_or(BlockId::Number(Latest)), full, fields) .await? }; sh_println!("{}", output)? } CastSubcommand::BlockNumber { rpc, block } => { let config = rpc.load_config()?; let provider = utils::get_provider(&config)?; let number = match block { Some(id) => { provider .get_block(id) .await? .ok_or_else(|| eyre::eyre!("block {id:?} not found"))? .header .number } None => Cast::new(provider).block_number().await?, }; sh_println!("{number}")? } CastSubcommand::Chain { rpc } => { let config = rpc.load_config()?; let provider = utils::get_provider(&config)?; sh_println!("{}", Cast::new(provider).chain().await?)? } CastSubcommand::ChainId { rpc } => { let config = rpc.load_config()?; let provider = utils::get_provider(&config)?; sh_println!("{}", Cast::new(provider).chain_id().await?)? } CastSubcommand::Client { rpc } => { let config = rpc.load_config()?; let provider = utils::get_provider(&config)?; sh_println!("{}", provider.get_client_version().await?)? } CastSubcommand::Code { block, who, disassemble, rpc } => { let config = rpc.load_config()?; let provider = utils::get_provider(&config)?; let who = who.resolve(&provider).await?; sh_println!("{}", Cast::new(provider).code(who, block, disassemble).await?)? } CastSubcommand::Codesize { block, who, rpc } => { let config = rpc.load_config()?; let provider = utils::get_provider(&config)?; let who = who.resolve(&provider).await?; sh_println!("{}", Cast::new(provider).codesize(who, block).await?)? } CastSubcommand::ComputeAddress { address, nonce, salt, init_code, init_code_hash, rpc } => { let address = stdin::unwrap_line(address)?; 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 } => { let bytecode = stdin::unwrap_line(bytecode)?; sh_println!("{}", SimpleCast::disassemble(&hex::decode(bytecode)?)?)? } CastSubcommand::Selectors { bytecode, resolve } => { let bytecode = stdin::unwrap_line(bytecode)?; let functions = SimpleCast::extract_functions(&bytecode)?; let max_args_len = functions.iter().map(|r| r.1.len()).max().unwrap_or(0); let max_mutability_len = functions.iter().map(|r| r.2.len()).max().unwrap_or(0); let resolve_results = if resolve { let selectors = functions .iter() .map(|&(selector, ..)| SelectorKind::Function(selector)) .collect::>(); let ds = decode_selectors(&selectors).await?; ds.into_iter().map(|v| v.join("|")).collect() } else { vec![] }; for (pos, (selector, arguments, state_mutability)) in functions.into_iter().enumerate() { if resolve { let resolved = &resolve_results[pos]; sh_println!( "{selector}\t{arguments:max_args_len$}\t{state_mutability:max_mutability_len$}\t{resolved}" )? } else { sh_println!("{selector}\t{arguments:max_args_len$}\t{state_mutability}")? } } } CastSubcommand::FindBlock(cmd) => cmd.run().await?, CastSubcommand::GasPrice { rpc } => { let config = rpc.load_config()?; let provider = utils::get_provider(&config)?; sh_println!("{}", Cast::new(provider).gas_price().await?)?; } CastSubcommand::Index { key_type, key, slot_number } => { sh_println!("{}", SimpleCast::index(&key_type, &key, &slot_number)?)?; } CastSubcommand::IndexErc7201 { id, formula_id } => { eyre::ensure!(formula_id == "erc7201", "unsupported formula ID: {formula_id}"); let id = stdin::unwrap_line(id)?; sh_println!("{}", foundry_common::erc7201(&id))?; } CastSubcommand::Implementation { block, beacon, who, rpc } => { let config = rpc.load_config()?; let provider = utils::get_provider(&config)?; let who = who.resolve(&provider).await?; sh_println!("{}", Cast::new(provider).implementation(who, beacon, block).await?)?; } CastSubcommand::Admin { block, who, rpc } => { let config = rpc.load_config()?; let provider = utils::get_provider(&config)?; let who = who.resolve(&provider).await?; sh_println!("{}", Cast::new(provider).admin(who, block).await?)?; } CastSubcommand::Nonce { block, who, rpc } => { let config = rpc.load_config()?; let provider = utils::get_provider(&config)?; let who = who.resolve(&provider).await?; sh_println!("{}", Cast::new(provider).nonce(who, block).await?)?; } CastSubcommand::Codehash { block, who, slots, rpc } => { let config = rpc.load_config()?; let provider = utils::get_provider(&config)?; let who = who.resolve(&provider).await?; sh_println!("{}", Cast::new(provider).codehash(who, slots, block).await?)?; } CastSubcommand::StorageRoot { block, who, slots, rpc } => { let config = rpc.load_config()?; let provider = utils::get_provider(&config)?; let who = who.resolve(&provider).await?; sh_println!("{}", Cast::new(provider).storage_root(who, slots, block).await?)?; } CastSubcommand::Proof { address, slots, rpc, block } => { let config = rpc.load_config()?; let provider = utils::get_provider(&config)?; let address = address.resolve(&provider).await?; let value = provider .get_proof(address, slots.into_iter().collect()) .block_id(block.unwrap_or_default()) .await?; sh_println!("{}", serde_json::to_string(&value)?)?; } CastSubcommand::Rpc(cmd) => cmd.run().await?, CastSubcommand::Storage(cmd) => cmd.run().await?, // Calls & transactions CastSubcommand::Call(cmd) => cmd.run().await?, CastSubcommand::Estimate(cmd) => cmd.run().await?, CastSubcommand::MakeTx(cmd) => cmd.run().await?, CastSubcommand::PublishTx { raw_tx, cast_async, rpc } => { let config = rpc.load_config()?; let provider = utils::get_provider(&config)?; let cast = Cast::new(&provider); let pending_tx = cast.publish(raw_tx).await?; let tx_hash = pending_tx.inner().tx_hash(); if cast_async { sh_println!("{tx_hash:#x}")?; } else { let receipt = pending_tx.get_receipt().await?; sh_println!("{}", serde_json::json!(receipt))?; } } CastSubcommand::Receipt { tx_hash, field, cast_async, confirmations, rpc } => { let config = rpc.load_config()?; let provider = utils::get_provider(&config)?; sh_println!( "{}", CastTxSender::new(provider) .receipt(tx_hash, field, confirmations, None, cast_async) .await? )? } CastSubcommand::Run(cmd) => cmd.run().await?, CastSubcommand::SendTx(cmd) => cmd.run().await?, CastSubcommand::Tx { tx_hash, from, nonce, field, raw, rpc, to_request, network } => { let config = rpc.load_config()?; // Can use either --raw or specify raw as a field let is_raw = raw || field.as_ref().is_some_and(|f| f == "raw"); let output = match network { Some(NetworkVariant::Optimism) => { let provider = ProviderBuilder::::from_config(&config)?.build()?; Cast::new(&provider) .transaction(tx_hash, from, nonce, field, is_raw, to_request) .await? } Some(NetworkVariant::Tempo) => { let provider = ProviderBuilder::::from_config(&config)?.build()?; Cast::new(&provider) .transaction(tx_hash, from, nonce, field, is_raw, to_request) .await? } // Ethereum (default) or no --raw flag _ => { let provider = utils::get_provider(&config)?; Cast::new(&provider) .transaction(tx_hash, from, nonce, field, is_raw, to_request) .await? } }; sh_println!("{}", output)? } // 4Byte CastSubcommand::FourByte { selector } => { let selector = stdin::unwrap_line(selector)?; let sigs = decode_function_selector(selector).await?; if sigs.is_empty() { eyre::bail!("No matching function signatures found for selector `{selector}`"); } for sig in sigs { sh_println!("{sig}")? } } CastSubcommand::FourByteCalldata { calldata } => { let calldata = stdin::unwrap_line(calldata)?; if calldata.len() == 10 { let sigs = decode_function_selector(calldata.parse()?).await?; if sigs.is_empty() { eyre::bail!("No matching function signatures found for calldata `{calldata}`"); } for sig in sigs { sh_println!("{sig}")? } return Ok(()); } let sigs = decode_calldata(&calldata).await?; sigs.iter().enumerate().for_each(|(i, sig)| { let _ = sh_println!("{}) \"{sig}\"", i + 1); }); let sig = match sigs.len() { 0 => eyre::bail!("No signatures found"), 1 => sigs.first().unwrap(), _ => { let i: usize = prompt!("Select a function signature by number: ")?; sigs.get(i - 1).ok_or_else(|| eyre::eyre!("Invalid signature index"))? } }; let tokens = SimpleCast::calldata_decode(sig, &calldata, true)?; print_tokens(&tokens); } CastSubcommand::FourByteEvent { topic } => { let topic = stdin::unwrap_line(topic)?; let sigs = decode_event_topic(topic).await?; if sigs.is_empty() { eyre::bail!("No matching event signatures found for topic `{topic}`"); } for sig in sigs { sh_println!("{sig}")? } } CastSubcommand::UploadSignature { signatures } => { let signatures = stdin::unwrap_vec(signatures)?; let ParsedSignatures { signatures, abis } = parse_signatures(signatures); if !abis.is_empty() { import_selectors(SelectorImportData::Abi(abis)).await?.describe(); } if !signatures.is_empty() { import_selectors(SelectorImportData::Raw(signatures)).await?.describe(); } } // ENS CastSubcommand::Namehash { name } => { let name = stdin::unwrap_line(name)?; sh_println!("{}", namehash(&name))? } CastSubcommand::LookupAddress { who, rpc, verify } => { let config = rpc.load_config()?; let provider = utils::get_provider(&config)?; let who = stdin::unwrap_line(who)?; let name = provider.lookup_address(&who).await?; if verify { let address = provider.resolve_name(&name).await?; eyre::ensure!( address == who, "Reverse lookup verification failed: got `{address}`, expected `{who}`" ); } sh_println!("{name}")? } CastSubcommand::ResolveName { who, rpc, verify } => { let config = rpc.load_config()?; let provider = utils::get_provider(&config)?; let who = stdin::unwrap_line(who)?; let address = provider.resolve_name(&who).await?; if verify { let name = provider.lookup_address(&address).await?; eyre::ensure!( name == who, "Forward lookup verification failed: got `{name}`, expected `{who}`" ); } sh_println!("{address}")? } // Misc CastSubcommand::Keccak { data } => { let bytes = match data { Some(data) => data.into_bytes(), None => stdin::read_bytes(false)?, }; match String::from_utf8(bytes) { Ok(s) => { let s = SimpleCast::keccak(&s)?; sh_println!("{s}")? } Err(e) => { let hash = keccak256(e.as_bytes()); let s = hex::encode(hash); sh_println!("0x{s}")? } }; } CastSubcommand::HashMessage { message } => { let message = stdin::unwrap(message, false)?; sh_println!("{}", eip191_hash_message(message))? } CastSubcommand::SigEvent { event_string } => { let event_string = stdin::unwrap_line(event_string)?; let parsed_event = get_event(&event_string)?; sh_println!("{:?}", parsed_event.selector())? } CastSubcommand::LeftShift { value, bits, base_in, base_out } => sh_println!( "{}", SimpleCast::left_shift(&value, &bits, base_in.as_deref(), &base_out)? )?, CastSubcommand::RightShift { value, bits, base_in, base_out } => sh_println!( "{}", SimpleCast::right_shift(&value, &bits, base_in.as_deref(), &base_out)? )?, CastSubcommand::Source { address, directory, explorer_api_url, explorer_url, etherscan, flatten, } => { let config = etherscan.load_config()?; let chain = config.chain.unwrap_or_default(); let api_key = config.get_etherscan_api_key(Some(chain)); match (directory, flatten) { (Some(dir), false) => { SimpleCast::expand_etherscan_source_to_directory( chain, address, api_key, dir, explorer_api_url, explorer_url, ) .await? } (None, false) => sh_println!( "{}", SimpleCast::etherscan_source( chain, address, api_key, explorer_api_url, explorer_url ) .await? )?, (dir, true) => { SimpleCast::etherscan_source_flatten( chain, address, api_key, dir, explorer_api_url, explorer_url, ) .await?; } } } CastSubcommand::Create2(cmd) => { cmd.run()?; } CastSubcommand::Wallet { command } => command.run().await?, CastSubcommand::Completions { shell } => { generate(shell, &mut CastArgs::command(), "cast", &mut std::io::stdout()) } CastSubcommand::Logs(cmd) => cmd.run().await?, CastSubcommand::DecodeTransaction { tx } => { let tx = stdin::unwrap_line(tx)?; let tx = SimpleCast::decode_raw_transaction(&tx)?; if let Ok(signer) = tx.recover() { let recovered = Recovered::new_unchecked(tx, signer); sh_println!("{}", serde_json::to_string_pretty(&recovered)?)?; } else { sh_println!("{}", serde_json::to_string_pretty(&tx)?)?; } } CastSubcommand::RecoverAuthority { auth } => { let auth: SignedAuthorization = serde_json::from_str(&auth)?; sh_println!("{}", auth.recover_authority()?)?; } CastSubcommand::TxPool { command } => command.run().await?, CastSubcommand::Erc20Token { command } => command.run().await?, CastSubcommand::DAEstimate(cmd) => { cmd.run().await?; } CastSubcommand::Trace(cmd) => cmd.run().await?, }; /// Prints slice of tokens using [`format_tokens`] or [`serialize_value_as_json`] depending /// whether the shell is in JSON mode. /// /// This is included here to avoid a cyclic dependency between `fmt` and `common`. fn print_tokens(tokens: &[DynSolValue]) { if shell::is_json() { let tokens: Vec = tokens .iter() .cloned() .map(|t| serialize_value_as_json(t, None)) .collect::>>() .unwrap(); let _ = sh_println!("{}", serde_json::to_string_pretty(&tokens).unwrap()); } else { let tokens = format_tokens(tokens); tokens.for_each(|t| { let _ = sh_println!("{t}"); }); } } Ok(()) } ================================================ FILE: crates/cast/src/base.rs ================================================ use alloy_primitives::{I256, Sign, U256, utils::ParseUnits}; use eyre::Result; use std::{ convert::Infallible, fmt::{Binary, Debug, Display, Formatter, LowerHex, Octal, Result as FmtResult, UpperHex}, num::IntErrorKind, str::FromStr, }; /* -------------------------------------------- Base -------------------------------------------- */ /// Represents a number's [radix] or base. Currently it supports the same bases that [std::fmt] /// supports. /// /// [radix]: https://en.wikipedia.org/wiki/Radix #[repr(u32)] #[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord)] pub enum Base { Binary = 2, Octal = 8, #[default] Decimal = 10, Hexadecimal = 16, } impl Display for Base { fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { Display::fmt(&(*self as u32), f) } } impl FromStr for Base { type Err = eyre::Report; fn from_str(s: &str) -> Result { match s.to_lowercase().as_str() { "2" | "b" | "bin" | "binary" => Ok(Self::Binary), "8" | "o" | "oct" | "octal" => Ok(Self::Octal), "10" | "d" | "dec" | "decimal" => Ok(Self::Decimal), "16" | "h" | "hex" | "hexadecimal" => Ok(Self::Hexadecimal), s => Err(eyre::eyre!( "\ Invalid base \"{s}\". Possible values: 2, b, bin, binary 8, o, oct, octal 10, d, dec, decimal 16, h, hex, hexadecimal" )), } } } impl TryFrom for Base { type Error = eyre::Report; fn try_from(s: String) -> Result { Self::from_str(&s) } } impl TryFrom for Base { type Error = eyre::Report; fn try_from(n: u32) -> Result { match n { 2 => Ok(Self::Binary), 8 => Ok(Self::Octal), 10 => Ok(Self::Decimal), 16 => Ok(Self::Hexadecimal), n => Err(eyre::eyre!("Invalid base \"{}\". Possible values: 2, 8, 10, 16", n)), } } } impl TryFrom for Base { type Error = eyre::Report; fn try_from(n: I256) -> Result { Self::try_from(n.low_u32()) } } impl TryFrom for Base { type Error = eyre::Report; fn try_from(n: U256) -> Result { Self::try_from(n.saturating_to::()) } } impl From for u32 { fn from(b: Base) -> Self { b as Self } } impl From for String { fn from(b: Base) -> Self { b.to_string() } } impl Base { pub fn unwrap_or_detect(base: Option<&str>, s: impl AsRef) -> Result { match base { Some(base) => base.parse(), None => Self::detect(s), } } /// Try parsing a number's base from a string. pub fn detect(s: impl AsRef) -> Result { let s = s.as_ref(); match s { // Ignore sign _ if s.starts_with(['+', '-']) => Self::detect(&s[1..]), // Verify binary and octal values with u128::from_str_radix as U256 does not support // them; // assume overflows are within u128::MAX and U256::MAX, we're not using the parsed value // anyway; // strip prefix when using u128::from_str_radix because it does not recognize it as // valid. _ if s.starts_with("0b") => match u64::from_str_radix(&s[2..], 2) { Ok(_) => Ok(Self::Binary), Err(e) => match e.kind() { IntErrorKind::PosOverflow => Ok(Self::Binary), _ => Err(eyre::eyre!("could not parse binary value: {}", e)), }, }, _ if s.starts_with("0o") => match u64::from_str_radix(&s[2..], 8) { Ok(_) => Ok(Self::Octal), Err(e) => match e.kind() { IntErrorKind::PosOverflow => Ok(Self::Octal), _ => Err(eyre::eyre!("could not parse octal value: {e}")), }, }, _ if s.starts_with("0x") => match u64::from_str_radix(&s[2..], 16) { Ok(_) => Ok(Self::Hexadecimal), Err(e) => match e.kind() { IntErrorKind::PosOverflow => Ok(Self::Hexadecimal), _ => Err(eyre::eyre!("could not parse hexadecimal value: {e}")), }, }, // No prefix => first try parsing as decimal _ => match U256::from_str_radix(s, 10) { // Can be both, ambiguous but default to Decimal Ok(_) => Ok(Self::Decimal), Err(_) => match U256::from_str_radix(s, 16) { Ok(_) => Ok(Self::Hexadecimal), Err(e) => Err(eyre::eyre!( "could not autodetect base as neither decimal or hexadecimal: {e}" )), }, }, } } /// Returns the Rust standard prefix for a base pub const fn prefix(&self) -> &str { match self { Self::Binary => "0b", Self::Octal => "0o", Self::Decimal => "", Self::Hexadecimal => "0x", } } } /* --------------------------------------- NumberWithBase --------------------------------------- */ /// Utility struct for parsing numbers and formatting them into different [bases][Base]. /// /// # Example /// /// ``` /// use cast::base::NumberWithBase; /// use alloy_primitives::U256; /// /// let number: NumberWithBase = U256::from(12345).into(); /// assert_eq!(number.format(), "12345"); /// /// // Debug uses number.base() to determine which base to format to, which defaults to Base::Decimal /// assert_eq!(format!("{:?}", number), "12345"); /// /// // Display uses Base::Decimal /// assert_eq!(format!("{}", number), "12345"); /// /// // The alternate formatter ("#") prepends the base's prefix /// assert_eq!(format!("{:x}", number), "3039"); /// assert_eq!(format!("{:#x}", number), "0x3039"); /// /// assert_eq!(format!("{:b}", number), "11000000111001"); /// assert_eq!(format!("{:#b}", number), "0b11000000111001"); /// /// assert_eq!(format!("{:o}", number), "30071"); /// assert_eq!(format!("{:#o}", number), "0o30071"); /// ``` #[derive(Clone, Copy)] pub struct NumberWithBase { /// The number. number: U256, /// Whether the number is positive or zero. is_nonnegative: bool, /// The base to format to. base: Base, } impl std::ops::Deref for NumberWithBase { type Target = U256; fn deref(&self) -> &Self::Target { &self.number } } // Format using self.base impl Debug for NumberWithBase { fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { let prefix = self.base.prefix(); if self.number.is_zero() { f.pad_integral(true, prefix, "0") } else { // Add sign only for decimal let is_nonnegative = match self.base { Base::Decimal => self.is_nonnegative, _ => true, }; f.pad_integral(is_nonnegative, prefix, &self.format()) } } } impl Binary for NumberWithBase { fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { Debug::fmt(&self.with_base(Base::Binary), f) } } impl Octal for NumberWithBase { fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { Debug::fmt(&self.with_base(Base::Octal), f) } } impl Display for NumberWithBase { fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { Debug::fmt(&self.with_base(Base::Decimal), f) } } impl LowerHex for NumberWithBase { fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { Debug::fmt(&self.with_base(Base::Hexadecimal), f) } } impl UpperHex for NumberWithBase { fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { let n = format!("{self:x}").to_uppercase(); f.pad_integral(true, Base::Hexadecimal.prefix(), &n) } } impl FromStr for NumberWithBase { type Err = eyre::Report; fn from_str(s: &str) -> Result { Self::parse_int(s, None) } } impl From for NumberWithBase { fn from(number: I256) -> Self { // both is_positive and is_negative return false for 0 Self::new(number.into_raw(), !number.is_negative(), Base::default()) } } impl From for NumberWithBase { fn from(value: ParseUnits) -> Self { match value { ParseUnits::U256(val) => val.into(), ParseUnits::I256(val) => val.into(), } } } impl From for NumberWithBase { fn from(number: U256) -> Self { Self::new(number, true, Base::default()) } } impl From for I256 { fn from(n: NumberWithBase) -> Self { Self::from_raw(n.number) } } impl From for U256 { fn from(n: NumberWithBase) -> Self { n.number } } impl From for String { /// Formats the number into the specified base. See [NumberWithBase::format]. /// /// [NumberWithBase::format]: NumberWithBase fn from(n: NumberWithBase) -> Self { n.format() } } impl NumberWithBase { pub fn new(number: impl Into, is_nonnegative: bool, base: Base) -> Self { Self { number: number.into(), is_nonnegative, base } } /// Creates a copy of the number with the provided base. pub fn with_base(&self, base: Base) -> Self { Self { number: self.number, is_nonnegative: self.is_nonnegative, base } } /// Parses a string slice into a signed integer. If base is None then it tries to determine base /// from the prefix, otherwise defaults to Decimal. pub fn parse_int(s: &str, base: Option<&str>) -> Result { let base = Base::unwrap_or_detect(base, s)?; let (number, is_nonnegative) = Self::_parse_int(s, base)?; Ok(Self { number, is_nonnegative, base }) } /// Parses a string slice into an unsigned integer. If base is None then it tries to determine /// base from the prefix, otherwise defaults to Decimal. pub fn parse_uint(s: &str, base: Option<&str>) -> Result { let base = Base::unwrap_or_detect(base, s)?; let number = Self::_parse_uint(s, base)?; Ok(Self { number, is_nonnegative: true, base }) } /// Returns a copy of the underlying number as an unsigned integer. If the value is negative /// then the two's complement of its absolute value will be returned. pub fn number(&self) -> U256 { self.number } /// Returns whether the underlying number is positive or zero. pub fn is_nonnegative(&self) -> bool { self.is_nonnegative } /// Returns the underlying base. Defaults to [Decimal][Base]. pub fn base(&self) -> Base { self.base } /// Returns the Rust standard prefix for the base. pub const fn prefix(&self) -> &str { self.base.prefix() } /// Sets the number's base to format to. pub fn set_base(&mut self, base: Base) -> &mut Self { self.base = base; self } /// Formats the number into the specified base. /// /// **Note**: this method only formats the number into the base, without adding any prefixes, /// signs or padding. Refer to the [std::fmt] module documentation on how to format this /// number with the aforementioned properties. pub fn format(&self) -> String { let s = match self.base { Base::Binary => format!("{:b}", self.number), Base::Octal => format!("{:o}", self.number), Base::Decimal => { if self.is_nonnegative { self.number.to_string() } else { let s = I256::from_raw(self.number).to_string(); s.strip_prefix('-').unwrap_or(&s).to_string() } } Base::Hexadecimal => format!("{:x}", self.number), }; if s.starts_with('0') { s.trim_start_matches('0').to_string() } else { s } } fn _parse_int(s: &str, base: Base) -> Result<(U256, bool)> { let (s, sign) = get_sign(s); let mut n = Self::_parse_uint(s, base)?; let is_neg = matches!(sign, Sign::Negative); if is_neg { n = (!n).overflowing_add(U256::from(1)).0; } Ok((n, !is_neg)) } fn _parse_uint(s: &str, base: Base) -> Result { let s = match s.get(0..2) { Some("0x" | "0X" | "0o" | "0O" | "0b" | "0B") => &s[2..], _ => s, }; U256::from_str_radix(s, base as u64).map_err(Into::into) } } /* ------------------------------------------- ToBase ------------------------------------------- */ /// Facilitates formatting an integer into a [Base]. pub trait ToBase { type Err; /// Formats self into a base, specifying whether to add the base prefix or not. /// /// Tries converting `self` into a [NumberWithBase] and then formats into the provided base by /// using the [Debug] implementation. /// /// # Example /// /// ``` /// use alloy_primitives::U256; /// use cast::base::{Base, ToBase}; /// /// // Any type that implements ToBase /// let number = U256::from(12345); /// assert_eq!(number.to_base(Base::Decimal, false).unwrap(), "12345"); /// assert_eq!(number.to_base(Base::Hexadecimal, false).unwrap(), "3039"); /// assert_eq!(number.to_base(Base::Hexadecimal, true).unwrap(), "0x3039"); /// assert_eq!(number.to_base(Base::Binary, true).unwrap(), "0b11000000111001"); /// assert_eq!(number.to_base(Base::Octal, true).unwrap(), "0o30071"); /// ``` fn to_base(&self, base: Base, add_prefix: bool) -> Result; } impl ToBase for NumberWithBase { type Err = Infallible; fn to_base(&self, base: Base, add_prefix: bool) -> Result { let n = self.with_base(base); if add_prefix { Ok(format!("{n:#?}")) } else { Ok(format!("{n:?}")) } } } impl ToBase for I256 { type Err = Infallible; fn to_base(&self, base: Base, add_prefix: bool) -> Result { let n = NumberWithBase::from(*self).with_base(base); if add_prefix { Ok(format!("{n:#?}")) } else { Ok(format!("{n:?}")) } } } impl ToBase for U256 { type Err = Infallible; fn to_base(&self, base: Base, add_prefix: bool) -> Result { let n = NumberWithBase::from(*self).with_base(base); if add_prefix { Ok(format!("{n:#?}")) } else { Ok(format!("{n:?}")) } } } impl ToBase for String { type Err = eyre::Report; fn to_base(&self, base: Base, add_prefix: bool) -> Result { str::to_base(self, base, add_prefix) } } impl ToBase for str { type Err = eyre::Report; fn to_base(&self, base: Base, add_prefix: bool) -> Result { let n = NumberWithBase::from_str(self)?.with_base(base); if add_prefix { Ok(format!("{n:#?}")) } else { Ok(format!("{n:?}")) } } } fn get_sign(s: &str) -> (&str, Sign) { match s.as_bytes().first() { Some(b'+') => (&s[1..], Sign::Positive), Some(b'-') => (&s[1..], Sign::Negative), _ => (s, Sign::Positive), } } #[cfg(test)] mod tests { use super::*; use Base::*; const POS_NUM: [i128; 44] = [ 1, 2, 3, 5, 7, 8, 10, 11, 13, 16, 17, 19, 23, 29, 31, 32, 37, 41, 43, 47, 53, 59, 61, 64, 67, 71, 73, 79, 83, 89, 97, 100, 128, 200, 333, 500, 666, 1000, 6666, 10000, i16::MAX as i128, i32::MAX as i128, i64::MAX as i128, i128::MAX, ]; const NEG_NUM: [i128; 44] = [ -1, -2, -3, -5, -7, -8, -10, -11, -13, -16, -17, -19, -23, -29, -31, -32, -37, -41, -43, -47, -53, -59, -61, -64, -67, -71, -73, -79, -83, -89, -97, -100, -128, -200, -333, -500, -666, -1000, -6666, -10000, i16::MIN as i128, i32::MIN as i128, i64::MIN as i128, i128::MIN, ]; #[test] fn test_defaults() { let def: Base = Default::default(); assert!(matches!(def, Decimal)); let n: NumberWithBase = U256::ZERO.into(); assert!(matches!(n.base, Decimal)); let n: NumberWithBase = I256::ZERO.into(); assert!(matches!(n.base, Decimal)); } #[test] fn can_parse_base() { assert_eq!("2".parse::().unwrap(), Binary); assert_eq!("b".parse::().unwrap(), Binary); assert_eq!("bin".parse::().unwrap(), Binary); assert_eq!("binary".parse::().unwrap(), Binary); assert_eq!("8".parse::().unwrap(), Octal); assert_eq!("o".parse::().unwrap(), Octal); assert_eq!("oct".parse::().unwrap(), Octal); assert_eq!("octal".parse::().unwrap(), Octal); assert_eq!("10".parse::().unwrap(), Decimal); assert_eq!("d".parse::().unwrap(), Decimal); assert_eq!("dec".parse::().unwrap(), Decimal); assert_eq!("decimal".parse::().unwrap(), Decimal); assert_eq!("16".parse::().unwrap(), Hexadecimal); assert_eq!("h".parse::().unwrap(), Hexadecimal); assert_eq!("hex".parse::().unwrap(), Hexadecimal); assert_eq!("hexadecimal".parse::().unwrap(), Hexadecimal); } #[test] fn can_detect_base() { assert_eq!(Base::detect("0b100").unwrap(), Binary); assert_eq!(Base::detect("0o100").unwrap(), Octal); assert_eq!(Base::detect("100").unwrap(), Decimal); assert_eq!(Base::detect("0x100").unwrap(), Hexadecimal); assert_eq!(Base::detect("0123456789abcdef").unwrap(), Hexadecimal); let _ = Base::detect("0b234abc").unwrap_err(); let _ = Base::detect("0o89cba").unwrap_err(); let _ = Base::detect("0123456789abcdefg").unwrap_err(); let _ = Base::detect("0x123abclpmk").unwrap_err(); let _ = Base::detect("hello world").unwrap_err(); } #[test] fn test_format_pos() { let expected_2: Vec<_> = POS_NUM.iter().map(|n| format!("{n:b}")).collect(); let expected_8: Vec<_> = POS_NUM.iter().map(|n| format!("{n:o}")).collect(); let expected_10: Vec<_> = POS_NUM.iter().map(|n| format!("{n:}")).collect(); let expected_l16: Vec<_> = POS_NUM.iter().map(|n| format!("{n:x}")).collect(); let expected_u16: Vec<_> = POS_NUM.iter().map(|n| format!("{n:X}")).collect(); for (i, n) in POS_NUM.into_iter().enumerate() { 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(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]); } } #[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| { 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(); let expected_l16: Vec<_> = NEG_NUM.iter().map(|n| format!("{n:f>64x}")).collect(); let expected_u16: Vec<_> = NEG_NUM.iter().map(|n| format!("{n:F>64X}")).collect(); for (i, n) in NEG_NUM.into_iter().enumerate() { 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(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]); } } #[test] fn test_fmt_macro() { let nums: Vec<_> = POS_NUM.into_iter().map(|n| NumberWithBase::from(I256::try_from(n).unwrap())).collect(); let actual_2: Vec<_> = nums.iter().map(|n| format!("{n:b}")).collect(); let actual_2_alt: Vec<_> = nums.iter().map(|n| format!("{n:#b}")).collect(); let actual_8: Vec<_> = nums.iter().map(|n| format!("{n:o}")).collect(); let actual_8_alt: Vec<_> = nums.iter().map(|n| format!("{n:#o}")).collect(); let actual_10: Vec<_> = nums.iter().map(|n| format!("{n:}")).collect(); let actual_10_alt: Vec<_> = nums.iter().map(|n| format!("{n:#}")).collect(); let actual_l16: Vec<_> = nums.iter().map(|n| format!("{n:x}")).collect(); let actual_l16_alt: Vec<_> = nums.iter().map(|n| format!("{n:#x}")).collect(); let actual_u16: Vec<_> = nums.iter().map(|n| format!("{n:X}")).collect(); let actual_u16_alt: Vec<_> = nums.iter().map(|n| format!("{n:#X}")).collect(); let expected_2: Vec<_> = POS_NUM.iter().map(|n| format!("{n:b}")).collect(); let expected_2_alt: Vec<_> = POS_NUM.iter().map(|n| format!("{n:#b}")).collect(); let expected_8: Vec<_> = POS_NUM.iter().map(|n| format!("{n:o}")).collect(); let expected_8_alt: Vec<_> = POS_NUM.iter().map(|n| format!("{n:#o}")).collect(); let expected_10: Vec<_> = POS_NUM.iter().map(|n| format!("{n:}")).collect(); let expected_10_alt: Vec<_> = POS_NUM.iter().map(|n| format!("{n:#}")).collect(); let expected_l16: Vec<_> = POS_NUM.iter().map(|n| format!("{n:x}")).collect(); let expected_l16_alt: Vec<_> = POS_NUM.iter().map(|n| format!("{n:#x}")).collect(); let expected_u16: Vec<_> = POS_NUM.iter().map(|n| format!("{n:X}")).collect(); let expected_u16_alt: Vec<_> = POS_NUM.iter().map(|n| format!("{n:#X}")).collect(); for (i, _) in POS_NUM.iter().enumerate() { assert_eq!(actual_2[i], expected_2[i]); assert_eq!(actual_2_alt[i], expected_2_alt[i]); assert_eq!(actual_8[i], expected_8[i]); assert_eq!(actual_8_alt[i], expected_8_alt[i]); assert_eq!(actual_10[i], expected_10[i]); assert_eq!(actual_10_alt[i], expected_10_alt[i]); assert_eq!(actual_l16[i], expected_l16[i]); assert_eq!(actual_l16_alt[i], expected_l16_alt[i]); assert_eq!(actual_u16[i], expected_u16[i]); assert_eq!(actual_u16_alt[i], expected_u16_alt[i]); } } } ================================================ FILE: crates/cast/src/cmd/access_list.rs ================================================ use crate::{ Cast, tx::{CastTxBuilder, SenderKind}, }; use alloy_ens::NameOrAddress; use alloy_network::{AnyNetwork, Network}; use alloy_rpc_types::BlockId; use clap::Parser; use eyre::Result; use foundry_cli::{ opts::{RpcOpts, TransactionOpts}, utils::LoadConfig, }; use foundry_common::provider::ProviderBuilder; use foundry_primitives::FoundryTransactionBuilder; use foundry_wallets::WalletOpts; use std::str::FromStr; use tempo_alloy::TempoNetwork; /// CLI arguments for `cast access-list`. #[derive(Debug, Parser)] pub struct AccessListArgs { /// The destination of the transaction. #[arg( value_name = "TO", value_parser = NameOrAddress::from_str )] to: Option, /// The signature of the function to call. #[arg(value_name = "SIG")] sig: Option, /// The arguments of the function to call. #[arg(value_name = "ARGS", allow_negative_numbers = true)] args: Vec, /// Raw hex-encoded data for the transaction. Used instead of \[SIG\] and \[ARGS\]. #[arg( long, conflicts_with_all = &["sig", "args"] )] data: Option, /// The block height to query at. /// /// Can also be the tags earliest, finalized, safe, latest, or pending. #[arg(long, short = 'B')] block: Option, #[command(flatten)] tx: TransactionOpts, #[command(flatten)] rpc: RpcOpts, #[command(flatten)] wallet: WalletOpts, } impl AccessListArgs { pub async fn run(self) -> Result<()> { if self.tx.tempo.is_tempo() { self.run_with_network::().await } else { self.run_with_network::().await } } pub async fn run_with_network(self) -> Result<()> where N::TransactionRequest: FoundryTransactionBuilder, { let Self { to, mut sig, args, data, tx, rpc, wallet, block } = self; if let Some(data) = data { sig = Some(data); } let config = rpc.load_config()?; let provider = ProviderBuilder::::from_config(&config)?.build()?; let sender = SenderKind::from_wallet_opts(wallet).await?; let (tx, _) = CastTxBuilder::new(&provider, tx, &config) .await? .with_to(to) .await? .with_code_sig_and_args(None, sig, args) .await? .raw() .build(sender) .await?; let access_list: String = Cast::new(&provider).access_list(&tx, block).await?; sh_println!("{access_list}")?; Ok(()) } } #[cfg(test)] mod tests { use super::*; use alloy_primitives::hex; use clap::error::ErrorKind; #[test] fn can_parse_access_list_data() { let data = hex::encode("hello"); let args = AccessListArgs::parse_from(["foundry-cli", "--data", data.as_str()]); assert_eq!(args.data, Some(data)); let data = hex::encode_prefixed("hello"); let args = AccessListArgs::parse_from(["foundry-cli", "--data", data.as_str()]); assert_eq!(args.data, Some(data)); } #[test] fn data_conflicts_with_sig_and_args() { let err = AccessListArgs::try_parse_from([ "foundry-cli", "0x0000000000000000000000000000000000000001", "transfer(address,uint256)", "0x0000000000000000000000000000000000000002", "1", "--data", "0x1234", ]) .unwrap_err(); assert_eq!(err.kind(), ErrorKind::ArgumentConflict); } } ================================================ FILE: crates/cast/src/cmd/artifact.rs ================================================ use super::{ creation_code::{fetch_creation_code_from_etherscan, parse_code_output}, interface::load_abi_from_file, }; use alloy_primitives::Address; use alloy_provider::Provider; use clap::Parser; use eyre::Result; use foundry_cli::{ opts::{EtherscanOpts, RpcOpts}, utils::{self, LoadConfig, fetch_abi_from_etherscan}, }; use foundry_common::fs; use serde_json::json; use std::path::PathBuf; foundry_config::impl_figment_convert!(ArtifactArgs, etherscan, rpc); /// CLI arguments for `cast artifact`. #[derive(Parser)] pub struct ArtifactArgs { /// An Ethereum address, for which the artifact will be produced. contract: Address, /// Path to file containing the contract's JSON ABI. It's necessary if the target contract is /// not verified on Etherscan. #[arg(long)] abi_path: Option, /// The path to the output file. /// /// If not specified, the artifact will be output to stdout. #[arg( short, long, value_hint = clap::ValueHint::FilePath, value_name = "PATH", )] output: Option, #[command(flatten)] etherscan: EtherscanOpts, #[command(flatten)] rpc: RpcOpts, } impl ArtifactArgs { pub async fn run(self) -> Result<()> { let mut config = self.load_config()?; let Self { contract, output: output_location, abi_path, etherscan: _, rpc: _ } = self; let provider = utils::get_provider(&config)?; let chain = provider.get_chain_id().await?; config.chain = Some(chain.into()); let abi = if let Some(ref abi_path) = abi_path { load_abi_from_file(abi_path, None)? } else { fetch_abi_from_etherscan(contract, &config).await? }; let (abi, _) = abi.first().ok_or_else(|| eyre::eyre!("No ABI found"))?; let bytecode = fetch_creation_code_from_etherscan(contract, &config, provider).await?; let bytecode = parse_code_output(bytecode, contract, &config, abi_path.as_deref(), true, false) .await?; let artifact = json!({ "abi": abi, "bytecode": { "object": bytecode } }); let artifact = serde_json::to_string_pretty(&artifact)?; if let Some(loc) = output_location { if let Some(parent) = loc.parent() { fs::create_dir_all(parent)?; } fs::write(&loc, artifact)?; sh_println!("Saved artifact at {}", loc.display())?; } else { sh_println!("{artifact}")?; } Ok(()) } } ================================================ FILE: crates/cast/src/cmd/b2e_payload.rs ================================================ //! Command Line handler to convert Beacon block's execution payload to Execution format. use std::path::PathBuf; use alloy_rpc_types_beacon::payload::BeaconBlockData; use clap::{Parser, builder::ValueParser}; use eyre::{Result, eyre}; use foundry_common::{fs, sh_print}; /// CLI arguments for `cast b2e-payload`, convert Beacon block's execution payload to Execution /// format. #[derive(Parser)] pub struct B2EPayloadArgs { /// Input data, it can be either a file path to JSON file or raw JSON string containing the /// beacon block #[arg(value_name = "INPUT", value_parser=ValueParser::new(parse_input_source), help = "File path to JSON file or raw JSON string containing the beacon block")] pub input: InputSource, } impl B2EPayloadArgs { pub async fn run(self) -> Result<()> { let beacon_block_json = match self.input { InputSource::Json(json) => json, InputSource::File(path) => fs::read_to_string(&path) .map_err(|e| eyre!("Failed to read JSON file '{}': {}", path.display(), e))?, }; let beacon_block_data: BeaconBlockData = serde_json::from_str(&beacon_block_json) .map_err(|e| eyre!("Failed to parse beacon block JSON: {}", e))?; let execution_payload = beacon_block_data.execution_payload(); // Output raw execution payload let output = serde_json::to_string(&execution_payload) .map_err(|e| eyre!("Failed to serialize execution payload: {}", e))?; sh_print!("{}", output)?; Ok(()) } } /// Represents the different input sources for beacon block data #[derive(Debug, Clone)] pub enum InputSource { /// Path to a JSON file containing beacon block data File(PathBuf), /// Raw JSON string containing beacon block data Json(String), } fn parse_input_source(s: &str) -> Result { // Try parsing as JSON first if serde_json::from_str::(s).is_ok() { return Ok(InputSource::Json(s.to_string())); } // Otherwise treat as file path Ok(InputSource::File(PathBuf::from(s))) } #[cfg(test)] mod tests { use super::*; #[test] fn test_parse_input_source_json_object() { let json_input = r#"{"execution_payload": {"block_hash": "0x123"}}"#; let result = parse_input_source(json_input).unwrap(); match result { InputSource::Json(json) => assert_eq!(json, json_input), InputSource::File(_) => panic!("Expected JSON input, got File"), } } #[test] fn test_parse_input_source_json_array() { let json_input = r#"[{"block": "data"}]"#; let result = parse_input_source(json_input).unwrap(); match result { InputSource::Json(json) => assert_eq!(json, json_input), InputSource::File(_) => panic!("Expected JSON input, got File"), } } #[test] fn test_parse_input_source_file_path() { let file_path = "block-12225729-6ceadbf2a6adbbd64cbec33fdebbc582f25171cd30ac43f641cbe76ac7313ddf.json"; let result = parse_input_source(file_path).unwrap(); match result { InputSource::File(path) => assert_eq!(path, PathBuf::from(file_path)), InputSource::Json(_) => panic!("Expected File input, got JSON"), } } #[test] fn test_parse_input_source_malformed_but_not_json() { let malformed = "not-json-{"; let result = parse_input_source(malformed).unwrap(); // Should be treated as file path since it's not valid JSON match result { InputSource::File(path) => assert_eq!(path, PathBuf::from(malformed)), InputSource::Json(_) => panic!("Expected File input, got File"), } } } ================================================ FILE: crates/cast/src/cmd/bind.rs ================================================ use clap::{Parser, ValueHint}; use eyre::Result; use foundry_cli::opts::EtherscanOpts; use std::path::PathBuf; const DEFAULT_CRATE_NAME: &str = "foundry-contracts"; const DEFAULT_CRATE_VERSION: &str = "0.0.1"; /// CLI arguments for `cast bind`. #[derive(Clone, Debug, Parser)] pub struct BindArgs { /// The contract address, or the path to an ABI Directory /// /// If an address is specified, then the ABI is fetched from Etherscan. path_or_address: String, /// Path to where bindings will be stored #[arg( short, long, value_hint = ValueHint::DirPath, value_name = "PATH" )] pub output_dir: Option, /// The name of the Rust crate to generate. /// /// This should be a valid crates.io crate name. However, this is currently not validated by /// this command. #[arg( long, default_value = DEFAULT_CRATE_NAME, value_name = "NAME" )] crate_name: String, /// The version of the Rust crate to generate. /// /// This should be a standard semver version string. However, it is not currently validated by /// this command. #[arg( long, default_value = DEFAULT_CRATE_VERSION, value_name = "VERSION" )] crate_version: String, /// Generate bindings as separate files. #[arg(long)] separate_files: bool, #[command(flatten)] etherscan: EtherscanOpts, } impl BindArgs { pub async fn run(self) -> Result<()> { Err(eyre::eyre!( "`cast bind` has been removed.\n\ Please use `cast source` to create a Forge project from a block explorer source\n\ and `forge bind` to generate the bindings to it instead." )) } } ================================================ FILE: crates/cast/src/cmd/call.rs ================================================ use super::run::fetch_contracts_bytecode_from_trace; use crate::{ Cast, debug::handle_traces, traces::TraceKind, tx::{CastTxBuilder, SenderKind}, }; use alloy_ens::NameOrAddress; use alloy_network::{AnyNetwork, Network, TransactionBuilder}; use alloy_primitives::{Address, B256, Bytes, TxKind, U256, hex, map::HashMap}; use alloy_provider::Provider; use alloy_rpc_types::{ BlockId, BlockNumberOrTag, BlockOverrides, state::{StateOverride, StateOverridesBuilder}, }; use clap::Parser; use eyre::Result; use foundry_cli::{ opts::{ChainValueParser, RpcOpts, TransactionOpts}, utils::{LoadConfig, TraceResult, parse_ether_value}, }; use foundry_common::{ abi::{encode_function_args, get_func}, provider::{ProviderBuilder, curl_transport::generate_curl_command}, sh_println, shell, }; use foundry_compilers::artifacts::EvmVersion; use foundry_config::{ Chain, Config, figment::{ self, Metadata, Profile, value::{Dict, Map}, }, }; use foundry_evm::{ executors::TracingExecutor, opts::EvmOpts, traces::{InternalTraceMode, TraceMode}, }; use foundry_primitives::FoundryTransactionBuilder; use foundry_wallets::WalletOpts; use itertools::Either; use regex::Regex; use revm::context::TransactionType; use std::{str::FromStr, sync::LazyLock}; use tempo_alloy::TempoNetwork; // matches override pattern
:: // e.g. 0x123:0x1:0x1234 static OVERRIDE_PATTERN: LazyLock = LazyLock::new(|| Regex::new(r"^([^:]+):([^:]+):([^:]+)$").unwrap()); /// CLI arguments for `cast call`. /// /// ## State Override Flags /// /// The following flags can be used to override the state for the call: /// /// * `--override-balance
:` - Override the balance of an account /// * `--override-nonce
:` - Override the nonce of an account /// * `--override-code
:` - Override the code of an account /// * `--override-state
::` - Override a storage slot of an account /// /// Multiple overrides can be specified for the same account. For example: /// /// ```bash /// cast call 0x... "transfer(address,uint256)" 0x... 100 \ /// --override-balance 0x123:0x1234 \ /// --override-nonce 0x123:1 \ /// --override-code 0x123:0x1234 \ /// --override-state 0x123:0x1:0x1234 /// --override-state-diff 0x123:0x1:0x1234 /// ``` #[derive(Debug, Parser)] pub struct CallArgs { /// The destination of the transaction. #[arg(value_parser = NameOrAddress::from_str)] to: Option, /// The signature of the function to call. sig: Option, /// The arguments of the function to call. #[arg(allow_negative_numbers = true)] args: Vec, /// Raw hex-encoded data for the transaction. Used instead of \[SIG\] and \[ARGS\]. #[arg( long, conflicts_with_all = &["sig", "args"] )] data: Option, /// Forks the remote rpc, executes the transaction locally and prints a trace #[arg(long, default_value_t = false)] trace: bool, /// Disables the labels in the traces. /// Can only be set with `--trace`. #[arg(long, default_value_t = false, requires = "trace")] disable_labels: bool, /// Opens an interactive debugger. /// Can only be used with `--trace`. #[arg(long, requires = "trace")] debug: bool, /// Identify internal functions in traces. /// /// This will trace internal functions and decode stack parameters. /// /// Parameters stored in memory (such as bytes or arrays) are currently decoded only when a /// single function is matched, similarly to `--debug`, for performance reasons. #[arg(long, requires = "trace")] decode_internal: bool, /// Labels to apply to the traces; format: `address:label`. /// Can only be used with `--trace`. #[arg(long, requires = "trace")] labels: Vec, /// The EVM Version to use. /// Can only be used with `--trace`. #[arg(long, requires = "trace")] evm_version: Option, /// The block height to query at. /// /// Can also be the tags earliest, finalized, safe, latest, or pending. #[arg(long, short)] block: Option, #[command(subcommand)] command: Option, #[command(flatten)] tx: TransactionOpts, #[command(flatten)] rpc: RpcOpts, #[command(flatten)] wallet: WalletOpts, #[arg( short, long, alias = "chain-id", env = "CHAIN", value_parser = ChainValueParser::default(), )] pub chain: Option, /// Use current project artifacts for trace decoding. #[arg(long, visible_alias = "la")] pub with_local_artifacts: bool, /// Override the accounts balance. /// Format: "address:balance,address:balance" #[arg(long = "override-balance", value_name = "ADDRESS:BALANCE", value_delimiter = ',')] pub balance_overrides: Option>, /// Override the accounts nonce. /// Format: "address:nonce,address:nonce" #[arg(long = "override-nonce", value_name = "ADDRESS:NONCE", value_delimiter = ',')] pub nonce_overrides: Option>, /// Override the accounts code. /// Format: "address:code,address:code" #[arg(long = "override-code", value_name = "ADDRESS:CODE", value_delimiter = ',')] pub code_overrides: Option>, /// Override the accounts state and replace the current state entirely with the new one. /// Format: "address:slot:value,address:slot:value" #[arg(long = "override-state", value_name = "ADDRESS:SLOT:VALUE", value_delimiter = ',')] pub state_overrides: Option>, /// Override the accounts state specific slots and preserve the rest of the state. /// Format: "address:slot:value,address:slot:value" #[arg(long = "override-state-diff", value_name = "ADDRESS:SLOT:VALUE", value_delimiter = ',')] 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)] pub enum CallSubcommands { /// ignores the address field and simulates creating a contract #[command(name = "--create")] Create { /// Bytecode of contract. code: String, /// The signature of the constructor. sig: Option, /// The arguments of the constructor. #[arg(allow_negative_numbers = true)] args: Vec, /// Ether to send in the transaction. /// /// Either specified in wei, or as a string with a unit type. /// /// Examples: 1ether, 10gwei, 0.01ether #[arg(long, value_parser = parse_ether_value)] value: Option, }, } impl CallArgs { pub async fn run(self) -> Result<()> { // Handle --curl mode early, before any provider interaction if self.rpc.curl { return self.run_curl().await; } if self.tx.tempo.is_tempo() { self.run_with_network::().await } else { self.run_with_network::().await } } pub async fn run_with_network(self) -> Result<()> where N::TransactionRequest: FoundryTransactionBuilder, { let figment = self.rpc.clone().into_figment(self.with_local_artifacts).merge(&self); 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, mut sig, mut args, mut tx, command, block, trace, evm_version, debug, decode_internal, labels, data, with_local_artifacts, disable_labels, wallet, .. } = self; if let Some(data) = data { sig = Some(data); } let provider = ProviderBuilder::::from_config(&config)?.build()?; let sender = SenderKind::from_wallet_opts(wallet).await?; let from = sender.address(); let code = if let Some(CallSubcommands::Create { code, sig: create_sig, args: create_args, value, }) = command { sig = create_sig; args = create_args; if let Some(value) = value { tx.value = Some(value); } Some(code) } else { None }; let (tx, func) = CastTxBuilder::new(&provider, tx, &config) .await? .with_to(to) .await? .with_code_sig_and_args(code, sig, args) .await? .raw() .build(sender) .await?; if trace { if let Some(BlockId::Number(BlockNumberOrTag::Number(block_number))) = self.block { // Override Config `fork_block_number` (if set) with CLI value. config.fork_block_number = Some(block_number); } let create2_deployer = evm_opts.create2_deployer; let (mut evm_env, tx_env, fork, chain, networks) = TracingExecutor::get_fork_material(&mut config, evm_opts).await?; // modify settings that usually set in eth_call evm_env.cfg_env.disable_block_gas_limit = true; evm_env.cfg_env.tx_gas_limit_cap = Some(u64::MAX); evm_env.block_env.gas_limit = u64::MAX; // Apply the block overrides. if let Some(block_overrides) = block_overrides { if let Some(number) = block_overrides.number { evm_env.block_env.number = number.to(); } if let Some(time) = block_overrides.time { evm_env.block_env.timestamp = U256::from(time); } } let trace_mode = TraceMode::Call .with_debug(debug) .with_decode_internal(if decode_internal { InternalTraceMode::Full } else { InternalTraceMode::None }) .with_state_changes(shell::verbosity() > 4); let mut executor = TracingExecutor::new( (evm_env, tx_env), fork, evm_version, trace_mode, networks, create2_deployer, state_overrides, )?; let value = tx.value().unwrap_or_default(); let input = tx.input().cloned().unwrap_or_default(); let tx_kind = tx.kind().expect("set by builder"); let env_tx = executor.tx_env_mut(); // Set transaction options with --trace if let Some(gas_limit) = tx.gas_limit() { env_tx.gas_limit = gas_limit; } if let Some(gas_price) = tx.gas_price() { env_tx.gas_price = gas_price; } if let Some(max_fee_per_gas) = tx.max_fee_per_gas() { env_tx.gas_price = max_fee_per_gas; } if let Some(max_priority_fee_per_gas) = tx.max_priority_fee_per_gas() { env_tx.gas_priority_fee = Some(max_priority_fee_per_gas); } if let Some(max_fee_per_blob_gas) = tx.max_fee_per_blob_gas() { env_tx.max_fee_per_blob_gas = max_fee_per_blob_gas; } if let Some(nonce) = tx.nonce() { env_tx.nonce = nonce; } env_tx.tx_type = tx.output_tx_type().into(); if let Some(access_list) = tx.access_list().cloned() { env_tx.access_list = access_list; if env_tx.tx_type == TransactionType::Legacy as u8 { env_tx.tx_type = TransactionType::Eip2930 as u8; } } if let Some(auth) = tx.authorization_list().cloned() { env_tx.authorization_list = auth.into_iter().map(Either::Left).collect(); env_tx.tx_type = TransactionType::Eip7702 as u8; } let trace = match tx_kind { TxKind::Create => { let deploy_result = executor.deploy(from, input, value, None); TraceResult::try_from(deploy_result)? } TxKind::Call(to) => TraceResult::from_raw( executor.transact_raw(from, to, input, value)?, TraceKind::Execution, ), }; let contracts_bytecode = fetch_contracts_bytecode_from_trace(&executor, &trace)?; handle_traces( trace, &config, chain, &contracts_bytecode, labels, with_local_artifacts, debug, decode_internal, disable_labels, None, ) .await?; return Ok(()); } let response = Cast::new(&provider) .call(&tx, func.as_ref(), block, state_overrides, block_overrides) .await?; if response == "0x" && let Some(contract_address) = tx.to() { let code = provider.get_code_at(contract_address).await?; if code.is_empty() { sh_warn!("Contract code is empty")?; } } sh_println!("{}", response)?; Ok(()) } /// Handle --curl mode by generating curl command without any RPC interaction. async fn run_curl(self) -> Result<()> { let config = self.rpc.load_config()?; let url = config.get_rpc_url_or_localhost_http()?; let jwt = config.get_rpc_jwt_secret()?; // Get call data - either from --data or from sig + args let data = if let Some(data) = &self.data { hex::decode(data)? } else if let Some(sig) = &self.sig { // If sig is already hex data, use it directly if let Ok(data) = hex::decode(sig) { data } else { // Parse function signature and encode args let func = get_func(sig)?; encode_function_args(&func, &self.args)? } } else { Vec::new() }; // Resolve the destination address (must be a raw address for curl mode) let to = self.to.as_ref().map(|n| match n { NameOrAddress::Address(addr) => Ok(*addr), NameOrAddress::Name(name) => { eyre::bail!("ENS names are not supported with --curl. Please use a raw address instead of '{}'", name) } }).transpose()?; // Build eth_call params let call_object = serde_json::json!({ "to": to, "data": format!("0x{}", hex::encode(&data)), }); let block_param = self .block .map(|b| serde_json::to_value(b).unwrap_or(serde_json::json!("latest"))) .unwrap_or(serde_json::json!("latest")); let params = serde_json::json!([call_object, block_param]); let curl_cmd = generate_curl_command( url.as_ref(), "eth_call", params, config.eth_rpc_headers.as_deref(), jwt.as_deref(), ); sh_println!("{}", curl_cmd)?; Ok(()) } /// 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(), self.state_diff_overrides.as_ref(), ] .iter() .all(Option::is_none) { return Ok(None); } let mut state_overrides_builder = StateOverridesBuilder::default(); // Parse balance overrides for override_str in self.balance_overrides.iter().flatten() { let (addr, balance) = address_value_override(override_str)?; state_overrides_builder = state_overrides_builder.with_balance(addr.parse()?, balance.parse()?); } // Parse nonce overrides for override_str in self.nonce_overrides.iter().flatten() { let (addr, nonce) = address_value_override(override_str)?; state_overrides_builder = state_overrides_builder.with_nonce(addr.parse()?, nonce.parse()?); } // Parse code overrides for override_str in self.code_overrides.iter().flatten() { let (addr, code_str) = address_value_override(override_str)?; state_overrides_builder = state_overrides_builder.with_code(addr.parse()?, Bytes::from_str(code_str)?); } type StateOverrides = HashMap>; let parse_state_overrides = |overrides: &Option>| -> Result { let mut state_overrides: StateOverrides = StateOverrides::default(); overrides.iter().flatten().try_for_each(|s| -> Result<(), eyre::Report> { let (addr, slot, value) = address_slot_value_override(s)?; state_overrides.entry(addr).or_default().insert(slot.into(), value.into()); Ok(()) })?; Ok(state_overrides) }; // Parse and apply state overrides for (addr, entries) in parse_state_overrides(&self.state_overrides)? { state_overrides_builder = state_overrides_builder.with_state(addr, entries); } // Parse and apply state diff overrides for (addr, entries) in parse_state_overrides(&self.state_diff_overrides)? { state_overrides_builder = state_overrides_builder.with_state_diff(addr, entries) } 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)) } } } impl figment::Provider for CallArgs { fn metadata(&self) -> Metadata { Metadata::named("CallArgs") } fn data(&self) -> Result, figment::Error> { let mut map = Map::new(); if let Some(evm_version) = self.evm_version { map.insert("evm_version".into(), figment::value::Value::serialize(evm_version)?); } Ok(Map::from([(Config::selected_profile(), map)])) } } /// Parse an override string in the format address:value. fn address_value_override(address_override: &str) -> Result<(&str, &str)> { address_override.split_once(':').ok_or_else(|| { eyre::eyre!("Invalid override {address_override}. Expected
:") }) } /// Parse an override string in the format address:slot:value. fn address_slot_value_override(address_override: &str) -> Result<(Address, U256, U256)> { let captures = OVERRIDE_PATTERN.captures(address_override).ok_or_else(|| { eyre::eyre!("Invalid override {address_override}. Expected
::") })?; Ok(( captures[1].parse()?, // Address captures[2].parse()?, // Slot (U256) captures[3].parse()?, // Value (U256) )) } #[cfg(test)] mod tests { use super::*; use alloy_primitives::{U64, address, b256, fixed_bytes}; #[test] fn test_get_state_overrides() { let call_args = CallArgs::parse_from([ "foundry-cli", "--override-balance", "0x0000000000000000000000000000000000000001:2", "--override-nonce", "0x0000000000000000000000000000000000000001:3", "--override-code", "0x0000000000000000000000000000000000000001:0x04", "--override-state", "0x0000000000000000000000000000000000000001:5:6", "--override-state-diff", "0x0000000000000000000000000000000000000001:7:8", ]); let overrides = call_args.get_state_overrides().unwrap().unwrap(); let address = address!("0x0000000000000000000000000000000000000001"); if let Some(account_override) = overrides.get(&address) { if let Some(balance) = account_override.balance { assert_eq!(balance, U256::from(2)); } if let Some(nonce) = account_override.nonce { assert_eq!(nonce, 3); } if let Some(code) = &account_override.code { assert_eq!(*code, Bytes::from([0x04])); } if let Some(state) = &account_override.state && let Some(value) = state.get(&b256!( "0x0000000000000000000000000000000000000000000000000000000000000005" )) { assert_eq!( *value, b256!("0x0000000000000000000000000000000000000000000000000000000000000006") ); } if let Some(state_diff) = &account_override.state_diff && let Some(value) = state_diff.get(&b256!( "0x0000000000000000000000000000000000000000000000000000000000000007" )) { assert_eq!( *value, b256!("0x0000000000000000000000000000000000000000000000000000000000000008") ); } } } #[test] fn test_get_state_overrides_empty() { let call_args = CallArgs::parse_from([""]); let overrides = call_args.get_state_overrides().unwrap(); assert_eq!(overrides, None); } #[test] fn test_get_block_overrides() { let mut call_args = CallArgs::parse_from([""]); call_args.block_number = Some(1); call_args.block_time = Some(2); let overrides = call_args.get_block_overrides().unwrap().unwrap(); assert_eq!(overrides.number, Some(U256::from(1))); assert_eq!(overrides.time, Some(2)); } #[test] fn test_get_block_overrides_empty() { let call_args = CallArgs::parse_from([""]); let overrides = call_args.get_block_overrides().unwrap(); assert_eq!(overrides, None); } #[test] fn test_address_value_override_success() { let text = "0x0000000000000000000000000000000000000001:2"; let (address, value) = address_value_override(text).unwrap(); assert_eq!(address, "0x0000000000000000000000000000000000000001"); assert_eq!(value, "2"); } #[test] fn test_address_value_override_error() { let text = "invalid_value"; let error = address_value_override(text).unwrap_err(); assert_eq!(error.to_string(), "Invalid override invalid_value. Expected
:"); } #[test] fn test_address_slot_value_override_success() { let text = "0x0000000000000000000000000000000000000001:2:3"; let (address, slot, value) = address_slot_value_override(text).unwrap(); assert_eq!(*address, fixed_bytes!("0x0000000000000000000000000000000000000001")); assert_eq!(slot, U256::from(2)); assert_eq!(value, U256::from(3)); } #[test] fn test_address_slot_value_override_error() { let text = "invalid_value"; let error = address_slot_value_override(text).unwrap_err(); assert_eq!( error.to_string(), "Invalid override invalid_value. Expected
::" ); } #[test] fn can_parse_call_data() { let data = hex::encode("hello"); let args = CallArgs::parse_from(["foundry-cli", "--data", data.as_str()]); assert_eq!(args.data, Some(data)); let data = hex::encode_prefixed("hello"); let args = CallArgs::parse_from(["foundry-cli", "--data", data.as_str()]); assert_eq!(args.data, Some(data)); } #[test] fn can_parse_state_overrides() { let args = CallArgs::parse_from([ "foundry-cli", "--override-balance", "0x123:0x1234", "--override-nonce", "0x123:1", "--override-code", "0x123:0x1234", "--override-state", "0x123:0x1:0x1234", ]); assert_eq!(args.balance_overrides, Some(vec!["0x123:0x1234".to_string()])); assert_eq!(args.nonce_overrides, Some(vec!["0x123:1".to_string()])); assert_eq!(args.code_overrides, Some(vec!["0x123:0x1234".to_string()])); assert_eq!(args.state_overrides, Some(vec!["0x123:0x1:0x1234".to_string()])); } #[test] fn can_parse_multiple_state_overrides() { let args = CallArgs::parse_from([ "foundry-cli", "--override-balance", "0x123:0x1234", "--override-balance", "0x456:0x5678", "--override-nonce", "0x123:1", "--override-nonce", "0x456:2", "--override-code", "0x123:0x1234", "--override-code", "0x456:0x5678", "--override-state", "0x123:0x1:0x1234", "--override-state", "0x456:0x2:0x5678", ]); assert_eq!( args.balance_overrides, Some(vec!["0x123:0x1234".to_string(), "0x456:0x5678".to_string()]) ); assert_eq!(args.nonce_overrides, Some(vec!["0x123:1".to_string(), "0x456:2".to_string()])); assert_eq!( args.code_overrides, Some(vec!["0x123:0x1234".to_string(), "0x456:0x5678".to_string()]) ); assert_eq!( args.state_overrides, Some(vec!["0x123:0x1:0x1234".to_string(), "0x456:0x2:0x5678".to_string()]) ); } #[test] fn test_negative_args_with_flags() { // Test that negative args work with flags let args = CallArgs::parse_from([ "foundry-cli", "--trace", "0xDeaDBeeFcAfEbAbEfAcEfEeDcBaDbEeFcAfEbAbE", "process(int256)", "-999999", "--debug", ]); assert!(args.trace); assert!(args.debug); assert_eq!(args.args, vec!["-999999"]); } #[test] fn test_transaction_opts_with_trace() { // Test that transaction options are correctly parsed when using --trace let args = CallArgs::parse_from([ "foundry-cli", "--trace", "--gas-limit", "1000000", "--gas-price", "20000000000", "--priority-gas-price", "2000000000", "--nonce", "42", "--value", "1000000000000000000", // 1 ETH "--blob-gas-price", "10000000000", "0xDeaDBeeFcAfEbAbEfAcEfEeDcBaDbEeFcAfEbAbE", "balanceOf(address)", "0x123456789abcdef123456789abcdef123456789a", ]); assert!(args.trace); assert_eq!(args.tx.gas_limit, Some(U256::from(1000000u32))); assert_eq!(args.tx.gas_price, Some(U256::from(20000000000u64))); assert_eq!(args.tx.priority_gas_price, Some(U256::from(2000000000u64))); assert_eq!(args.tx.nonce, Some(U64::from(42))); assert_eq!(args.tx.value, Some(U256::from(1000000000000000000u64))); assert_eq!(args.tx.blob_gas_price, Some(U256::from(10000000000u64))); } } ================================================ FILE: crates/cast/src/cmd/constructor_args.rs ================================================ use super::{creation_code::fetch_creation_code_from_etherscan, interface::load_abi_from_file}; use alloy_dyn_abi::DynSolType; use alloy_primitives::{Address, Bytes}; use alloy_provider::Provider; use clap::Parser; use eyre::{OptionExt, Result, eyre}; use foundry_cli::{ opts::{EtherscanOpts, RpcOpts}, utils::{self, LoadConfig, fetch_abi_from_etherscan}, }; use foundry_config::Config; foundry_config::impl_figment_convert!(ConstructorArgsArgs, etherscan, rpc); /// CLI arguments for `cast creation-args`. #[derive(Parser)] pub struct ConstructorArgsArgs { /// An Ethereum address, for which the bytecode will be fetched. contract: Address, /// Path to file containing the contract's JSON ABI. It's necessary if the target contract is /// not verified on Etherscan #[arg(long)] abi_path: Option, #[command(flatten)] etherscan: EtherscanOpts, #[command(flatten)] rpc: RpcOpts, } impl ConstructorArgsArgs { pub async fn run(self) -> Result<()> { let mut config = self.load_config()?; let Self { contract, abi_path, etherscan: _, rpc: _ } = self; let provider = utils::get_provider(&config)?; config.chain = Some(provider.get_chain_id().await?.into()); let bytecode = fetch_creation_code_from_etherscan(contract, &config, provider).await?; let args_arr = parse_constructor_args(bytecode, contract, &config, abi_path).await?; for arg in args_arr { let _ = sh_println!("{arg}"); } Ok(()) } } /// Fetches the constructor arguments values and types from the creation bytecode and ABI. async fn parse_constructor_args( bytecode: Bytes, contract: Address, config: &Config, abi_path: Option, ) -> Result> { let abi = if let Some(abi_path) = abi_path { load_abi_from_file(&abi_path, None)? } else { fetch_abi_from_etherscan(contract, config).await? }; let abi = abi.into_iter().next().ok_or_eyre("No ABI found.")?; let (abi, _) = abi; let constructor = abi.constructor.ok_or_else(|| eyre!("No constructor found."))?; if constructor.inputs.is_empty() { return Err(eyre!("No constructor arguments found.")); } let args_size = constructor.inputs.len() * 32; if bytecode.len() < args_size { return Err(eyre!( "Invalid creation bytecode length: have {} bytes, need at least {} for {} constructor inputs", bytecode.len(), args_size, constructor.inputs.len() )); } let args_bytes = Bytes::from(bytecode[bytecode.len() - args_size..].to_vec()); let display_args: Vec = args_bytes .chunks(32) .enumerate() .map(|(i, arg)| format_arg(&constructor.inputs[i].ty, arg)) .collect::>>()?; Ok(display_args) } fn format_arg(ty: &str, arg: &[u8]) -> Result { let arg_type: DynSolType = ty.parse()?; let decoded = arg_type.abi_decode(arg)?; let bytes = Bytes::from(arg.to_vec()); Ok(format!("{bytes} → {decoded:?}")) } ================================================ FILE: crates/cast/src/cmd/create2.rs ================================================ use alloy_primitives::{Address, B256, U256, hex, keccak256}; use clap::Parser; use eyre::{Result, WrapErr}; use rand::{RngCore, SeedableRng, rngs::StdRng}; use regex::RegexSetBuilder; use std::{ sync::{ Arc, atomic::{AtomicBool, Ordering}, }, time::Instant, }; // https://etherscan.io/address/0x4e59b44847b379578588920ca78fbf26c0b4956c#code const DEPLOYER: &str = "0x4e59b44847b379578588920ca78fbf26c0b4956c"; /// CLI arguments for `cast create2`. #[derive(Clone, Debug, Parser)] pub struct Create2Args { /// Prefix for the contract address. #[arg( long, short, required_unless_present_any = &["ends_with", "matching", "salt"], value_name = "HEX" )] starts_with: Option, /// Suffix for the contract address. #[arg(long, short, value_name = "HEX")] ends_with: Option, /// Sequence that the address has to match. #[arg(long, short, value_name = "HEX")] matching: Option, /// Case sensitive matching. #[arg(short, long)] case_sensitive: bool, /// Address of the contract deployer. #[arg( short, long, default_value = DEPLOYER, value_name = "ADDRESS" )] deployer: Address, /// Salt to be used for the contract deployment. This option separate from the default salt /// mining with filters. #[arg( long, conflicts_with_all = [ "starts_with", "ends_with", "matching", "case_sensitive", "caller", "seed", "no_random" ], value_name = "HEX" )] salt: Option, /// Init code of the contract to be deployed. #[arg(short, long, value_name = "HEX")] init_code: Option, /// Init code hash of the contract to be deployed. #[arg(alias = "ch", long, value_name = "HASH", required_unless_present = "init_code")] init_code_hash: Option, /// Number of threads to use. Specifying 0 defaults to the number of logical cores. #[arg(global = true, long, short = 'j', visible_alias = "jobs")] threads: Option, /// Address of the caller. Used for the first 20 bytes of the salt. #[arg(long, value_name = "ADDRESS")] caller: Option
, /// The random number generator's seed, used to initialize the salt. #[arg(long, value_name = "HEX")] seed: Option, /// Don't initialize the salt with a random value, and instead use the default value of 0. #[arg(long, conflicts_with = "seed")] no_random: bool, } pub struct Create2Output { pub address: Address, pub salt: B256, } impl Create2Args { pub fn run(self) -> Result { let Self { starts_with, ends_with, matching, case_sensitive, deployer, salt, init_code, init_code_hash, threads, caller, seed, no_random, } = self; let init_code_hash = if let Some(init_code_hash) = init_code_hash { hex::FromHex::from_hex(init_code_hash) } else if let Some(init_code) = init_code { hex::decode(init_code).map(keccak256) } else { unreachable!(); }?; if let Some(salt) = salt { let salt = hex::FromHex::from_hex(salt)?; let address = deployer.create2(salt, init_code_hash); sh_println!("{address}")?; return Ok(Create2Output { address, salt }); } let mut regexs = vec![]; if let Some(matches) = matching { if starts_with.is_some() || ends_with.is_some() { eyre::bail!("Either use --matching or --starts/ends-with"); } let matches = matches.trim_start_matches("0x"); if matches.len() != 40 { eyre::bail!("Please provide a 40 characters long sequence for matching"); } hex::decode(matches.replace('X', "0")).wrap_err("invalid matching hex provided")?; // replacing X placeholders by . to match any character at these positions regexs.push(matches.replace('X', ".")); } if let Some(prefix) = starts_with { regexs.push(format!( r"^{}", get_regex_hex_string(prefix).wrap_err("invalid prefix hex provided")? )); } if let Some(suffix) = ends_with { regexs.push(format!( r"{}$", get_regex_hex_string(suffix).wrap_err("invalid suffix hex provided")? )) } debug_assert!( regexs.iter().map(|p| p.len() - 1).sum::() <= 40, "vanity patterns length exceeded. cannot be more than 40 characters", ); let regex = RegexSetBuilder::new(regexs).case_insensitive(!case_sensitive).build()?; let mut n_threads = threads.unwrap_or(0); if n_threads == 0 { n_threads = std::thread::available_parallelism().map_or(1, |n| n.get()); } if cfg!(test) { n_threads = n_threads.min(2); } let mut salt = B256::ZERO; let remaining = if let Some(caller_address) = caller { salt[..20].copy_from_slice(&caller_address.into_array()); &mut salt[20..] } else { &mut salt[..] }; if !no_random { let mut rng = match seed { Some(seed) => StdRng::from_seed(seed.0), None => StdRng::from_os_rng(), }; rng.fill_bytes(remaining); } sh_println!("Configuration:")?; sh_println!("Init code hash: {init_code_hash}")?; sh_println!("Regex patterns: {:?}\n", regex.patterns())?; sh_println!( "Starting to generate deterministic contract address with {n_threads} threads..." )?; let mut handles = Vec::with_capacity(n_threads); let found = Arc::new(AtomicBool::new(false)); let timer = Instant::now(); // Loops through all possible salts in parallel until a result is found. // Each thread iterates over `(i..).step_by(n_threads)`. for i in 0..n_threads { // Create local copies for the thread. let increment = n_threads; let regex = regex.clone(); let regex_len = regex.patterns().len(); let found = Arc::clone(&found); handles.push(std::thread::spawn(move || { // Read the first bytes of the salt as a usize to be able to increment it. struct B256Aligned(B256, [usize; 0]); let mut salt = B256Aligned(salt, []); // SAFETY: B256 is aligned to `usize`. let salt_word = unsafe { &mut *salt.0.as_mut_ptr().add(32 - usize::BITS as usize / 8).cast::() }; // Important: add the thread index to the salt to avoid duplicate results. *salt_word = salt_word.wrapping_add(i); // Use checksum format only when case_sensitive is enabled. // This avoids an extra keccak256 call per iteration when not needed. let mut checksum_buf = [0u8; 42]; let mut hex_buf = [0u8; 40]; loop { // Stop if a result was found in another thread. if found.load(Ordering::Relaxed) { break None; } // Calculate the `CREATE2` address. #[expect(clippy::needless_borrows_for_generic_args)] let addr = deployer.create2(&salt.0, &init_code_hash); // Check if the regex matches the calculated address. // When case_sensitive is true, use EIP-55 checksum format (requires keccak256). // Otherwise, use lowercase hex to avoid the extra hash computation. let s = if case_sensitive { let _ = addr.to_checksum_raw(&mut checksum_buf, None); // SAFETY: stripping 2 ASCII bytes ("0x") off of an already valid UTF-8 // string is safe. unsafe { std::str::from_utf8_unchecked(checksum_buf.get_unchecked(2..)) } } else { // SAFETY: hex::encode_to_slice always produces valid UTF-8 (hex digits). let _ = hex::encode_to_slice(addr.as_slice(), &mut hex_buf); unsafe { std::str::from_utf8_unchecked(&hex_buf) } }; if regex.matches(s).into_iter().count() == regex_len { // Notify other threads that we found a result. found.store(true, Ordering::Relaxed); break Some((addr, salt.0)); } // Increment the salt for the next iteration. *salt_word = salt_word.wrapping_add(increment); } })); } let results = handles.into_iter().filter_map(|h| h.join().unwrap()).collect::>(); let (address, salt) = results.into_iter().next().unwrap(); sh_println!("Successfully found contract address in {:?}", timer.elapsed())?; sh_println!("Address: {address}")?; sh_println!("Salt: {salt} ({})", U256::from_be_bytes(salt.0))?; Ok(Create2Output { address, salt }) } } fn get_regex_hex_string(s: String) -> Result { let s = s.strip_prefix("0x").unwrap_or(&s); let pad_width = s.len() + s.len() % 2; hex::decode(format!("{s:0, /// Disassemble bytecodes into individual opcodes. #[arg(long)] disassemble: bool, /// Return creation bytecode without constructor arguments appended. #[arg(long, conflicts_with = "only_args")] without_args: bool, /// Return only constructor arguments. #[arg(long)] only_args: bool, #[command(flatten)] etherscan: EtherscanOpts, #[command(flatten)] rpc: RpcOpts, } impl CreationCodeArgs { pub async fn run(self) -> Result<()> { let mut config = self.load_config()?; let Self { contract, disassemble, without_args, only_args, abi_path, etherscan: _, rpc: _ } = self; let provider = utils::get_provider(&config)?; let chain = provider.get_chain_id().await?; config.chain = Some(chain.into()); let bytecode = fetch_creation_code_from_etherscan(contract, &config, provider).await?; let bytecode = parse_code_output( bytecode, contract, &config, abi_path.as_deref(), without_args, only_args, ) .await?; if disassemble { let _ = sh_println!("{}", SimpleCast::disassemble(&bytecode)?); } else { let _ = sh_println!("{bytecode}"); } Ok(()) } } /// Parses the creation bytecode and returns one of the following: /// - The complete bytecode /// - The bytecode without constructor arguments /// - Only the constructor arguments pub async fn parse_code_output( bytecode: Bytes, contract: Address, config: &Config, abi_path: Option<&str>, without_args: bool, only_args: bool, ) -> Result { if !without_args && !only_args { return Ok(bytecode); } let abi = if let Some(abi_path) = abi_path { load_abi_from_file(abi_path, None)? } else { fetch_abi_from_etherscan(contract, config).await? }; let abi = abi.into_iter().next().ok_or_eyre("No ABI found.")?; let (abi, _) = abi; if abi.constructor.is_none() { if only_args { return Err(eyre!("No constructor found.")); } return Ok(bytecode); } let constructor = abi.constructor.unwrap(); if constructor.inputs.is_empty() { if only_args { return Err(eyre!("No constructor arguments found.")); } return Ok(bytecode); } let args_size = constructor.inputs.len() * 32; let bytecode = if without_args { Bytes::from(bytecode[..bytecode.len() - args_size].to_vec()) } else if only_args { Bytes::from(bytecode[bytecode.len() - args_size..].to_vec()) } else { unreachable!(); }; Ok(bytecode) } /// Fetches the creation code of a contract from Etherscan and RPC. pub async fn fetch_creation_code_from_etherscan( contract: Address, config: &Config, provider: RootProvider, ) -> Result { let chain = config.chain.unwrap_or_default(); let api_key = config.get_etherscan_api_key(Some(chain)).unwrap_or_default(); let client = Client::new(chain, api_key)?; 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?; let tx_data = tx_data.ok_or_eyre("Could not find creation tx data.")?; let bytecode = if tx_data.to().is_none() { // Contract was created using a standard transaction tx_data.input().clone() } else { // Contract was created using a factory pattern or create2 // Extract creation code from tx traces let mut creation_bytecode = None; let traces = provider.trace_transaction(creation_tx_hash).await.map_err(|e| { eyre!("Could not fetch traces for transaction {}: {}", creation_tx_hash, e) })?; for trace in traces { if let Some(TraceOutput::Create(CreateOutput { address, .. })) = trace.trace.result && address == contract { creation_bytecode = match trace.trace.action { Action::Create(CreateAction { init, .. }) => Some(init), _ => None, }; } } creation_bytecode.ok_or_else(|| eyre!("Could not find contract creation trace."))? }; Ok(bytecode) } ================================================ FILE: crates/cast/src/cmd/da_estimate.rs ================================================ //! 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 foundry_primitives::FoundryTxEnvelope; /// 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() { // convert into FoundryTxEnvelope to support all foundry tx types let tx = FoundryTxEnvelope::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(()) } } ================================================ FILE: crates/cast/src/cmd/erc20.rs ================================================ use std::{str::FromStr, time::Duration}; use crate::{cmd::send::cast_send, format_uint_exp, tx::SendTxOpts}; use alloy_consensus::{SignableTransaction, Signed}; use alloy_eips::BlockId; use alloy_ens::NameOrAddress; use alloy_network::{AnyNetwork, EthereumWallet, Network, TransactionBuilder}; use alloy_primitives::{U64, U256}; use alloy_provider::{Provider, fillers::RecommendedFillers}; use alloy_signer::Signature; use alloy_sol_types::sol; use clap::{Args, Parser}; use foundry_cli::{ opts::{RpcOpts, TempoOpts}, utils::{LoadConfig, get_chain, get_provider}, }; use foundry_common::{ fmt::{UIfmt, UIfmtReceiptExt}, provider::{ProviderBuilder, RetryProviderWithSigner}, shell, }; #[doc(hidden)] pub use foundry_config::{Chain, utils::*}; use foundry_primitives::FoundryTransactionBuilder; use tempo_alloy::TempoNetwork; sol! { #[sol(rpc)] interface IERC20 { #[derive(Debug)] function name() external view returns (string); function symbol() external view returns (string); function decimals() external view returns (uint8); function totalSupply() external view returns (uint256); function balanceOf(address owner) external view returns (uint256); function transfer(address to, uint256 amount) external returns (bool); function approve(address spender, uint256 amount) external returns (bool); function allowance(address owner, address spender) external view returns (uint256); function mint(address to, uint256 amount) external; function burn(uint256 amount) external; } } /// Transaction options for ERC20 operations. /// /// This struct contains only the transaction options relevant to ERC20 token interactions #[derive(Debug, Clone, Args)] pub struct Erc20TxOpts { /// Gas limit for the transaction. #[arg(long, env = "ETH_GAS_LIMIT")] pub gas_limit: Option, /// Gas price for legacy transactions, or max fee per gas for EIP1559 transactions. #[arg(long, env = "ETH_GAS_PRICE")] pub gas_price: Option, /// Max priority fee per gas for EIP1559 transactions. #[arg(long, env = "ETH_PRIORITY_GAS_PRICE")] pub priority_gas_price: Option, /// Nonce for the transaction. #[arg(long)] pub nonce: Option, #[command(flatten)] pub tempo: TempoOpts, } /// Creates a provider with wallet for signing transactions locally. pub(crate) async fn get_provider_with_wallet( tx_opts: &SendTxOpts, ) -> eyre::Result> where N::TxEnvelope: From>, N::UnsignedTx: SignableTransaction, { let config = tx_opts.eth.load_config()?; let signer = tx_opts.eth.wallet.signer().await?; let wallet = EthereumWallet::from(signer); let provider = ProviderBuilder::::from_config(&config)?.build_with_wallet(wallet)?; if let Some(interval) = tx_opts.poll_interval { provider.client().set_poll_interval(Duration::from_secs(interval)) } Ok(provider) } impl Erc20TxOpts { /// Applies gas, fee, nonce, and Tempo options to a transaction request. fn apply(&self, tx: &mut N::TransactionRequest, legacy: bool) where N::TransactionRequest: FoundryTransactionBuilder, { if let Some(gas_limit) = self.gas_limit { tx.set_gas_limit(gas_limit.to()); } if let Some(gas_price) = self.gas_price { if legacy { tx.set_gas_price(gas_price.to()); } else { tx.set_max_fee_per_gas(gas_price.to()); } } if !legacy && let Some(priority_fee) = self.priority_gas_price { tx.set_max_priority_fee_per_gas(priority_fee.to()); } self.tempo.apply::(tx, self.nonce.map(|n| n.to())); } } /// Interact with ERC20 tokens. #[derive(Debug, Parser, Clone)] pub enum Erc20Subcommand { /// Query ERC20 token balance. #[command(visible_alias = "b")] Balance { /// The ERC20 token contract address. #[arg(value_parser = NameOrAddress::from_str)] token: NameOrAddress, /// The owner to query balance for. #[arg(value_parser = NameOrAddress::from_str)] owner: NameOrAddress, /// The block height to query at. #[arg(long, short = 'B')] block: Option, #[command(flatten)] rpc: RpcOpts, }, /// Transfer ERC20 tokens. #[command(visible_aliases = ["t", "send"])] Transfer { /// The ERC20 token contract address. #[arg(value_parser = NameOrAddress::from_str)] token: NameOrAddress, /// The recipient address. #[arg(value_parser = NameOrAddress::from_str)] to: NameOrAddress, /// The amount to transfer. amount: String, #[command(flatten)] send_tx: SendTxOpts, #[command(flatten)] tx: Erc20TxOpts, }, /// Approve ERC20 token spending. #[command(visible_alias = "a")] Approve { /// The ERC20 token contract address. #[arg(value_parser = NameOrAddress::from_str)] token: NameOrAddress, /// The spender address. #[arg(value_parser = NameOrAddress::from_str)] spender: NameOrAddress, /// The amount to approve. amount: String, #[command(flatten)] send_tx: SendTxOpts, #[command(flatten)] tx: Erc20TxOpts, }, /// Query ERC20 token allowance. #[command(visible_alias = "al")] Allowance { /// The ERC20 token contract address. #[arg(value_parser = NameOrAddress::from_str)] token: NameOrAddress, /// The owner address. #[arg(value_parser = NameOrAddress::from_str)] owner: NameOrAddress, /// The spender address. #[arg(value_parser = NameOrAddress::from_str)] spender: NameOrAddress, /// The block height to query at. #[arg(long, short = 'B')] block: Option, #[command(flatten)] rpc: RpcOpts, }, /// Query ERC20 token name. #[command(visible_alias = "n")] Name { /// The ERC20 token contract address. #[arg(value_parser = NameOrAddress::from_str)] token: NameOrAddress, /// The block height to query at. #[arg(long, short = 'B')] block: Option, #[command(flatten)] rpc: RpcOpts, }, /// Query ERC20 token symbol. #[command(visible_alias = "s")] Symbol { /// The ERC20 token contract address. #[arg(value_parser = NameOrAddress::from_str)] token: NameOrAddress, /// The block height to query at. #[arg(long, short = 'B')] block: Option, #[command(flatten)] rpc: RpcOpts, }, /// Query ERC20 token decimals. #[command(visible_alias = "d")] Decimals { /// The ERC20 token contract address. #[arg(value_parser = NameOrAddress::from_str)] token: NameOrAddress, /// The block height to query at. #[arg(long, short = 'B')] block: Option, #[command(flatten)] rpc: RpcOpts, }, /// Query ERC20 token total supply. #[command(visible_alias = "ts")] TotalSupply { /// The ERC20 token contract address. #[arg(value_parser = NameOrAddress::from_str)] token: NameOrAddress, /// The block height to query at. #[arg(long, short = 'B')] block: Option, #[command(flatten)] rpc: RpcOpts, }, /// Mint ERC20 tokens (if the token supports minting). #[command(visible_alias = "m")] Mint { /// The ERC20 token contract address. #[arg(value_parser = NameOrAddress::from_str)] token: NameOrAddress, /// The recipient address. #[arg(value_parser = NameOrAddress::from_str)] to: NameOrAddress, /// The amount to mint. amount: String, #[command(flatten)] send_tx: SendTxOpts, #[command(flatten)] tx: Erc20TxOpts, }, /// Burn ERC20 tokens. #[command(visible_alias = "bu")] Burn { /// The ERC20 token contract address. #[arg(value_parser = NameOrAddress::from_str)] token: NameOrAddress, /// The amount to burn. amount: String, #[command(flatten)] send_tx: SendTxOpts, #[command(flatten)] tx: Erc20TxOpts, }, } impl Erc20Subcommand { fn rpc_opts(&self) -> &RpcOpts { match self { Self::Allowance { rpc, .. } => rpc, Self::Approve { send_tx, .. } => &send_tx.eth.rpc, Self::Balance { rpc, .. } => rpc, Self::Transfer { send_tx, .. } => &send_tx.eth.rpc, Self::Name { rpc, .. } => rpc, Self::Symbol { rpc, .. } => rpc, Self::Decimals { rpc, .. } => rpc, Self::TotalSupply { rpc, .. } => rpc, Self::Mint { send_tx, .. } => &send_tx.eth.rpc, Self::Burn { send_tx, .. } => &send_tx.eth.rpc, } } fn erc20_opts(&self) -> Option<&Erc20TxOpts> { match self { Self::Approve { tx, .. } | Self::Transfer { tx, .. } | Self::Mint { tx, .. } | Self::Burn { tx, .. } => Some(tx), Self::Allowance { .. } | Self::Balance { .. } | Self::Name { .. } | Self::Symbol { .. } | Self::Decimals { .. } | Self::TotalSupply { .. } => None, } } pub async fn run(self) -> eyre::Result<()> { if let Some(erc20) = self.erc20_opts() && erc20.tempo.is_tempo() { self.run_generic::().await } else { self.run_generic::().await } } pub async fn run_generic(self) -> eyre::Result<()> where N::TxEnvelope: From>, N::UnsignedTx: SignableTransaction, N::TransactionRequest: FoundryTransactionBuilder, N::ReceiptResponse: UIfmt + UIfmtReceiptExt, { let config = self.rpc_opts().load_config()?; match self { // Read-only Self::Allowance { token, owner, spender, block, .. } => { let provider = get_provider(&config)?; let token = token.resolve(&provider).await?; let owner = owner.resolve(&provider).await?; let spender = spender.resolve(&provider).await?; let allowance = IERC20::new(token, &provider) .allowance(owner, spender) .block(block.unwrap_or_default()) .call() .await?; if shell::is_json() { sh_println!("{}", serde_json::to_string(&allowance.to_string())?)? } else { sh_println!("{}", format_uint_exp(allowance))? } } Self::Balance { token, owner, block, .. } => { let provider = get_provider(&config)?; let token = token.resolve(&provider).await?; let owner = owner.resolve(&provider).await?; let balance = IERC20::new(token, &provider) .balanceOf(owner) .block(block.unwrap_or_default()) .call() .await?; if shell::is_json() { sh_println!("{}", serde_json::to_string(&balance.to_string())?)? } else { sh_println!("{}", format_uint_exp(balance))? } } Self::Name { token, block, .. } => { let provider = get_provider(&config)?; let token = token.resolve(&provider).await?; let name = IERC20::new(token, &provider) .name() .block(block.unwrap_or_default()) .call() .await?; if shell::is_json() { sh_println!("{}", serde_json::to_string(&name)?)? } else { sh_println!("{}", name)? } } Self::Symbol { token, block, .. } => { let provider = get_provider(&config)?; let token = token.resolve(&provider).await?; let symbol = IERC20::new(token, &provider) .symbol() .block(block.unwrap_or_default()) .call() .await?; if shell::is_json() { sh_println!("{}", serde_json::to_string(&symbol)?)? } else { sh_println!("{}", symbol)? } } Self::Decimals { token, block, .. } => { let provider = get_provider(&config)?; let token = token.resolve(&provider).await?; let decimals = IERC20::new(token, &provider) .decimals() .block(block.unwrap_or_default()) .call() .await?; if shell::is_json() { sh_println!("{}", serde_json::to_string(&decimals)?)? } else { sh_println!("{}", decimals)? } } Self::TotalSupply { token, block, .. } => { let provider = get_provider(&config)?; let token = token.resolve(&provider).await?; let total_supply = IERC20::new(token, &provider) .totalSupply() .block(block.unwrap_or_default()) .call() .await?; if shell::is_json() { sh_println!("{}", serde_json::to_string(&total_supply.to_string())?)? } else { sh_println!("{}", format_uint_exp(total_supply))? } } // State-changing Self::Transfer { token, to, amount, send_tx, tx: tx_opts, .. } => { let provider = get_provider_with_wallet::(&send_tx).await?; let mut tx = IERC20::new(token.resolve(&provider).await?, &provider) .transfer(to.resolve(&provider).await?, U256::from_str(&amount)?) .into_transaction_request(); tx_opts.apply::(&mut tx, get_chain(config.chain, &provider).await?.is_legacy()); cast_send( provider, tx, send_tx.cast_async, send_tx.sync, send_tx.confirmations, send_tx.timeout.unwrap_or(config.transaction_timeout), ) .await? } Self::Approve { token, spender, amount, send_tx, tx: tx_opts, .. } => { let provider = get_provider_with_wallet::(&send_tx).await?; let mut tx = IERC20::new(token.resolve(&provider).await?, &provider) .approve(spender.resolve(&provider).await?, U256::from_str(&amount)?) .into_transaction_request(); tx_opts.apply::(&mut tx, get_chain(config.chain, &provider).await?.is_legacy()); cast_send( provider, tx, send_tx.cast_async, send_tx.sync, send_tx.confirmations, send_tx.timeout.unwrap_or(config.transaction_timeout), ) .await? } Self::Mint { token, to, amount, send_tx, tx: tx_opts, .. } => { let provider = get_provider_with_wallet::(&send_tx).await?; let mut tx = IERC20::new(token.resolve(&provider).await?, &provider) .mint(to.resolve(&provider).await?, U256::from_str(&amount)?) .into_transaction_request(); tx_opts.apply::(&mut tx, get_chain(config.chain, &provider).await?.is_legacy()); cast_send( provider, tx, send_tx.cast_async, send_tx.sync, send_tx.confirmations, send_tx.timeout.unwrap_or(config.transaction_timeout), ) .await? } Self::Burn { token, amount, send_tx, tx: tx_opts, .. } => { let provider = get_provider_with_wallet::(&send_tx).await?; let mut tx = IERC20::new(token.resolve(&provider).await?, &provider) .burn(U256::from_str(&amount)?) .into_transaction_request(); tx_opts.apply::(&mut tx, get_chain(config.chain, &provider).await?.is_legacy()); cast_send( provider, tx, send_tx.cast_async, send_tx.sync, send_tx.confirmations, send_tx.timeout.unwrap_or(config.transaction_timeout), ) .await? } }; Ok(()) } } ================================================ FILE: crates/cast/src/cmd/estimate.rs ================================================ use crate::tx::{CastTxBuilder, SenderKind}; use alloy_ens::NameOrAddress; use alloy_network::{AnyNetwork, Network}; use alloy_primitives::U256; use alloy_provider::Provider; use alloy_rpc_types::BlockId; use clap::Parser; use eyre::Result; use foundry_cli::{ opts::{RpcOpts, TransactionOpts}, utils::{LoadConfig, parse_ether_value}, }; use foundry_common::provider::ProviderBuilder; use foundry_primitives::FoundryTransactionBuilder; use foundry_wallets::WalletOpts; use std::str::FromStr; use tempo_alloy::TempoNetwork; /// CLI arguments for `cast estimate`. #[derive(Debug, Parser)] pub struct EstimateArgs { /// The destination of the transaction. #[arg(value_parser = NameOrAddress::from_str)] to: Option, /// The signature of the function to call. sig: Option, /// The arguments of the function to call. #[arg(allow_negative_numbers = true)] args: Vec, /// The block height to query at. /// /// Can also be the tags earliest, finalized, safe, latest, or pending. #[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(flatten)] wallet: WalletOpts, #[command(subcommand)] command: Option, #[command(flatten)] tx: TransactionOpts, #[command(flatten)] rpc: RpcOpts, } #[derive(Debug, Parser)] pub enum EstimateSubcommands { /// Estimate gas cost to deploy a smart contract #[command(name = "--create")] Create { /// The bytecode of contract code: String, /// The signature of the constructor sig: Option, /// Constructor arguments #[arg(allow_negative_numbers = true)] args: Vec, /// Ether to send in the transaction /// /// Either specified in wei, or as a string with a unit type: /// /// Examples: 1ether, 10gwei, 0.01ether #[arg(long, value_parser = parse_ether_value)] value: Option, }, } impl EstimateArgs { pub async fn run(self) -> Result<()> { if self.tx.tempo.is_tempo() { self.run_with_network::().await } else { self.run_with_network::().await } } pub async fn run_with_network(self) -> Result<()> where N::TransactionRequest: FoundryTransactionBuilder, { let Self { to, mut sig, mut args, mut tx, block, cost, wallet, rpc, command } = self; let config = rpc.load_config()?; let provider = ProviderBuilder::::from_config(&config)?.build()?; let sender = SenderKind::from_wallet_opts(wallet).await?; let code = if let Some(EstimateSubcommands::Create { code, sig: create_sig, args: create_args, value, }) = command { sig = create_sig; args = create_args; if let Some(value) = value { tx.value = Some(value); } Some(code) } else { None }; let (tx, _) = CastTxBuilder::new(&provider, tx, &config) .await? .with_to(to) .await? .with_code_sig_and_args(code, sig, args) .await? .raw() .build(sender) .await?; let gas = provider.estimate_gas(tx).block(block.unwrap_or_default()).await?; 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(()) } } #[cfg(test)] mod tests { use super::*; #[test] fn parse_estimate_value() { let args: EstimateArgs = EstimateArgs::parse_from(["foundry-cli", "--value", "100"]); assert!(args.tx.value.is_some()); } } ================================================ FILE: crates/cast/src/cmd/find_block.rs ================================================ use crate::Cast; use alloy_provider::Provider; use clap::Parser; use eyre::Result; use foundry_cli::{ opts::RpcOpts, utils::{self, LoadConfig}, }; use futures::join; /// CLI arguments for `cast find-block`. #[derive(Clone, Debug, Parser)] pub struct FindBlockArgs { /// The UNIX timestamp to search for, in seconds. timestamp: u64, #[command(flatten)] rpc: RpcOpts, } impl FindBlockArgs { pub async fn run(self) -> Result<()> { let Self { timestamp, rpc } = self; let ts_target = timestamp; let config = rpc.load_config()?; let provider = utils::get_provider(&config)?; let last_block_num = provider.get_block_number().await?; let cast_provider = Cast::new(provider); let res = join!(cast_provider.timestamp(last_block_num), cast_provider.timestamp(1)); let ts_block_latest: u64 = res.0?.to(); let ts_block_1: u64 = res.1?.to(); let block_num = if ts_block_latest < ts_target { // If the most recent block's timestamp is below the target, return it last_block_num } else if ts_block_1 > ts_target { // If the target timestamp is below block 1's timestamp, return that 1 } else { // Otherwise, find the block that is closest to the timestamp let mut low_block = 1_u64; // block 0 has a timestamp of 0: https://github.com/ethereum/go-ethereum/issues/17042#issuecomment-559414137 let mut high_block = last_block_num; let mut matching_block = None; while high_block > low_block && matching_block.is_none() { // Get timestamp of middle block (this approach to avoids overflow) let high_minus_low_over_2 = high_block .checked_sub(low_block) .ok_or_else(|| eyre::eyre!("unexpected underflow")) .unwrap() .checked_div(2_u64) .unwrap(); let mid_block = high_block.checked_sub(high_minus_low_over_2).unwrap(); let ts_mid_block = cast_provider.timestamp(mid_block).await?.to::(); // Check if we've found a match or should keep searching if ts_mid_block == ts_target { matching_block = Some(mid_block) } else if high_block.checked_sub(low_block).unwrap() == 1_u64 { // The target timestamp is in between these blocks. This rounds to the // highest block if timestamp is equidistant between blocks let res = join!( cast_provider.timestamp(high_block), cast_provider.timestamp(low_block) ); let ts_high: u64 = res.0.unwrap().to(); let ts_low: u64 = res.1.unwrap().to(); let high_diff = ts_high.checked_sub(ts_target).unwrap(); let low_diff = ts_target.checked_sub(ts_low).unwrap(); let is_low = low_diff < high_diff; matching_block = if is_low { Some(low_block) } else { Some(high_block) } } else if ts_mid_block < ts_target { low_block = mid_block; } else { high_block = mid_block; } } matching_block.unwrap_or(low_block) }; sh_println!("{block_num}")?; Ok(()) } } ================================================ FILE: crates/cast/src/cmd/interface.rs ================================================ use alloy_json_abi::{ContractObject, JsonAbi, ToSolConfig}; use alloy_primitives::Address; use clap::Parser; use eyre::{Context, Result}; use forge_fmt::FormatterConfig; use foundry_cli::{ opts::EtherscanOpts, utils::{LoadConfig, fetch_abi_from_etherscan}, }; use foundry_common::{ ContractsByArtifact, compile::{PathOrContractInfo, ProjectCompiler}, find_target_path, fs, shell, }; use foundry_config::load_config; use itertools::Itertools; use serde_json::Value; use std::{ path::{Path, PathBuf}, str::FromStr, }; /// CLI arguments for `cast interface`. #[derive(Clone, Debug, Parser)] pub struct InterfaceArgs { /// The target contract, which can be one of: /// - A file path to an ABI JSON file. /// - A contract identifier in the form `:` or just ``. /// - An Ethereum address, for which the ABI will be fetched from Etherscan. contract: String, /// The name to use for the generated interface. /// /// Only relevant when retrieving the ABI from a file. #[arg(long, short)] name: Option, /// Solidity pragma version. #[arg(long, short, default_value = "^0.8.4", value_name = "VERSION")] pragma: String, /// The path to the output file. /// /// If not specified, the interface will be output to stdout. #[arg( short, long, value_hint = clap::ValueHint::FilePath, value_name = "PATH", )] output: Option, /// If set, generate all types in a single interface, inlining any inherited or library types. /// /// This can fail if there are structs with the same name in different interfaces. #[arg(long)] flatten: bool, #[command(flatten)] etherscan: EtherscanOpts, } impl InterfaceArgs { pub async fn run(self) -> Result<()> { let Self { contract, name, pragma, output: output_location, flatten, etherscan } = self; // Determine if the target contract is an ABI file, a local contract or an Ethereum address. let abis = if Path::new(&contract).is_file() && fs::read_to_string(&contract) .ok() .and_then(|content| serde_json::from_str::(&content).ok()) .is_some() { load_abi_from_file(&contract, name)? } else { match Address::from_str(&contract) { Ok(address) => fetch_abi_from_etherscan(address, ðerscan.load_config()?).await?, Err(_) => load_abi_from_artifact(&contract)?, } }; // Build config for to_sol conversion. let config = if flatten { Some(ToSolConfig::new().one_contract(true)) } else { None }; // Retrieve interfaces from the array of ABIs. let interfaces = get_interfaces(abis, config)?; // Print result or write to file. let res = if shell::is_json() { // Format as JSON. interfaces.iter().map(|iface| &iface.json_abi).format("\n").to_string() } else { // Format as Solidity. format!( "// SPDX-License-Identifier: UNLICENSED\n\ pragma solidity {pragma};\n\n\ {}", interfaces.iter().map(|iface| &iface.source).format("\n") ) }; if let Some(loc) = output_location { if let Some(parent) = loc.parent() { fs::create_dir_all(parent)?; } fs::write(&loc, res)?; sh_println!("Saved interface at {}", loc.display())?; } else { sh_print!("{res}")?; } Ok(()) } } struct InterfaceSource { json_abi: String, source: String, } /// Load the ABI from a file. pub fn load_abi_from_file(path: &str, name: Option) -> Result> { let file = std::fs::read_to_string(path).wrap_err("unable to read abi file")?; let obj: ContractObject = serde_json::from_str(&file)?; let abi = obj.abi.ok_or_else(|| eyre::eyre!("could not find ABI in file {path}"))?; let name = name.unwrap_or_else(|| "Interface".to_owned()); Ok(vec![(abi, name)]) } /// Load the ABI from the artifact of a locally compiled contract. fn load_abi_from_artifact(path_or_contract: &str) -> Result> { let config = load_config()?; let project = config.project()?; let compiler = ProjectCompiler::new().quiet(true); let contract = PathOrContractInfo::from_str(path_or_contract)?; let target_path = find_target_path(&project, &contract)?; let output = compiler.files([target_path.clone()]).compile(&project)?; let contracts_by_artifact = ContractsByArtifact::from(output); let maybe_abi = contracts_by_artifact .find_abi_by_name_or_src_path(contract.name().unwrap_or(&target_path.to_string_lossy())); let (abi, name) = maybe_abi.as_ref().ok_or_else(|| eyre::eyre!("Failed to fetch lossless ABI"))?; Ok(vec![(abi.clone(), contract.name().unwrap_or(name).to_string())]) } /// Converts a vector of tuples containing the ABI and contract name into a vector of /// `InterfaceSource` objects. fn get_interfaces( abis: Vec<(JsonAbi, String)>, config: Option, ) -> Result> { abis.into_iter() .map(|(contract_abi, name)| { let source = match forge_fmt::format( &contract_abi.to_sol(&name, config.clone()), FormatterConfig::default(), ) .into_result() { Ok(generated_source) => generated_source, Err(e) => { sh_warn!("Failed to format interface for {name}: {e}")?; contract_abi.to_sol(&name, config.clone()) } }; Ok(InterfaceSource { json_abi: serde_json::to_string_pretty(&contract_abi)?, source }) }) .collect() } ================================================ FILE: crates/cast/src/cmd/logs.rs ================================================ 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::{Address, B256, hex::FromHex}; use alloy_rpc_types::{BlockId, BlockNumberOrTag, Filter, FilterBlockOption, FilterSet, Topic}; use clap::Parser; use eyre::Result; use foundry_cli::{ opts::RpcOpts, utils::{self, LoadConfig}, }; use itertools::Itertools; use std::{io, str::FromStr}; /// CLI arguments for `cast logs`. #[derive(Debug, Parser)] pub struct LogsArgs { /// The block height to start query at. /// /// Can also be the tags earliest, finalized, safe, latest, or pending. #[arg(long)] from_block: Option, /// The block height to stop query at. /// /// Can also be the tags earliest, finalized, safe, latest, or pending. #[arg(long)] to_block: Option, /// The contract address to filter on. #[arg(long, value_parser = NameOrAddress::from_str)] address: Option>, /// The signature of the event to filter logs by which will be converted to the first topic or /// a topic to filter on. #[arg(value_name = "SIG_OR_TOPIC")] sig_or_topic: Option, /// If used with a signature, the indexed fields of the event to filter by. Otherwise, the /// remaining topics of the filter. #[arg(value_name = "TOPICS_OR_ARGS")] topics_or_args: Vec, /// If the RPC type and endpoints supports `eth_subscribe` stream logs instead of printing and /// exiting. Will continue until interrupted or TO_BLOCK is reached. #[arg(long)] subscribe: bool, /// Number of blocks to query in each chunk when the provider has range limits. /// Defaults to 10000 blocks per chunk. #[arg(long, default_value_t = 10000)] query_size: u64, #[command(flatten)] rpc: RpcOpts, } impl LogsArgs { pub async fn run(self) -> Result<()> { let Self { from_block, to_block, address, sig_or_topic, topics_or_args, subscribe, query_size, rpc, } = self; let config = rpc.load_config()?; let provider = utils::get_provider(&config)?; let cast = Cast::new(&provider); let addresses = match address { Some(addresses) => Some( futures::future::try_join_all(addresses.into_iter().map(|address| { let provider = provider.clone(); async move { address.resolve(&provider).await } })) .await?, ), None => None, }; let from_block = cast.convert_block_number(Some(from_block.unwrap_or_else(BlockId::earliest))).await?; let to_block = cast.convert_block_number(Some(to_block.unwrap_or_else(BlockId::latest))).await?; let filter = build_filter(from_block, to_block, addresses, sig_or_topic, topics_or_args)?; if !subscribe { let logs = cast.filter_logs_chunked(filter, query_size).await?; sh_println!("{logs}")?; return Ok(()); } // FIXME: this is a hotfix for // currently the alloy `eth_subscribe` impl does not work with all transports, so we use // the builtin transport here for now let url = config.get_rpc_url_or_localhost_http()?; let provider = alloy_provider::ProviderBuilder::<_, _, AnyNetwork>::default() .connect(url.as_ref()) .await?; let cast = Cast::new(&provider); let mut stdout = io::stdout(); cast.subscribe(filter, &mut stdout).await?; Ok(()) } } /// Builds a Filter by first trying to parse the `sig_or_topic` as an event signature. If /// successful, `topics_or_args` is parsed as indexed inputs and converted to topics. Otherwise, /// `sig_or_topic` is prepended to `topics_or_args` and used as raw topics. fn build_filter( from_block: Option, to_block: Option, address: Option>, sig_or_topic: Option, topics_or_args: Vec, ) -> Result { let block_option = FilterBlockOption::Range { from_block, to_block }; let filter = match sig_or_topic { // Try and parse the signature as an event signature Some(sig_or_topic) => match foundry_common::abi::get_event(sig_or_topic.as_str()) { Ok(event) => build_filter_event_sig(event, topics_or_args)?, Err(_) => { let topics = [vec![sig_or_topic], topics_or_args].concat(); build_filter_topics(topics)? } }, None => Filter::default(), }; let mut filter = filter.select(block_option); if let Some(address) = address { filter = filter.address(address) } Ok(filter) } /// Creates a [Filter] from the given event signature and arguments. fn build_filter_event_sig(event: Event, args: Vec) -> Result { let args = args.iter().map(|arg| arg.as_str()).collect::>(); // Match the args to indexed inputs. Enumerate so that the ordering can be restored // when merging the inputs with arguments and without arguments let (with_args, without_args): (Vec<_>, Vec<_>) = event .inputs .iter() .zip(args) .filter(|(input, _)| input.indexed) .map(|(input, arg)| { let kind = input.resolve()?; Ok((kind, arg)) }) .collect::>>()? .into_iter() .enumerate() .partition(|(_, (_, arg))| !arg.is_empty()); // Only parse the inputs with arguments let indexed_tokens = with_args .iter() .map(|(_, (kind, arg))| kind.coerce_str(arg)) .collect::, _>>()?; // Merge the inputs restoring the original ordering let mut topics = with_args .into_iter() .zip(indexed_tokens) .map(|((i, _), t)| (i, Some(t))) .chain(without_args.into_iter().map(|(i, _)| (i, None))) .sorted_by(|(i1, _), (i2, _)| i1.cmp(i2)) .map(|(_, token)| { token .map(|token| Topic::from(B256::from_slice(token.abi_encode().as_slice()))) .unwrap_or(Topic::default()) }) .collect::>(); topics.resize(3, Topic::default()); let filter = Filter::new() .event_signature(event.selector()) .topic1(topics[0].clone()) .topic2(topics[1].clone()) .topic3(topics[2].clone()); Ok(filter) } /// Creates a [Filter] from raw topic hashes. fn build_filter_topics(topics: Vec) -> Result { let mut topics = topics .into_iter() .map(|topic| { if topic.is_empty() { Ok(Topic::default()) } else { Ok(Topic::from(B256::from_hex(topic.as_str())?)) } }) .collect::>>>()?; topics.resize(4, Topic::default()); let filter = Filter::new() .event_signature(topics[0].clone()) .topic1(topics[1].clone()) .topic2(topics[2].clone()) .topic3(topics[3].clone()); Ok(filter) } #[cfg(test)] mod tests { use super::*; use alloy_primitives::{U160, U256}; use alloy_rpc_types::ValueOrArray; const ADDRESS: &str = "0x4D1A2e2bB4F88F0250f26Ffff098B0b30B26BF38"; const TRANSFER_SIG: &str = "Transfer(address indexed,address indexed,uint256)"; const TRANSFER_TOPIC: &str = "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"; #[test] fn test_build_filter_basic() { let from_block = Some(BlockNumberOrTag::from(1337)); let to_block = Some(BlockNumberOrTag::Latest); let address = Address::from_str(ADDRESS).ok(); let expected = Filter { block_option: FilterBlockOption::Range { from_block, to_block }, address: ValueOrArray::Value(address.unwrap()).into(), topics: [vec![].into(), vec![].into(), vec![].into(), vec![].into()], }; let filter = build_filter(from_block, to_block, address.map(|addr| vec![addr]), None, vec![]) .unwrap(); assert_eq!(filter, expected) } #[test] fn test_build_filter_sig() { let expected = Filter { block_option: FilterBlockOption::Range { from_block: None, to_block: None }, address: vec![].into(), topics: [ B256::from_str(TRANSFER_TOPIC).unwrap().into(), vec![].into(), vec![].into(), vec![].into(), ], }; let filter = build_filter(None, None, None, Some(TRANSFER_SIG.to_string()), vec![]).unwrap(); assert_eq!(filter, expected) } #[test] fn test_build_filter_mismatch() { let expected = Filter { block_option: FilterBlockOption::Range { from_block: None, to_block: None }, address: vec![].into(), topics: [ B256::from_str(TRANSFER_TOPIC).unwrap().into(), vec![].into(), vec![].into(), vec![].into(), ], }; let filter = build_filter( None, None, None, Some("Swap(address indexed from, address indexed to, uint256 value)".to_string()), // Change signature, should result in error vec![], ) .unwrap(); assert_ne!(filter, expected) } #[test] fn test_build_filter_sig_with_arguments() { let addr = Address::from_str(ADDRESS).unwrap(); let addr = U256::from(U160::from_be_bytes(addr.0.0)); let expected = Filter { block_option: FilterBlockOption::Range { from_block: None, to_block: None }, address: vec![].into(), topics: [ B256::from_str(TRANSFER_TOPIC).unwrap().into(), addr.into(), vec![].into(), vec![].into(), ], }; let filter = build_filter( None, None, None, Some(TRANSFER_SIG.to_string()), vec![ADDRESS.to_string()], ) .unwrap(); assert_eq!(filter, expected) } #[test] fn test_build_filter_sig_with_skipped_arguments() { let addr = Address::from_str(ADDRESS).unwrap(); let addr = U256::from(U160::from_be_bytes(addr.0.0)); let expected = Filter { block_option: FilterBlockOption::Range { from_block: None, to_block: None }, address: vec![].into(), topics: [ vec![B256::from_str(TRANSFER_TOPIC).unwrap()].into(), vec![].into(), addr.into(), vec![].into(), ], }; let filter = build_filter( None, None, None, Some(TRANSFER_SIG.to_string()), vec![String::new(), ADDRESS.to_string()], ) .unwrap(); assert_eq!(filter, expected) } #[test] fn test_build_filter_with_topics() { let expected = Filter { block_option: FilterBlockOption::Range { from_block: None, to_block: None }, address: vec![].into(), topics: [ vec![B256::from_str(TRANSFER_TOPIC).unwrap()].into(), vec![B256::from_str(TRANSFER_TOPIC).unwrap()].into(), vec![].into(), vec![].into(), ], }; let filter = build_filter( None, None, None, Some(TRANSFER_TOPIC.to_string()), vec![TRANSFER_TOPIC.to_string()], ) .unwrap(); assert_eq!(filter, expected) } #[test] fn test_build_filter_with_skipped_topic() { let expected = Filter { block_option: FilterBlockOption::Range { from_block: None, to_block: None }, address: vec![].into(), topics: [ vec![B256::from_str(TRANSFER_TOPIC).unwrap()].into(), vec![].into(), vec![B256::from_str(TRANSFER_TOPIC).unwrap()].into(), vec![].into(), ], }; let filter = build_filter( None, None, None, Some(TRANSFER_TOPIC.to_string()), vec![String::new(), TRANSFER_TOPIC.to_string()], ) .unwrap(); assert_eq!(filter, expected) } #[test] fn test_build_filter_with_multiple_addresses() { let expected = Filter { block_option: FilterBlockOption::Range { from_block: None, to_block: None }, address: vec![Address::ZERO, ADDRESS.parse().unwrap()].into(), topics: [ vec![TRANSFER_TOPIC.parse().unwrap()].into(), vec![].into(), vec![].into(), vec![].into(), ], }; let filter = build_filter( None, None, Some(vec![Address::ZERO, ADDRESS.parse().unwrap()]), Some(TRANSFER_TOPIC.to_string()), vec![], ) .unwrap(); assert_eq!(filter, expected) } #[test] fn test_build_filter_sig_with_mismatched_argument() { let err = build_filter( None, None, None, Some(TRANSFER_SIG.to_string()), vec!["1234".to_string()], ) .err() .unwrap() .to_string() .to_lowercase(); assert_eq!(err, "parser error:\n1234\n^\ninvalid string length"); } #[test] fn test_build_filter_with_invalid_sig_or_topic() { let err = build_filter(None, None, None, Some("asdasdasd".to_string()), vec![]) .err() .unwrap() .to_string() .to_lowercase(); assert_eq!(err, "odd number of digits"); } #[test] fn test_build_filter_with_invalid_sig_or_topic_hex() { let err = build_filter(None, None, None, Some(ADDRESS.to_string()), vec![]) .err() .unwrap() .to_string() .to_lowercase(); assert_eq!(err, "invalid string length"); } #[test] fn test_build_filter_with_invalid_topic() { let err = build_filter( None, None, None, Some(TRANSFER_TOPIC.to_string()), vec!["1234".to_string()], ) .err() .unwrap() .to_string() .to_lowercase(); assert_eq!(err, "invalid string length"); } } ================================================ FILE: crates/cast/src/cmd/mktx.rs ================================================ use crate::tx::{self, CastTxBuilder}; use alloy_consensus::{SignableTransaction, Signed}; use alloy_eips::Encodable2718; use alloy_ens::NameOrAddress; use alloy_network::{AnyNetwork, EthereumWallet, Network, TransactionBuilder}; use alloy_primitives::{Address, hex}; use alloy_provider::Provider; use alloy_signer::{Signature, Signer}; use clap::Parser; use eyre::Result; use foundry_cli::{ opts::{EthereumOpts, TransactionOpts}, utils::LoadConfig, }; use foundry_common::provider::ProviderBuilder; use foundry_primitives::FoundryTransactionBuilder; use std::{path::PathBuf, str::FromStr}; use tempo_alloy::TempoNetwork; /// CLI arguments for `cast mktx`. #[derive(Debug, Parser)] pub struct MakeTxArgs { /// The destination of the transaction. /// /// If not provided, you must use `cast mktx --create`. #[arg(value_parser = NameOrAddress::from_str)] to: Option, /// The signature of the function to call. sig: Option, /// The arguments of the function to call. #[arg(allow_negative_numbers = true)] args: Vec, #[command(subcommand)] command: Option, #[command(flatten)] tx: TransactionOpts, /// The path of blob data to be sent. #[arg( long, value_name = "BLOB_DATA_PATH", conflicts_with = "legacy", requires = "blob", help_heading = "Transaction options" )] path: Option, #[command(flatten)] eth: EthereumOpts, /// Generate a raw RLP-encoded unsigned transaction. /// /// Relaxes the wallet requirement. #[arg(long)] 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)] pub enum MakeTxSubcommands { /// Use to deploy raw contract bytecode. #[command(name = "--create")] Create { /// The initialization bytecode of the contract to deploy. code: String, /// The signature of the constructor. sig: Option, /// The constructor arguments. #[arg(allow_negative_numbers = true)] args: Vec, }, } impl MakeTxArgs { pub async fn run(self) -> Result<()> { if self.tx.tempo.is_tempo() { self.run_generic::().await } else { self.run_generic::().await } } pub async fn run_generic(self) -> Result<()> where N::TxEnvelope: From>, N::UnsignedTx: SignableTransaction, N::TransactionRequest: FoundryTransactionBuilder, { let Self { to, mut sig, mut args, command, tx, path, eth, raw_unsigned, ethsign } = self; let print_sponsor_hash = tx.tempo.print_sponsor_hash; let blob_data = if let Some(path) = path { Some(std::fs::read(path)?) } else { None }; let code = if let Some(MakeTxSubcommands::Create { code, sig: constructor_sig, args: constructor_args, }) = command { sig = constructor_sig; args = constructor_args; Some(code) } else { None }; let config = eth.load_config()?; let provider = ProviderBuilder::::from_config(&config)?.build()?; let tx_builder = CastTxBuilder::new(&provider, tx.clone(), &config) .await? .with_to(to) .await? .with_code_sig_and_args(code, sig, args) .await? .with_blob_data(blob_data)?; // If --tempo.print-sponsor-hash was passed, build the tx, print the hash, and exit. if print_sponsor_hash { let from = eth.wallet.from.unwrap_or(Address::ZERO); let (tx, _) = tx_builder.build(from).await?; let hash = tx.compute_sponsor_hash(from).ok_or_else(|| { eyre::eyre!("This network does not support sponsored transactions") })?; sh_println!("{hash:?}")?; return Ok(()); } if raw_unsigned { // Build unsigned raw tx // Check if nonce is provided when --from is not specified // See: if eth.wallet.from.is_none() && tx.nonce.is_none() { eyre::bail!( "Missing required parameters for raw unsigned transaction. When --from is not provided, you must specify: --nonce" ); } // Use zero address as placeholder for unsigned transactions let from = eth.wallet.from.unwrap_or(Address::ZERO); let (tx, _) = tx_builder.build(from).await?; let raw_tx = hex::encode_prefixed(tx.build_unsigned()?.encoded_for_signing()); sh_println!("{raw_tx}")?; return Ok(()); } 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(); tx::validate_from_address(eth.wallet.from, from)?; let (tx, _) = tx_builder.build(&signer).await?; let tx = tx.build(&EthereumWallet::new(signer)).await?; let signed_tx = hex::encode(tx.encoded_2718()); sh_println!("0x{signed_tx}")?; Ok(()) } } ================================================ FILE: crates/cast/src/cmd/mod.rs ================================================ //! `cast` subcommands. //! //! All subcommands should respect the `foundry_config::Config`. //! If a subcommand accepts values that are supported by the `Config`, then the subcommand should //! implement `figment::Provider` which allows the subcommand to override the config's defaults, see //! [`foundry_config::Config`]. pub mod access_list; pub mod artifact; pub mod b2e_payload; pub mod bind; pub mod call; pub mod constructor_args; pub mod create2; pub mod creation_code; pub mod da_estimate; pub mod erc20; pub mod estimate; pub mod find_block; pub mod interface; pub mod logs; pub mod mktx; pub mod rpc; pub mod run; pub mod send; pub mod storage; pub mod trace; pub mod txpool; pub mod wallet; ================================================ FILE: crates/cast/src/cmd/rpc.rs ================================================ use crate::Cast; use clap::Parser; use eyre::Result; use foundry_cli::{opts::RpcOpts, utils, utils::LoadConfig}; use foundry_common::shell; use itertools::Itertools; /// CLI arguments for `cast rpc`. #[derive(Clone, Debug, Parser)] pub struct RpcArgs { /// RPC method name method: String, /// RPC parameters /// /// Interpreted as JSON: /// /// cast rpc eth_getBlockByNumber 0x123 false /// => {"method": "eth_getBlockByNumber", "params": ["0x123", false] ... } params: Vec, /// Send raw JSON parameters /// /// The first param will be interpreted as a raw JSON array of params. /// If no params are given, stdin will be used. For example: /// /// cast rpc eth_getBlockByNumber '["0x123", false]' --raw /// => {"method": "eth_getBlockByNumber", "params": ["0x123", false] ... } #[arg(long, short = 'w')] raw: bool, #[command(flatten)] rpc: RpcOpts, } impl RpcArgs { pub async fn run(self) -> Result<()> { let Self { raw, method, params, rpc } = self; let config = rpc.load_config()?; let params = if raw { if params.is_empty() { serde_json::Deserializer::from_reader(std::io::stdin()) .into_iter() .next() .transpose()? .ok_or_else(|| eyre::format_err!("Empty JSON parameters"))? } else { value_or_string(params.into_iter().join(" ")) } } else { serde_json::Value::Array(params.into_iter().map(value_or_string).collect()) }; let provider = utils::get_provider(&config)?; let result = Cast::new(provider).rpc(&method, params).await?; if shell::is_json() { let result: serde_json::Value = serde_json::from_str(&result)?; sh_println!("{}", serde_json::to_string_pretty(&result)?)?; } else { sh_println!("{}", result)?; } Ok(()) } } fn value_or_string(value: String) -> serde_json::Value { serde_json::from_str(&value).unwrap_or(serde_json::Value::String(value)) } ================================================ FILE: crates/cast/src/cmd/run.rs ================================================ use crate::{ debug::handle_traces, utils::{apply_chain_and_block_specific_env_changes, block_env_from_header}, }; use alloy_consensus::{BlockHeader, Transaction}; use alloy_evm::FromRecoveredTx; use alloy_network::{AnyNetwork, TransactionResponse}; use alloy_primitives::{ Address, Bytes, U256, map::{AddressSet, HashMap}, }; use alloy_provider::Provider; use alloy_rpc_types::BlockTransactions; use clap::Parser; use eyre::{Result, WrapErr}; use foundry_cli::{ opts::{EtherscanOpts, RpcOpts}, utils::{TraceResult, init_progress}, }; use foundry_common::{SYSTEM_TRANSACTION_TYPE, is_impersonated_tx, is_known_system_sender, shell}; use foundry_compilers::artifacts::EvmVersion; use foundry_config::{ Config, figment::{ self, Metadata, Profile, value::{Dict, Map}, }, }; use foundry_evm::{ executors::{EvmError, Executor, TracingExecutor}, opts::EvmOpts, traces::{InternalTraceMode, TraceMode, Traces}, }; use futures::TryFutureExt; use revm::{DatabaseRef, context::TxEnv}; /// CLI arguments for `cast run`. #[derive(Clone, Debug, Parser)] pub struct RunArgs { /// The transaction hash. tx_hash: String, /// Opens the transaction in the debugger. #[arg(long, short)] debug: bool, /// Whether to identify internal functions in traces. #[arg(long)] decode_internal: bool, /// Defines the depth of a trace #[arg(long)] trace_depth: Option, /// Print out opcode traces. #[arg(long, short)] trace_printer: bool, /// Executes the transaction only with the state from the previous block. /// /// May result in different results than the live execution! #[arg(long)] quick: bool, /// Whether to replay system transactions. #[arg(long, alias = "sys")] replay_system_txes: bool, /// Disables the labels in the traces. #[arg(long, default_value_t = false)] disable_labels: bool, /// Label addresses in the trace. /// /// Example: 0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045:vitalik.eth #[arg(long, short)] label: Vec, #[command(flatten)] etherscan: EtherscanOpts, #[command(flatten)] rpc: RpcOpts, /// The EVM version to use. /// /// Overrides the version specified in the config. #[arg(long)] evm_version: Option, /// Sets the number of assumed available compute units per second for this provider /// /// default value: 330 /// /// See also, #[arg(long, alias = "cups", value_name = "CUPS")] pub compute_units_per_second: Option, /// Disables rate limiting for this node's provider. /// /// default value: false /// /// See also, #[arg(long, value_name = "NO_RATE_LIMITS", visible_alias = "no-rpc-rate-limit")] pub no_rate_limit: bool, /// Use current project artifacts for trace decoding. #[arg(long, visible_alias = "la")] pub with_local_artifacts: bool, /// Disable block gas limit check. #[arg(long)] pub disable_block_gas_limit: bool, /// Enable the tx gas limit checks as imposed by Osaka (EIP-7825). #[arg(long)] pub enable_tx_gas_limit: bool, } impl RunArgs { /// Executes the transaction by replaying it /// /// This replays the entire block the transaction was mined in unless `quick` is set to true /// /// Note: This executes the transaction(s) as is: Cheatcodes are disabled pub async fn run(self) -> Result<()> { let figment = self.rpc.clone().into_figment(self.with_local_artifacts).merge(&self); let evm_opts = figment.extract::()?; let mut config = Config::from_provider(figment)?.sanitized(); let label = self.label; let with_local_artifacts = self.with_local_artifacts; let debug = self.debug; let decode_internal = self.decode_internal; let disable_labels = self.disable_labels; let compute_units_per_second = if self.no_rate_limit { Some(u64::MAX) } else { self.compute_units_per_second }; let provider = foundry_cli::utils::get_provider_builder(&config)? .compute_units_per_second_opt(compute_units_per_second) .build()?; let tx_hash = self.tx_hash.parse().wrap_err("invalid tx hash")?; let tx = provider .get_transaction_by_hash(tx_hash) .await .wrap_err_with(|| format!("tx not found: {tx_hash:?}"))? .ok_or_else(|| eyre::eyre!("tx not found: {:?}", tx_hash))?; // check if the tx is a system transaction if !self.replay_system_txes && (is_known_system_sender(tx.from()) || tx.transaction_type() == Some(SYSTEM_TRANSACTION_TYPE)) { return Err(eyre::eyre!( "{:?} is a system transaction.\nReplaying system transactions is currently not supported.", tx.tx_hash() )); } let tx_block_number = tx.block_number.ok_or_else(|| eyre::eyre!("tx may still be pending: {:?}", tx_hash))?; // we need to fork off the parent block config.fork_block_number = Some(tx_block_number - 1); let create2_deployer = evm_opts.create2_deployer; let (block, (mut evm_env, tx_env, fork, chain, networks)) = tokio::try_join!( // fetch the block the transaction was mined in provider.get_block(tx_block_number.into()).full().into_future().map_err(Into::into), TracingExecutor::get_fork_material(&mut config, evm_opts) )?; let mut evm_version = self.evm_version; evm_env.cfg_env.disable_block_gas_limit = self.disable_block_gas_limit; // By default do not enforce transaction gas limits imposed by Osaka (EIP-7825). // Users can opt-in to enable these limits by setting `enable_tx_gas_limit` to true. if !self.enable_tx_gas_limit { evm_env.cfg_env.tx_gas_limit_cap = Some(u64::MAX); } evm_env.cfg_env.limit_contract_code_size = None; evm_env.block_env.number = U256::from(tx_block_number); if let Some(block) = &block { evm_env.block_env = block_env_from_header(&block.header); // 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::Prague); } } apply_chain_and_block_specific_env_changes::( &mut evm_env, block, config.networks, ); } let trace_mode = TraceMode::Call .with_debug(self.debug) .with_decode_internal(if self.decode_internal { InternalTraceMode::Full } else { InternalTraceMode::None }) .with_state_changes(shell::verbosity() > 4); let mut executor = TracingExecutor::new( (evm_env.clone(), tx_env), fork, evm_version, trace_mode, networks, create2_deployer, None, )?; evm_env.cfg_env.set_spec(executor.spec_id()); // Set the state to the moment right before the transaction if !self.quick { if !shell::is_json() { sh_println!("Executing previous transactions from the block.")?; } if let Some(block) = block { let pb = init_progress(block.transactions.len() as u64, "tx"); pb.set_position(0); let BlockTransactions::Full(ref txs) = block.transactions else { return Err(eyre::eyre!("Could not get block txs")); }; for (index, tx) in txs.iter().enumerate() { // Replay system transactions only if running with `sys` option. // System transactions such as on L2s don't contain any pricing info so it // could cause reverts. if !self.replay_system_txes && (is_known_system_sender(tx.from()) || tx.transaction_type() == Some(SYSTEM_TRANSACTION_TYPE)) { pb.set_position((index + 1) as u64); continue; } if tx.tx_hash() == tx_hash { break; } let tx_env = tx.as_envelope().map_or(Default::default(), |tx_envelope| { TxEnv::from_recovered_tx(tx_envelope, tx.from()) }); evm_env.cfg_env.disable_balance_check = true; if let Some(to) = Transaction::to(tx) { trace!(tx=?tx.tx_hash(),?to, "executing previous call transaction"); executor.transact_with_env(evm_env.clone(), tx_env.clone()).wrap_err_with( || { format!( "Failed to execute transaction: {:?} in block {}", tx.tx_hash(), evm_env.block_env.number ) }, )?; } else { trace!(tx=?tx.tx_hash(), "executing previous create transaction"); if let Err(error) = executor.deploy_with_env(evm_env.clone(), tx_env.clone(), None) { match error { // Reverted transactions should be skipped EvmError::Execution(_) => (), error => { return Err(error).wrap_err_with(|| { format!( "Failed to deploy transaction: {:?} in block {}", tx.tx_hash(), evm_env.block_env.number ) }); } } } } pb.set_position((index + 1) as u64); } } } // Execute our transaction let result = { executor.set_trace_printer(self.trace_printer); let tx_env = tx.as_envelope().map_or(Default::default(), |tx_envelope| { TxEnv::from_recovered_tx(tx_envelope, tx.from()) }); if is_impersonated_tx(tx.as_ref()) { evm_env.cfg_env.disable_balance_check = true; } if let Some(to) = Transaction::to(&tx) { trace!(tx=?tx.tx_hash(), to=?to, "executing call transaction"); TraceResult::try_from(executor.transact_with_env(evm_env, tx_env))? } else { trace!(tx=?tx.tx_hash(), "executing create transaction"); TraceResult::try_from(executor.deploy_with_env(evm_env, tx_env, None))? } }; let contracts_bytecode = fetch_contracts_bytecode_from_trace(&executor, &result)?; handle_traces( result, &config, chain, &contracts_bytecode, label, with_local_artifacts, debug, decode_internal, disable_labels, self.trace_depth, ) .await?; Ok(()) } } pub fn fetch_contracts_bytecode_from_trace( executor: &Executor, result: &TraceResult, ) -> Result> { let mut contracts_bytecode = HashMap::default(); if let Some(ref traces) = result.traces { contracts_bytecode.extend(gather_trace_addresses(traces).filter_map(|addr| { // All relevant bytecodes should already be cached in the executor. let code = executor .backend() .basic_ref(addr) .inspect_err(|e| _ = sh_warn!("Failed to fetch code for {addr}: {e}")) .ok()?? .code? .bytes(); if code.is_empty() { return None; } Some((addr, code)) })); } Ok(contracts_bytecode) } fn gather_trace_addresses(traces: &Traces) -> impl Iterator { let mut addresses = AddressSet::default(); for (_, trace) in traces { for node in trace.arena.nodes() { if !node.trace.address.is_zero() { addresses.insert(node.trace.address); } if !node.trace.caller.is_zero() { addresses.insert(node.trace.caller); } } } addresses.into_iter() } impl figment::Provider for RunArgs { fn metadata(&self) -> Metadata { Metadata::named("RunArgs") } fn data(&self) -> Result, figment::Error> { let mut map = Map::new(); if let Some(api_key) = &self.etherscan.key { map.insert("etherscan_api_key".into(), api_key.as_str().into()); } if let Some(evm_version) = self.evm_version { map.insert("evm_version".into(), figment::value::Value::serialize(evm_version)?); } Ok(Map::from([(Config::selected_profile(), map)])) } } ================================================ FILE: crates/cast/src/cmd/send.rs ================================================ use std::{path::PathBuf, str::FromStr, time::Duration}; use alloy_consensus::{SignableTransaction, Signed}; use alloy_ens::NameOrAddress; use alloy_network::{AnyNetwork, EthereumWallet, Network}; use alloy_provider::{Provider, ProviderBuilder as AlloyProviderBuilder}; use alloy_signer::{Signature, Signer}; use clap::Parser; use eyre::{Result, eyre}; use foundry_cli::{opts::TransactionOpts, utils::LoadConfig}; use foundry_common::{ fmt::{UIfmt, UIfmtReceiptExt}, provider::ProviderBuilder, }; use foundry_primitives::FoundryTransactionBuilder; use tempo_alloy::TempoNetwork; use crate::tx::{self, CastTxBuilder, CastTxSender, SendTxOpts}; /// CLI arguments for `cast send`. #[derive(Debug, Parser)] pub struct SendTxArgs { /// The destination of the transaction. /// /// If not provided, you must use cast send --create. #[arg(value_parser = NameOrAddress::from_str)] to: Option, /// The signature of the function to call. sig: Option, /// The arguments of the function to call. #[arg(allow_negative_numbers = true)] args: Vec, /// Raw hex-encoded data for the transaction. Used instead of \[SIG\] and \[ARGS\]. #[arg( long, conflicts_with_all = &["sig", "args"] )] data: Option, #[command(flatten)] send_tx: SendTxOpts, #[command(subcommand)] command: Option, /// Send via `eth_sendTransaction` using the `--from` argument or $ETH_FROM as sender #[arg(long, requires = "from")] unlocked: bool, #[command(flatten)] tx: TransactionOpts, /// The path of blob data to be sent. #[arg( long, value_name = "BLOB_DATA_PATH", conflicts_with = "legacy", requires = "blob", help_heading = "Transaction options" )] path: Option, } #[derive(Debug, Parser)] pub enum SendTxSubcommands { /// Use to deploy raw contract bytecode. #[command(name = "--create")] Create { /// The bytecode of the contract to deploy. code: String, /// The signature of the function to call. sig: Option, /// The arguments of the function to call. #[arg(allow_negative_numbers = true)] args: Vec, }, } impl SendTxArgs { pub async fn run(self) -> Result<()> { if self.tx.tempo.is_tempo() { self.run_generic::().await } else { self.run_generic::().await } } pub async fn run_generic(self) -> Result<()> where N::TxEnvelope: From>, N::UnsignedTx: SignableTransaction, N::TransactionRequest: FoundryTransactionBuilder, N::ReceiptResponse: UIfmt + UIfmtReceiptExt, { let Self { to, mut sig, mut args, data, send_tx, tx, command, unlocked, path } = self; let print_sponsor_hash = tx.tempo.print_sponsor_hash; let blob_data = if let Some(path) = path { Some(std::fs::read(path)?) } else { None }; if let Some(data) = data { sig = Some(data); } let code = if let Some(SendTxSubcommands::Create { code, sig: constructor_sig, 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_empty() { 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) } else { None }; let config = send_tx.eth.load_config()?; let provider = ProviderBuilder::::from_config(&config)?.build()?; if let Some(interval) = send_tx.poll_interval { provider.client().set_poll_interval(Duration::from_secs(interval)) } let builder = CastTxBuilder::new(&provider, tx, &config) .await? .with_to(to) .await? .with_code_sig_and_args(code, sig, args) .await? .with_blob_data(blob_data)?; // If --tempo.print-sponsor-hash was passed, build the tx, print the hash, and exit. if print_sponsor_hash { let from = send_tx.eth.wallet.from.unwrap_or(config.sender); let (tx, _) = builder.build(from).await?; let hash = tx .compute_sponsor_hash(from) .ok_or_else(|| eyre!("This network does not support sponsored transactions"))?; sh_println!("{hash:?}")?; return Ok(()); } let timeout = send_tx.timeout.unwrap_or(config.transaction_timeout); // Launch browser signer if `--browser` flag is set let browser = send_tx.browser.run::().await?; // Case 1: // Default to sending via eth_sendTransaction if the --unlocked flag is passed. // This should be the only way this RPC method is used as it requires a local node // or remote RPC with unlocked accounts. if unlocked && browser.is_none() { // only check current chain id if it was specified in the config if let Some(config_chain) = config.chain { let current_chain_id = provider.get_chain_id().await?; let config_chain_id = config_chain.id(); // switch chain if current chain id is not the same as the one specified in the // config if config_chain_id != current_chain_id { sh_warn!("Switching to chain {}", config_chain)?; provider .raw_request::<_, ()>( "wallet_switchEthereumChain".into(), [serde_json::json!({ "chainId": format!("0x{:x}", config_chain_id), })], ) .await?; } } let (tx, _) = builder.build(config.sender).await?; cast_send( provider, tx, send_tx.cast_async, send_tx.sync, send_tx.confirmations, timeout, ) .await // Case 2: // Browser wallet signs and sends the transaction in one step. } else if let Some(browser) = browser { let (tx_request, _) = builder.build(browser.address()).await?; let tx_hash = browser.send_transaction_via_browser(tx_request).await?; let cast = CastTxSender::new(&provider); cast.print_tx_result(tx_hash, send_tx.cast_async, send_tx.confirmations, timeout).await // Case 3: // An option to use a local signer was provided. // If we cannot successfully instantiate a local signer, then we will assume we don't have // enough information to sign and we must bail. } else { let signer = send_tx.eth.wallet.signer().await?; let from = signer.address(); tx::validate_from_address(send_tx.eth.wallet.from, from)?; let (tx_request, _) = builder.build(&signer).await?; let wallet = EthereumWallet::from(signer); let provider = AlloyProviderBuilder::<_, _, N>::default() .wallet(wallet) .connect_provider(&provider); cast_send( provider, tx_request, send_tx.cast_async, send_tx.sync, send_tx.confirmations, timeout, ) .await } } } pub(crate) async fn cast_send>( provider: P, tx: N::TransactionRequest, cast_async: bool, sync: bool, confs: u64, timeout: u64, ) -> Result<()> where N::TransactionRequest: FoundryTransactionBuilder, N::ReceiptResponse: UIfmt + UIfmtReceiptExt, { let cast = CastTxSender::new(provider); if sync { // Send transaction and wait for receipt synchronously let receipt = cast.send_sync(tx).await?; sh_println!("{receipt}")?; } else { let pending_tx = cast.send(tx).await?; let tx_hash = *pending_tx.inner().tx_hash(); cast.print_tx_result(tx_hash, cast_async, confs, timeout).await?; } Ok(()) } ================================================ FILE: crates/cast/src/cmd/storage.rs ================================================ use crate::{Cast, opts::parse_slot}; use alloy_ens::NameOrAddress; use alloy_network::AnyNetwork; use alloy_primitives::{Address, B256, U256}; use alloy_provider::Provider; use alloy_rpc_types::BlockId; use clap::Parser; use comfy_table::{Cell, Table, modifiers::UTF8_ROUND_CORNERS, presets::ASCII_MARKDOWN}; use eyre::Result; use foundry_block_explorers::Client; use foundry_cli::{ opts::{BuildOpts, EtherscanOpts, RpcOpts}, utils, utils::LoadConfig, }; use foundry_common::{ abi::find_source, compile::{ProjectCompiler, etherscan_project}, shell, }; use foundry_compilers::{ Artifact, Project, artifacts::{ConfigurableContractArtifact, Contract, StorageLayout}, compilers::{ Compiler, solc::{Solc, SolcCompiler}, }, }; use foundry_config::{ Config, figment::{self, Metadata, Profile, value::Dict}, impl_figment_convert_cast, }; use semver::Version; use serde::{Deserialize, Serialize}; use std::str::FromStr; /// The minimum Solc version for outputting storage layouts. /// /// const MIN_SOLC: Version = Version::new(0, 6, 5); /// CLI arguments for `cast storage`. #[derive(Clone, Debug, Parser)] pub struct StorageArgs { /// The contract address. #[arg(value_parser = NameOrAddress::from_str)] address: NameOrAddress, /// The storage slot number. If not provided, it gets the full storage layout. #[arg(value_parser = parse_slot)] base_slot: Option, /// The storage offset from the base slot. If not provided, it is assumed to be zero. #[arg(value_parser = str::parse::, default_value_t = U256::ZERO)] offset: U256, /// The known proxy address. If provided, the storage layout is retrieved from this address. #[arg(long,value_parser = NameOrAddress::from_str)] proxy: Option, /// The block height to query at. /// /// Can also be the tags earliest, finalized, safe, latest, or pending. #[arg(long, short)] block: Option, #[command(flatten)] rpc: RpcOpts, #[command(flatten)] etherscan: EtherscanOpts, #[command(flatten)] build: BuildOpts, /// Specify the solc version to compile with. Overrides detected version. #[arg(long, value_parser = Version::parse)] solc_version: Option, } impl_figment_convert_cast!(StorageArgs); impl figment::Provider for StorageArgs { fn metadata(&self) -> Metadata { Metadata::named("StorageArgs") } fn data(&self) -> Result, figment::Error> { let mut map = self.build.data()?; let dict = map.get_mut(&Config::selected_profile()).unwrap(); dict.extend(self.rpc.dict()); dict.extend(self.etherscan.dict()); Ok(map) } } impl StorageArgs { pub async fn run(self) -> Result<()> { let config = self.load_config()?; let Self { address, base_slot, offset, block, build, .. } = self; let provider = utils::get_provider(&config)?; let address = address.resolve(&provider).await?; // Slot was provided, perform a simple RPC call if let Some(slot) = base_slot { let cast = Cast::new(provider); sh_println!( "{}", cast.storage( address, (Into::::into(slot).saturating_add(offset)).into(), block ) .await? )?; return Ok(()); } // No slot was provided // Get deployed bytecode at given address let address_code = provider.get_code_at(address).block_id(block.unwrap_or_default()).await?; if address_code.is_empty() { eyre::bail!("Provided address has no deployed code and thus no storage"); } // Check if we're in a forge project and if we can find the address' code let mut project = build.project()?; if project.paths.has_input_files() { // Find in artifacts and pretty print add_storage_layout_output(&mut project); let out = ProjectCompiler::new().quiet(shell::is_json()).compile(&project)?; let artifact = out.artifacts().find(|(_, artifact)| { artifact.get_deployed_bytecode_bytes().is_some_and(|b| *b == address_code) }); if let Some((_, artifact)) = artifact { return fetch_and_print_storage(provider, address, block, artifact).await; } } let chain = utils::get_chain(config.chain, &provider).await?; let api_key = config.get_etherscan_api_key(Some(chain)).or_else(|| self.etherscan.key()).ok_or_else(|| { eyre::eyre!("You must provide an Etherscan API key if you're fetching a remote contract's storage.") })?; let client = Client::new(chain, api_key)?; let source = if let Some(proxy) = self.proxy { find_source(client, proxy.resolve(&provider).await?).await? } else { find_source(client, address).await? }; let metadata = source.items.first().unwrap(); if metadata.is_vyper() { eyre::bail!("Contract at provided address is not a valid Solidity contract") } // Create or reuse a persistent cache for Etherscan sources; fall back to a temp dir let mut temp_dir = None; let root_path = if let Some(cache_root) = foundry_config::Config::foundry_etherscan_chain_cache_dir(chain) { let sources_root = cache_root.join("sources"); let contract_root = sources_root.join(format!("{address}")); if let Err(err) = std::fs::create_dir_all(&contract_root) { sh_warn!("Could not create etherscan cache dir, falling back to temp: {err}")?; let tmp = tempfile::tempdir()?; let path = tmp.path().to_path_buf(); temp_dir = Some(tmp); path } else { contract_root } } else { let tmp = tempfile::tempdir()?; let path = tmp.path().to_path_buf(); temp_dir = Some(tmp); path }; let mut project = etherscan_project(metadata, &root_path)?; add_storage_layout_output(&mut project); // Decide on compiler to use (user override -> metadata -> autodetect) let meta_version = metadata.compiler_version()?; let mut auto_detect = false; let desired = if let Some(user_version) = self.solc_version { if user_version < MIN_SOLC { sh_warn!( "The provided --solc-version is {user_version} while the minimum version for \ storage layouts is {MIN_SOLC} and as a result the output may be empty." )?; } SolcCompiler::Specific(Solc::find_or_install(&user_version)?) } else if meta_version < MIN_SOLC { auto_detect = true; SolcCompiler::AutoDetect } else { SolcCompiler::Specific(Solc::find_or_install(&meta_version)?) }; project.compiler.solc = Some(desired); // Compile let mut out = ProjectCompiler::new().quiet(true).compile(&project)?; let artifact = { let (_, mut artifact) = out .artifacts() .find(|(name, _)| name == &metadata.contract_name) .ok_or_else(|| eyre::eyre!("Could not find artifact"))?; if auto_detect && is_storage_layout_empty(&artifact.storage_layout) { // try recompiling with the minimum version sh_warn!( "The requested contract was compiled with {meta_version} while the minimum version \ for storage layouts is {MIN_SOLC} and as a result the output may be empty.", )?; let solc = Solc::find_or_install(&MIN_SOLC)?; project.compiler.solc = Some(SolcCompiler::Specific(solc)); if let Ok(output) = ProjectCompiler::new().quiet(true).compile(&project) { out = output; let (_, new_artifact) = out .artifacts() .find(|(name, _)| name == &metadata.contract_name) .ok_or_else(|| eyre::eyre!("Could not find artifact"))?; artifact = new_artifact; } } artifact }; drop(temp_dir); fetch_and_print_storage(provider, address, block, artifact).await } } /// Represents the value of a storage slot `eth_getStorageAt` call. #[derive(Clone, Debug, PartialEq, Eq)] struct StorageValue { /// The slot number. slot: B256, /// The value as returned by `eth_getStorageAt`. raw_slot_value: B256, } impl StorageValue { /// Returns the value of the storage slot, applying the offset if necessary. fn value(&self, offset: i64, number_of_bytes: Option) -> B256 { let offset = offset as usize; let mut end = 32; if let Some(number_of_bytes) = number_of_bytes { end = offset + number_of_bytes; if end > 32 { end = 32; } } // reverse range, because the value is stored in big endian let raw_sliced_value = &self.raw_slot_value.as_slice()[32 - end..32 - offset]; // copy the raw sliced value as tail let mut value = [0u8; 32]; value[32 - raw_sliced_value.len()..32].copy_from_slice(raw_sliced_value); B256::from(value) } } /// Represents the storage layout of a contract and its values. #[derive(Clone, Debug, Serialize, Deserialize)] struct StorageReport { #[serde(flatten)] layout: StorageLayout, values: Vec, } async fn fetch_and_print_storage>( provider: P, address: Address, block: Option, artifact: &ConfigurableContractArtifact, ) -> Result<()> { if is_storage_layout_empty(&artifact.storage_layout) { sh_warn!("Storage layout is empty.")?; Ok(()) } else { let layout = artifact.storage_layout.as_ref().unwrap().clone(); let values = fetch_storage_slots(provider, address, block, &layout).await?; print_storage(layout, values) } } async fn fetch_storage_slots>( provider: P, address: Address, block: Option, layout: &StorageLayout, ) -> Result> { let requests = layout.storage.iter().map(|storage_slot| async { let slot = B256::from(U256::from_str(&storage_slot.slot)?); let raw_slot_value = provider .get_storage_at(address, slot.into()) .block_id(block.unwrap_or_default()) .await?; let value = StorageValue { slot, raw_slot_value: raw_slot_value.into() }; Ok(value) }); futures::future::try_join_all(requests).await } fn print_storage(layout: StorageLayout, values: Vec) -> Result<()> { if shell::is_json() { let values: Vec<_> = layout .storage .iter() .zip(&values) .map(|(slot, storage_value)| { let storage_type = layout.types.get(&slot.storage_type); storage_value.value( slot.offset, storage_type.and_then(|t| t.number_of_bytes.parse::().ok()), ) }) .collect(); sh_println!( "{}", serde_json::to_string_pretty(&serde_json::to_value(StorageReport { layout, values })?)? )?; return Ok(()); } let mut table = Table::new(); if shell::is_markdown() { table.load_preset(ASCII_MARKDOWN); } else { table.apply_modifier(UTF8_ROUND_CORNERS); } table.set_header(vec![ Cell::new("Name"), Cell::new("Type"), Cell::new("Slot"), Cell::new("Offset"), Cell::new("Bytes"), Cell::new("Value"), Cell::new("Hex Value"), Cell::new("Contract"), ]); for (slot, storage_value) in layout.storage.into_iter().zip(values) { let storage_type = layout.types.get(&slot.storage_type); let value = storage_value .value(slot.offset, storage_type.and_then(|t| t.number_of_bytes.parse::().ok())); let converted_value = U256::from_be_bytes(value.0); table.add_row([ slot.label.as_str(), storage_type.map_or("?", |t| &t.label), &slot.slot, &slot.offset.to_string(), storage_type.map_or("?", |t| &t.number_of_bytes), &converted_value.to_string(), &value.to_string(), &slot.contract, ]); } sh_println!("\n{table}\n")?; Ok(()) } fn add_storage_layout_output>(project: &mut Project) { project.artifacts.additional_values.storage_layout = true; project.update_output_selection(|selection| { selection.0.values_mut().for_each(|contract_selection| { contract_selection .values_mut() .for_each(|selection| selection.push("storageLayout".to_string())) }); }) } fn is_storage_layout_empty(storage_layout: &Option) -> bool { if let Some(s) = storage_layout { s.storage.is_empty() } else { true } } #[cfg(test)] mod tests { use super::*; #[test] fn parse_storage_etherscan_api_key() { let args = StorageArgs::parse_from(["foundry-cli", "addr.eth", "--etherscan-api-key", "dummykey"]); assert_eq!(args.etherscan.key(), Some("dummykey".to_string())); unsafe { std::env::set_var("ETHERSCAN_API_KEY", "FXY"); } let config = args.load_config().unwrap(); unsafe { std::env::remove_var("ETHERSCAN_API_KEY"); } assert_eq!(config.etherscan_api_key, Some("dummykey".to_string())); let key = config.get_etherscan_api_key(None).unwrap(); assert_eq!(key, "dummykey".to_string()); } #[test] fn parse_solc_version_arg() { let args = StorageArgs::parse_from(["foundry-cli", "addr.eth", "--solc-version", "0.8.10"]); assert_eq!(args.solc_version, Some(Version::parse("0.8.10").unwrap())); } } ================================================ FILE: crates/cast/src/cmd/trace.rs ================================================ use alloy_eips::Encodable2718; use alloy_network::AnyRpcTransaction; use alloy_primitives::hex; use alloy_provider::ext::TraceApi; use clap::Parser; use eyre::Result; use foundry_cli::{ opts::RpcOpts, utils::{self, LoadConfig}, }; use foundry_common::stdin; /// CLI arguments for `cast trace`. #[derive(Debug, Parser)] pub struct TraceArgs { /// Transaction hash (for trace_transaction) or raw tx hex/JSON (for trace_rawTransaction /// with --raw) tx: Option, /// Use trace_rawTransaction instead of trace_transaction. /// Required when passing raw transaction hex or JSON instead of a tx hash. #[arg(long)] raw: bool, /// Include the basic trace of the transaction. #[arg(long, requires = "raw")] trace: bool, /// Include the full trace of the virtual machine's state during transaction execution #[arg(long, requires = "raw")] vm_trace: bool, /// Include state changes caused by the transaction (requires --raw). #[arg(long, requires = "raw")] state_diff: bool, #[command(flatten)] rpc: RpcOpts, } impl TraceArgs { pub async fn run(self) -> Result<()> { let config = self.rpc.load_config()?; let provider = utils::get_provider(&config)?; let input = stdin::unwrap_line(self.tx)?; let trimmed = input.trim(); let is_json = trimmed.starts_with('{'); let is_raw_hex = trimmed.starts_with("0x") && trimmed.len() > 66; let result = if self.raw { // trace_rawTransaction: accepts raw hex OR JSON tx let raw_bytes = if is_raw_hex { hex::decode(trimmed.strip_prefix("0x").unwrap_or(trimmed))? } else if is_json { let tx: AnyRpcTransaction = serde_json::from_str(trimmed)?; tx.as_ref().encoded_2718().to_vec() } else { hex::decode(trimmed)? }; let mut trace_builder = provider.trace_raw_transaction(&raw_bytes); if self.trace { trace_builder = trace_builder.trace(); } if self.vm_trace { trace_builder = trace_builder.vm_trace(); } if self.state_diff { trace_builder = trace_builder.state_diff(); } if trace_builder.get_trace_types().map(|t| t.is_empty()).unwrap_or(true) { eyre::bail!("No trace type specified. Use --trace, --vm-trace, or --state-diff"); } serde_json::to_string_pretty(&trace_builder.await?)? } else { // trace_transaction: use tx hash directly let hash = input.parse()?; let traces = provider.trace_transaction(hash).await?; serde_json::to_string_pretty(&traces)? }; sh_println!("{}", result)?; Ok(()) } } ================================================ FILE: crates/cast/src/cmd/txpool.rs ================================================ use alloy_primitives::Address; use alloy_provider::ext::TxPoolApi; use clap::Parser; use foundry_cli::{ opts::RpcOpts, utils::{self, LoadConfig}, }; /// CLI arguments for `cast tx-pool`. #[derive(Debug, Parser, Clone)] pub enum TxPoolSubcommands { /// Fetches the content of the transaction pool. Content { #[command(flatten)] args: RpcOpts, }, /// Fetches the content of the transaction pool filtered by a specific address. ContentFrom { /// The Signer to filter the transactions by. #[arg(short, long)] from: Address, #[command(flatten)] args: RpcOpts, }, /// Fetches a textual summary of each transaction in the pool. Inspect { #[command(flatten)] args: RpcOpts, }, /// Fetches the current status of the transaction pool. Status { #[command(flatten)] args: RpcOpts, }, } impl TxPoolSubcommands { pub async fn run(self) -> eyre::Result<()> { match self { Self::Content { args } => { let config = args.load_config()?; let provider = utils::get_provider(&config)?; let content = provider.txpool_content().await?; sh_println!("{}", serde_json::to_string_pretty(&content)?)?; } Self::ContentFrom { from, args } => { let config = args.load_config()?; let provider = utils::get_provider(&config)?; let content = provider.txpool_content_from(from).await?; sh_println!("{}", serde_json::to_string_pretty(&content)?)?; } Self::Inspect { args } => { let config = args.load_config()?; let provider = utils::get_provider(&config)?; let inspect = provider.txpool_inspect().await?; sh_println!("{}", serde_json::to_string_pretty(&inspect)?)?; } Self::Status { args } => { let config = args.load_config()?; let provider = utils::get_provider(&config)?; let status = provider.txpool_status().await?; sh_println!("{}", serde_json::to_string_pretty(&status)?)?; } }; Ok(()) } } ================================================ FILE: crates/cast/src/cmd/wallet/list.rs ================================================ use clap::Parser; use eyre::Result; use std::env; use foundry_common::{fs, sh_err, sh_println}; use foundry_config::Config; use foundry_wallets::wallet_multi::MultiWalletOptsBuilder; /// CLI arguments for `cast wallet list`. #[derive(Clone, Debug, Parser)] pub struct ListArgs { /// List all the accounts in the keystore directory. /// Default keystore directory is used if no path provided. #[arg(long, default_missing_value = "", num_args(0..=1))] dir: Option, /// List accounts from a Ledger hardware wallet. #[arg(long, short, group = "hw-wallets")] ledger: bool, /// List accounts from a Trezor hardware wallet. #[arg(long, short, group = "hw-wallets")] trezor: bool, /// List accounts from AWS KMS. /// /// Ensure either one of AWS_KMS_KEY_IDS (comma-separated) or AWS_KMS_KEY_ID environment /// variables are set. #[arg(long, hide = !cfg!(feature = "aws-kms"))] aws: bool, /// List accounts from Google Cloud KMS. /// /// Ensure the following environment variables are set: GCP_PROJECT_ID, GCP_LOCATION, /// GCP_KEY_RING, GCP_KEY_NAME, GCP_KEY_VERSION. /// /// See: #[arg(long, hide = !cfg!(feature = "gcp-kms"))] gcp: bool, /// List accounts from Turnkey. #[arg(long, hide = !cfg!(feature = "turnkey"))] turnkey: bool, /// List all configured accounts. #[arg(long, group = "hw-wallets")] all: bool, /// Max number of addresses to display from hardware wallets. #[arg(long, short, default_value = "3", requires = "hw-wallets")] max_senders: Option, } impl ListArgs { pub async fn run(self) -> Result<()> { // list local accounts as files in keystore dir, no need to unlock / provide password if self.dir.is_some() || self.all || (!self.ledger && !self.trezor && !self.aws && !self.gcp) { match self.list_local_senders() { Ok(()) => {} Err(e) if !self.all => { sh_err!("{}", e)?; } _ => {} } } // Create options for multi wallet - ledger, trezor and AWS let list_opts = MultiWalletOptsBuilder::default() .ledger(self.ledger || self.all) .mnemonic_indexes(Some(vec![0])) .trezor(self.trezor || self.all) .aws(self.aws || self.all) .gcp(self.gcp || (self.all && gcp_env_vars_set())) .turnkey(self.turnkey || self.all) .interactives(0) .interactive(false) .browser(Default::default()) .build() .expect("build multi wallet"); // macro to print senders for a list of signers macro_rules! list_senders { ($signers:expr, $label:literal) => { match $signers.await { Ok(signers) => { for signer in signers.unwrap_or_default().iter() { signer .available_senders(self.max_senders.unwrap()) .await? .iter() .for_each(|sender| { let _ = sh_println!("{} ({})", sender, $label); }) } } Err(e) if !self.all => { sh_err!("{}", e)?; } _ => {} } }; } list_senders!(list_opts.ledgers(), "Ledger"); list_senders!(list_opts.trezors(), "Trezor"); list_senders!(list_opts.aws_signers(), "AWS"); list_senders!(list_opts.gcp_signers(), "GCP"); Ok(()) } fn list_local_senders(&self) -> Result<()> { let keystore_path = self.dir.as_deref().unwrap_or_default(); let keystore_dir = if keystore_path.is_empty() { // Create the keystore default directory if it doesn't exist let default_dir = Config::foundry_keystores_dir().unwrap(); fs::create_dir_all(&default_dir)?; default_dir } else { dunce::canonicalize(keystore_path)? }; // List all files within the keystore directory. for entry in std::fs::read_dir(keystore_dir)? { let path = entry?.path(); if path.is_file() && let Some(file_name) = path.file_name() && let Some(name) = file_name.to_str() { // Extract address from keystore filename format: UTC--{timestamp}--{address} if let Some(address) = name.split("--").last() { sh_println!("0x{} (Local)", address)?; } } } Ok(()) } } fn gcp_env_vars_set() -> bool { let required_vars = ["GCP_PROJECT_ID", "GCP_LOCATION", "GCP_KEY_RING", "GCP_KEY_NAME", "GCP_KEY_VERSION"]; required_vars.iter().all(|&var| env::var(var).is_ok()) } ================================================ FILE: crates/cast/src/cmd/wallet/mod.rs ================================================ use alloy_chains::Chain; use alloy_dyn_abi::TypedData; use alloy_primitives::{Address, B256, Signature, U256, hex}; use alloy_provider::Provider; use alloy_rpc_types::Authorization; use alloy_signer::Signer; use alloy_signer_local::{ MnemonicBuilder, PrivateKeySigner, coins_bip39::{English, Entropy, Mnemonic}, }; use clap::Parser; use eyre::{Context, Result}; 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_08::thread_rng; use serde_json::json; use std::path::Path; use yansi::Paint; pub mod vanity; use vanity::VanityArgs; pub mod list; use list::ListArgs; /// CLI arguments for `cast wallet`. #[derive(Debug, Parser)] pub enum WalletSubcommands { /// Create a new random keypair. #[command(visible_alias = "n")] New { /// If provided, then keypair will be written to an encrypted JSON keystore. path: Option, /// Account name for the keystore file. If provided, the keystore file /// will be named using this account name. #[arg(value_name = "ACCOUNT_NAME")] account_name: Option, /// Triggers a hidden password prompt for the JSON keystore. /// /// Deprecated: prompting for a hidden password is now the default. #[arg(long, short, conflicts_with = "unsafe_password")] password: bool, /// Password for the JSON keystore in cleartext. /// /// This is UNSAFE to use and we recommend using the --password. #[arg(long, env = "CAST_PASSWORD", value_name = "PASSWORD")] unsafe_password: Option, /// Number of wallets to generate. #[arg(long, short, default_value = "1")] number: u32, /// Overwrite existing keystore files without prompting. #[arg(long)] force: bool, }, /// Generates a random BIP39 mnemonic phrase #[command(visible_alias = "nm")] NewMnemonic { /// Number of words for the mnemonic #[arg(long, short, default_value = "12")] words: usize, /// Number of accounts to display #[arg(long, short, default_value = "1")] accounts: u8, /// Entropy to use for the mnemonic #[arg(long, short, conflicts_with = "words")] entropy: Option, }, /// Generate a vanity address. #[command(visible_alias = "va")] Vanity(VanityArgs), /// Convert a private key to an address. #[command(visible_aliases = &["a", "addr"])] Address { /// If provided, the address will be derived from the specified private key. #[arg(value_name = "PRIVATE_KEY")] private_key_override: Option, #[command(flatten)] wallet: WalletOpts, }, /// Derive accounts from a mnemonic #[command(visible_alias = "d")] Derive { /// The accounts will be derived from the specified mnemonic phrase. #[arg(value_name = "MNEMONIC")] mnemonic: String, /// Number of accounts to display. #[arg(long, short, default_value = "1")] accounts: Option, /// Insecure mode: display private keys in the terminal. #[arg(long, default_value = "false")] insecure: bool, }, /// Sign a message or typed data. #[command(visible_alias = "s")] Sign { /// The message, typed data, or hash to sign. /// /// Messages starting with 0x are expected to be hex encoded, which get decoded before /// being signed. /// /// The message will be prefixed with the Ethereum Signed Message header and hashed before /// signing, unless `--no-hash` is provided. /// /// Typed data can be provided as a json string or a file name. /// Use --data flag to denote the message is a string of typed data. /// Use --data --from-file to denote the message is a file name containing typed data. /// The data will be combined and hashed using the EIP712 specification before signing. /// The data should be formatted as JSON. message: String, /// Treat the message as JSON typed data. #[arg(long)] data: bool, /// Treat the message as a file containing JSON typed data. Requires `--data`. #[arg(long, requires = "data")] from_file: bool, /// Treat the message as a raw 32-byte hash and sign it directly without hashing it again. #[arg(long, conflicts_with = "data")] no_hash: bool, #[command(flatten)] wallet: WalletOpts, }, /// EIP-7702 sign authorization. #[command(visible_alias = "sa")] SignAuth { /// Address to sign authorization for. address: Address, #[command(flatten)] rpc: RpcOpts, #[arg(long)] nonce: Option, #[arg(long)] chain: Option, /// If set, indicates the authorization will be broadcast by the signing account itself. /// This means the nonce used will be the current nonce + 1 (to account for the /// transaction that will include this authorization). #[arg(long, conflicts_with = "nonce")] self_broadcast: bool, #[command(flatten)] wallet: WalletOpts, }, /// Verify the signature of a message. #[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. /// /// The message will be prefixed with the Ethereum Signed Message header and hashed before /// signing, unless `--no-hash` is provided. /// /// Typed data can be provided as a json string or a file name. /// Use --data flag to denote the message is a string of typed data. /// Use --data --from-file to denote the message is a file name containing typed data. /// The data will be combined and hashed using the EIP712 specification before signing. /// The data should be formatted as JSON. message: String, /// The signature to verify. signature: Signature, /// The address of the message signer. #[arg(long, short)] address: Address, /// Treat the message as JSON typed data. #[arg(long)] data: bool, /// Treat the message as a file containing JSON typed data. Requires `--data`. #[arg(long, requires = "data")] from_file: bool, /// Treat the message as a raw 32-byte hash and sign it directly without hashing it again. #[arg(long, conflicts_with = "data")] no_hash: bool, }, /// Import a private key into an encrypted keystore. #[command(visible_alias = "i")] Import { /// The name for the account in the keystore. #[arg(value_name = "ACCOUNT_NAME")] account_name: String, /// If provided, keystore will be saved here instead of the default keystores directory /// (~/.foundry/keystores) #[arg(long, short)] keystore_dir: Option, /// Password for the JSON keystore in cleartext /// This is unsafe, we recommend using the default hidden password prompt #[arg(long, env = "CAST_UNSAFE_PASSWORD", value_name = "PASSWORD")] unsafe_password: Option, #[command(flatten)] raw_wallet_options: RawWalletOpts, }, /// List all the accounts in the keystore default directory #[command(visible_alias = "ls")] List(ListArgs), /// Remove a wallet from the keystore. /// /// This command requires the wallet alias and will prompt for a password to ensure that only /// an authorized user can remove the wallet. #[command(visible_aliases = &["rm"], override_usage = "cast wallet remove --name ")] Remove { /// The alias (or name) of the wallet to remove. #[arg(long, required = true)] name: String, /// Optionally provide the keystore directory if not provided. default directory will be /// used (~/.foundry/keystores). #[arg(long)] dir: Option, /// Password for the JSON keystore in cleartext /// This is unsafe, we recommend using the default hidden password prompt #[arg(long, env = "CAST_UNSAFE_PASSWORD", value_name = "PASSWORD")] unsafe_password: Option, }, /// Derives private key from mnemonic #[command(name = "private-key", visible_alias = "pk", aliases = &["derive-private-key", "--derive-private-key"])] PrivateKey { /// If provided, the private key will be derived from the specified mnemonic phrase. #[arg(value_name = "MNEMONIC")] mnemonic_override: Option, /// If provided, the private key will be derived using the /// specified mnemonic index (if integer) or derivation path. #[arg(value_name = "MNEMONIC_INDEX_OR_DERIVATION_PATH")] mnemonic_index_or_derivation_path_override: Option, #[command(flatten)] wallet: WalletOpts, }, /// Get the public key for the given private key. #[command(visible_aliases = &["pubkey"])] PublicKey { /// If provided, the public key will be derived from the specified private key. #[arg(long = "raw-private-key", value_name = "PRIVATE_KEY")] private_key_override: Option, #[command(flatten)] wallet: WalletOpts, }, /// Decrypt a keystore file to get the private key #[command(name = "decrypt-keystore", visible_alias = "dk")] DecryptKeystore { /// The name for the account in the keystore. #[arg(value_name = "ACCOUNT_NAME")] account_name: String, /// If not provided, keystore will try to be located at the default keystores directory /// (~/.foundry/keystores) #[arg(long, short)] keystore_dir: Option, /// Password for the JSON keystore in cleartext /// This is unsafe, we recommend using the default hidden password prompt #[arg(long, env = "CAST_UNSAFE_PASSWORD", value_name = "PASSWORD")] unsafe_password: Option, }, /// Change the password of a keystore file #[command(name = "change-password", visible_alias = "cp")] ChangePassword { /// The name for the account in the keystore. #[arg(value_name = "ACCOUNT_NAME")] account_name: String, /// If not provided, keystore will try to be located at the default keystores directory /// (~/.foundry/keystores) #[arg(long, short)] keystore_dir: Option, /// Current password for the JSON keystore in cleartext /// This is unsafe, we recommend using the default hidden password prompt #[arg(long, env = "CAST_UNSAFE_PASSWORD", value_name = "PASSWORD")] unsafe_password: Option, /// New password for the JSON keystore in cleartext /// This is unsafe, we recommend using the default hidden password prompt #[arg(long, env = "CAST_UNSAFE_NEW_PASSWORD", value_name = "NEW_PASSWORD")] unsafe_new_password: Option, }, } impl WalletSubcommands { pub async fn run(self) -> Result<()> { match self { Self::New { path, account_name, unsafe_password, number, password, force } => { let mut rng = thread_rng(); let mut json_values = if shell::is_json() { Some(vec![]) } else { None }; let path = if let Some(path) = path { match dunce::canonicalize(&path) { Ok(path) => { if !path.is_dir() { // we require path to be an existing directory eyre::bail!("`{}` is not a directory", path.display()); } Some(path) } Err(e) => { eyre::bail!( "If you specified a directory, please make sure it exists, or create it before running `cast wallet new `.\n{path} is not a directory.\nError: {}", e ); } } } else if unsafe_password.is_some() || password { let path = Config::foundry_keystores_dir().ok_or_else(|| { eyre::eyre!("Could not find the default keystore directory.") })?; fs::create_dir_all(&path)?; Some(path) } else { None }; match path { Some(path) => { let password = if let Some(password) = unsafe_password { password } else { // if no --unsafe-password was provided read via stdin rpassword::prompt_password("Enter secret: ")? }; // Prevent accidental overwriting: check all target files upfront if !force && let Some(ref acc_name) = account_name { let mut existing_files = Vec::new(); for i in 0..number { let name = match number { 1 => acc_name.to_string(), _ => format!("{}_{}", acc_name, i + 1), }; let file_path = path.join(&name); if file_path.exists() { existing_files.push(name); } } if !existing_files.is_empty() { use std::io::Write; sh_eprintln!("The following keystore file(s) already exist:")?; for file in &existing_files { sh_eprintln!(" - {file}")?; } sh_print!( "\nDo you want to overwrite all {} file(s)? [y/N]: ", existing_files.len() )?; std::io::stdout().flush()?; let mut input = String::new(); std::io::stdin().read_line(&mut input)?; if !input.trim().eq_ignore_ascii_case("y") { eyre::bail!("Operation cancelled. No keystores were modified."); } } } for i in 0..number { let account_name_ref = account_name.as_deref().map(|name| match number { 1 => name.to_string(), _ => format!("{}_{}", name, i + 1), }); let (wallet, uuid) = PrivateKeySigner::new_keystore( &path, &mut rng, password.clone(), account_name_ref.as_deref(), )?; let identifier = account_name_ref.as_deref().unwrap_or(&uuid); if let Some(json) = json_values.as_mut() { json.push(if shell::verbosity() > 0 { json!({ "address": wallet.address().to_checksum(None), "public_key": format!("0x{}", hex::encode(wallet.public_key())), "path": format!("{}", path.join(identifier).display()), }) } else { json!({ "address": wallet.address().to_checksum(None), "path": format!("{}", path.join(identifier).display()), }) }); } else { sh_println!( "Created new encrypted keystore file: {}", path.join(identifier).display() )?; sh_println!("Address: {}", wallet.address().to_checksum(None))?; if shell::verbosity() > 0 { sh_println!( "Public key: 0x{}", hex::encode(wallet.public_key()) )?; } } } } None => { for _ in 0..number { let wallet = PrivateKeySigner::random_with(&mut rng); if let Some(json) = json_values.as_mut() { json.push(if shell::verbosity() > 0 { json!({ "address": wallet.address().to_checksum(None), "public_key": format!("0x{}", hex::encode(wallet.public_key())), "private_key": format!("0x{}", hex::encode(wallet.credential().to_bytes())), }) } else { json!({ "address": wallet.address().to_checksum(None), "private_key": format!("0x{}", hex::encode(wallet.credential().to_bytes())), }) }); } else { sh_println!("Successfully created new keypair.")?; sh_println!("Address: {}", wallet.address().to_checksum(None))?; if shell::verbosity() > 0 { sh_println!( "Public key: 0x{}", hex::encode(wallet.public_key()) )?; } sh_println!( "Private key: 0x{}", hex::encode(wallet.credential().to_bytes()) )?; } } } } if let Some(json) = json_values.as_ref() { sh_println!("{}", serde_json::to_string_pretty(json)?)?; } } Self::NewMnemonic { words, accounts, entropy } => { let phrase = if let Some(entropy) = entropy { let entropy = Entropy::from_slice(hex::decode(entropy)?)?; Mnemonic::::new_from_entropy(entropy).to_phrase() } else { let mut rng = thread_rng(); Mnemonic::::new_with_count(&mut rng, words)?.to_phrase() }; let format_json = shell::is_json(); if !format_json { sh_println!("{}", "Generating mnemonic from provided entropy...".yellow())?; } let builder = MnemonicBuilder::::default().phrase(phrase.as_str()); let derivation_path = "m/44'/60'/0'/0/"; let wallets = (0..accounts) .map(|i| builder.clone().derivation_path(format!("{derivation_path}{i}"))) .collect::, _>>()?; let wallets = wallets.into_iter().map(|b| b.build()).collect::, _>>()?; if !format_json { sh_println!("{}", "Successfully generated a new mnemonic.".green())?; sh_println!("Phrase:\n{phrase}")?; sh_println!("\nAccounts:")?; } let mut accounts = json!([]); for (i, wallet) in wallets.iter().enumerate() { let public_key = hex::encode(wallet.public_key()); let private_key = hex::encode(wallet.credential().to_bytes()); if format_json { accounts.as_array_mut().unwrap().push(if shell::verbosity() > 0 { json!({ "address": format!("{}", wallet.address()), "public_key": format!("0x{}", public_key), "private_key": format!("0x{}", private_key), }) } else { json!({ "address": format!("{}", wallet.address()), "private_key": format!("0x{}", private_key), }) }); } else { sh_println!("- Account {i}:")?; sh_println!("Address: {}", wallet.address())?; if shell::verbosity() > 0 { sh_println!("Public key: 0x{}", public_key)?; } sh_println!("Private key: 0x{}\n", private_key)?; } } if format_json { let obj = json!({ "mnemonic": phrase, "accounts": accounts, }); sh_println!("{}", serde_json::to_string_pretty(&obj)?)?; } } Self::Vanity(cmd) => { cmd.run()?; } Self::Address { wallet, private_key_override } => { let wallet = private_key_override .map(|pk| WalletOpts { raw: RawWalletOpts { private_key: Some(pk), ..Default::default() }, ..Default::default() }) .unwrap_or(wallet) .signer() .await?; let addr = wallet.address(); sh_println!("{}", addr.to_checksum(None))?; } Self::Derive { mnemonic, accounts, insecure } => { let format_json = shell::is_json(); let mut accounts_json = json!([]); for i in 0..accounts.unwrap_or(1) { let wallet = WalletOpts { raw: RawWalletOpts { mnemonic: Some(mnemonic.clone()), mnemonic_index: i as u32, ..Default::default() }, ..Default::default() } .signer() .await?; match wallet { WalletSigner::Local(local_wallet) => { let address = local_wallet.address().to_checksum(None); let private_key = hex::encode(local_wallet.credential().to_bytes()); if format_json { if insecure { accounts_json.as_array_mut().unwrap().push(json!({ "address": format!("{}", address), "private_key": format!("0x{}", private_key), })); } else { accounts_json.as_array_mut().unwrap().push(json!({ "address": format!("{}", address) })); } } else { sh_println!("- Account {i}:")?; if insecure { sh_println!("Address: {}", address)?; sh_println!("Private key: 0x{}\n", private_key)?; } else { sh_println!("Address: {}\n", address)?; } } } _ => eyre::bail!("Only local wallets are supported by this command"), } } if format_json { sh_println!("{}", serde_json::to_string_pretty(&accounts_json)?)?; } } Self::PublicKey { wallet, private_key_override } => { let wallet = private_key_override .map(|pk| WalletOpts { raw: RawWalletOpts { private_key: Some(pk), ..Default::default() }, ..Default::default() }) .unwrap_or(wallet) .signer() .await?; let public_key = match wallet { WalletSigner::Local(wallet) => wallet.public_key(), _ => eyre::bail!("Only local wallets are supported by this command"), }; sh_println!("0x{}", hex::encode(public_key))?; } Self::Sign { message, data, from_file, no_hash, wallet } => { let wallet = wallet.signer().await?; let sig = if data { let typed_data: TypedData = if from_file { // data is a file name, read json from file foundry_common::fs::read_json_file(message.as_ref())? } else { // data is a json string serde_json::from_str(&message)? }; wallet.sign_dynamic_typed_data(&typed_data).await? } else if no_hash { wallet.sign_hash(&hex::decode(&message)?[..].try_into()?).await? } else { wallet.sign_message(&Self::hex_str_to_bytes(&message)?).await? }; 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, self_broadcast } => { let wallet = wallet.signer().await?; let provider = utils::get_provider(&rpc.load_config()?)?; let nonce = if let Some(nonce) = nonce { nonce } else { let current_nonce = provider.get_transaction_count(wallet.address()).await?; if self_broadcast { // When self-broadcasting, the authorization nonce needs to be +1 // because the transaction itself will consume the current nonce current_nonce + 1 } else { current_nonce } }; let chain_id = if let Some(chain) = chain { chain.id() } else { provider.get_chain_id().await? }; 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); 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, data, from_file, no_hash } => { let recovered_address = if data { let typed_data: TypedData = if from_file { // data is a file name, read json from file foundry_common::fs::read_json_file(message.as_ref())? } else { // data is a json string serde_json::from_str(&message)? }; Self::recover_address_from_typed_data(&typed_data, &signature)? } else if no_hash { Self::recover_address_from_message_no_hash( &hex::decode(&message)?[..].try_into()?, &signature, )? } else { Self::recover_address_from_message(&message, &signature)? }; if address == recovered_address { sh_println!("Validation succeeded. Address {address} signed this message.")?; } else { eyre::bail!("Validation failed. Address {address} did not sign this message."); } } Self::Import { account_name, keystore_dir, unsafe_password, raw_wallet_options } => { // Set up keystore directory let dir = if let Some(path) = keystore_dir { Path::new(&path).to_path_buf() } else { Config::foundry_keystores_dir().ok_or_else(|| { eyre::eyre!("Could not find the default keystore directory.") })? }; fs::create_dir_all(&dir)?; // check if account exists already let keystore_path = Path::new(&dir).join(&account_name); if keystore_path.exists() { eyre::bail!("Keystore file already exists at {}", keystore_path.display()); } // get wallet let wallet = raw_wallet_options .signer()? .and_then(|s| match s { WalletSigner::Local(s) => Some(s), _ => None, }) .ok_or_else(|| { eyre::eyre!( "\ Did you set a private key or mnemonic? Run `cast wallet import --help` and use the corresponding CLI flag to set your key via: --private-key, --mnemonic-path or --interactive." ) })?; let private_key = wallet.credential().to_bytes(); let password = if let Some(password) = unsafe_password { password } else { // if no --unsafe-password was provided read via stdin rpassword::prompt_password("Enter password: ")? }; let mut rng = thread_rng(); let (wallet, _) = PrivateKeySigner::encrypt_keystore( dir, &mut rng, private_key, password, Some(&account_name), )?; let address = wallet.address(); let success_message = format!( "`{}` keystore was saved successfully. Address: {:?}", &account_name, address, ); sh_println!("{}", success_message.green())?; } Self::List(cmd) => { cmd.run().await?; } Self::Remove { name, dir, unsafe_password } => { let dir = if let Some(path) = dir { Path::new(&path).to_path_buf() } else { Config::foundry_keystores_dir().ok_or_else(|| { eyre::eyre!("Could not find the default keystore directory.") })? }; let keystore_path = Path::new(&dir).join(&name); if !keystore_path.exists() { eyre::bail!("Keystore file does not exist at {}", keystore_path.display()); } let password = if let Some(pwd) = unsafe_password { pwd } else { rpassword::prompt_password("Enter password: ")? }; if PrivateKeySigner::decrypt_keystore(&keystore_path, password).is_err() { eyre::bail!("Invalid password - wallet removal cancelled"); } std::fs::remove_file(&keystore_path).wrap_err_with(|| { format!("Failed to remove keystore file at {}", keystore_path.display()) })?; let success_message = format!("`{}` keystore was removed successfully.", &name); sh_println!("{}", success_message.green())?; } Self::PrivateKey { wallet, mnemonic_override, mnemonic_index_or_derivation_path_override, } => { let (index_override, derivation_path_override) = match mnemonic_index_or_derivation_path_override { Some(value) => match value.parse::() { Ok(index) => (Some(index), None), Err(_) => (None, Some(value)), }, None => (None, None), }; let wallet = WalletOpts { raw: RawWalletOpts { mnemonic: mnemonic_override.or(wallet.raw.mnemonic), mnemonic_index: index_override.unwrap_or(wallet.raw.mnemonic_index), hd_path: derivation_path_override.or(wallet.raw.hd_path), ..wallet.raw }, ..wallet } .signer() .await?; match wallet { WalletSigner::Local(wallet) => { if shell::verbosity() > 0 { sh_println!("Address: {}", wallet.address())?; sh_println!( "Private key: 0x{}", hex::encode(wallet.credential().to_bytes()) )?; } else { sh_println!("0x{}", hex::encode(wallet.credential().to_bytes()))?; } } _ => { eyre::bail!("Only local wallets are supported by this command."); } } } Self::DecryptKeystore { account_name, keystore_dir, unsafe_password } => { // Set up keystore directory let dir = if let Some(path) = keystore_dir { Path::new(&path).to_path_buf() } else { Config::foundry_keystores_dir().ok_or_else(|| { eyre::eyre!("Could not find the default keystore directory.") })? }; let keypath = dir.join(&account_name); if !keypath.exists() { eyre::bail!("Keystore file does not exist at {}", keypath.display()); } let password = if let Some(password) = unsafe_password { password } else { // if no --unsafe-password was provided read via stdin rpassword::prompt_password("Enter password: ")? }; let wallet = PrivateKeySigner::decrypt_keystore(keypath, password)?; let private_key = B256::from_slice(&wallet.credential().to_bytes()); let success_message = format!("{}'s private key is: {}", &account_name, private_key); sh_println!("{}", success_message.green())?; } Self::ChangePassword { account_name, keystore_dir, unsafe_password, unsafe_new_password, } => { // Set up keystore directory let dir = if let Some(path) = keystore_dir { Path::new(&path).to_path_buf() } else { Config::foundry_keystores_dir().ok_or_else(|| { eyre::eyre!("Could not find the default keystore directory.") })? }; let keypath = dir.join(&account_name); if !keypath.exists() { eyre::bail!("Keystore file does not exist at {}", keypath.display()); } let current_password = if let Some(password) = unsafe_password { password } else { // if no --unsafe-password was provided read via stdin rpassword::prompt_password("Enter current password: ")? }; // decrypt the keystore to verify the current password and get the private key let wallet = PrivateKeySigner::decrypt_keystore(&keypath, current_password.clone()) .map_err(|_| eyre::eyre!("Invalid password - password change cancelled"))?; let new_password = if let Some(password) = unsafe_new_password { password } else { // if no --unsafe-new-password was provided read via stdin rpassword::prompt_password("Enter new password: ")? }; if current_password == new_password { eyre::bail!("New password cannot be the same as the current password"); } // Create a new keystore with the new password let private_key = wallet.credential().to_bytes(); let mut rng = thread_rng(); let (wallet, _) = PrivateKeySigner::encrypt_keystore( dir, &mut rng, private_key, new_password, Some(&account_name), )?; let success_message = format!( "Password for keystore `{}` was changed successfully. Address: {:?}", &account_name, wallet.address(), ); sh_println!("{}", success_message.green())?; } }; Ok(()) } /// 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)?) } /// Recovers an address from the specified message and signature. fn recover_address_from_message_no_hash( prehash: &B256, signature: &Signature, ) -> Result
{ Ok(signature.recover_address_from_prehash(prehash)?) } /// Recovers an address from the specified EIP-712 typed data and signature. fn recover_address_from_typed_data( typed_data: &TypedData, signature: &Signature, ) -> Result
{ Ok(signature.recover_address_from_prehash(&typed_data.eip712_signing_hash()?)?) } /// 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.")?, None => s.as_bytes().to_vec(), }) } } #[cfg(test)] mod tests { use super::*; use alloy_primitives::{address, keccak256}; use std::str::FromStr; #[test] fn can_parse_wallet_sign_message() { let args = WalletSubcommands::parse_from(["foundry-cli", "sign", "deadbeef"]); match args { WalletSubcommands::Sign { message, data, from_file, .. } => { assert_eq!(message, "deadbeef".to_string()); assert!(!data); assert!(!from_file); } _ => panic!("expected WalletSubcommands::Sign"), } } #[test] fn can_parse_wallet_sign_hex_message() { let args = WalletSubcommands::parse_from(["foundry-cli", "sign", "0xdeadbeef"]); match args { WalletSubcommands::Sign { message, data, from_file, .. } => { assert_eq!(message, "0xdeadbeef".to_string()); assert!(!data); assert!(!from_file); } _ => panic!("expected WalletSubcommands::Sign"), } } #[test] fn can_verify_signed_hex_message() { let message = "hello"; let signature = Signature::from_str("f2dd00eac33840c04b6fc8a5ec8c4a47eff63575c2bc7312ecb269383de0c668045309c423484c8d097df306e690c653f8e1ec92f7f6f45d1f517027771c3e801c").unwrap(); let address = address!("0x28A4F420a619974a2393365BCe5a7b560078Cc13"); let recovered_address = WalletSubcommands::recover_address_from_message(message, &signature); assert!(recovered_address.is_ok()); assert_eq!(address, recovered_address.unwrap()); } #[test] fn can_verify_signed_hex_message_no_hash() { let prehash = keccak256("hello"); let signature = Signature::from_str("433ec3d37e4f1253df15e2dea412fed8e915737730f74b3dfb1353268f932ef5557c9158e0b34bce39de28d11797b42e9b1acb2749230885fe075aedc3e491a41b").unwrap(); let address = address!("0x7E5F4552091A69125d5DfCb7b8C2659029395Bdf"); // private key = 1 let recovered_address = WalletSubcommands::recover_address_from_message_no_hash(&prehash, &signature); assert!(recovered_address.is_ok()); assert_eq!(address, recovered_address.unwrap()); } #[test] fn can_verify_signed_typed_data() { let typed_data: TypedData = serde_json::from_str(r#"{"domain":{"name":"Test","version":"1","chainId":1,"verifyingContract":"0xDeaDbeefdEAdbeefdEadbEEFdeadbeEFdEaDbeeF"},"message":{"value":123},"primaryType":"Data","types":{"Data":[{"name":"value","type":"uint256"}]}}"#).unwrap(); let signature = Signature::from_str("0285ff83b93bd01c14e201943af7454fe2bc6c98be707a73888c397d6ae3b0b92f73ca559f81cbb19fe4e0f1dc4105bd7b647c6a84b033057977cf2ec982daf71b").unwrap(); let address = address!("0x7E5F4552091A69125d5DfCb7b8C2659029395Bdf"); // private key = 1 let recovered_address = WalletSubcommands::recover_address_from_typed_data(&typed_data, &signature); assert!(recovered_address.is_ok()); assert_eq!(address, recovered_address.unwrap()); } #[test] fn can_parse_wallet_sign_data() { let args = WalletSubcommands::parse_from(["foundry-cli", "sign", "--data", "{ ... }"]); match args { WalletSubcommands::Sign { message, data, from_file, .. } => { assert_eq!(message, "{ ... }".to_string()); assert!(data); assert!(!from_file); } _ => panic!("expected WalletSubcommands::Sign"), } } #[test] fn can_parse_wallet_sign_data_file() { let args = WalletSubcommands::parse_from([ "foundry-cli", "sign", "--data", "--from-file", "tests/data/typed_data.json", ]); match args { WalletSubcommands::Sign { message, data, from_file, .. } => { assert_eq!(message, "tests/data/typed_data.json".to_string()); assert!(data); assert!(from_file); } _ => panic!("expected WalletSubcommands::Sign"), } } #[test] fn can_parse_wallet_change_password() { let args = WalletSubcommands::parse_from([ "foundry-cli", "change-password", "my_account", "--unsafe-password", "old_password", "--unsafe-new-password", "new_password", ]); match args { WalletSubcommands::ChangePassword { account_name, keystore_dir, unsafe_password, unsafe_new_password, } => { assert_eq!(account_name, "my_account".to_string()); assert_eq!(unsafe_password, Some("old_password".to_string())); assert_eq!(unsafe_new_password, Some("new_password".to_string())); assert!(keystore_dir.is_none()); } _ => panic!("expected WalletSubcommands::ChangePassword"), } } #[test] fn wallet_sign_auth_nonce_and_self_broadcast_conflict() { let result = WalletSubcommands::try_parse_from([ "foundry-cli", "sign-auth", "0xDeaDbeefdEAdbeefdEadbEEFdeadbeEFdEaDbeeF", "--nonce", "42", "--self-broadcast", ]); assert!( result.is_err(), "expected error when both --nonce and --self-broadcast are provided" ); } } ================================================ FILE: crates/cast/src/cmd/wallet/vanity.rs ================================================ use alloy_primitives::{Address, hex}; use alloy_signer::{k256::ecdsa::SigningKey, utils::secret_key_to_address}; use alloy_signer_local::PrivateKeySigner; use clap::Parser; use eyre::Result; use foundry_common::sh_println; use itertools::Either; use rayon::iter::{self, ParallelIterator}; use regex::Regex; use serde::{Deserialize, Serialize}; use std::{ fs, path::{Path, PathBuf}, time::Instant, }; /// Type alias for the result of [generate_wallet]. pub type GeneratedWallet = (SigningKey, Address); /// CLI arguments for `cast wallet vanity`. #[derive(Clone, Debug, Parser)] pub struct VanityArgs { /// Prefix regex pattern or hex string. #[arg(long, value_name = "PATTERN", required_unless_present = "ends_with")] pub starts_with: Option, /// Suffix regex pattern or hex string. #[arg(long, value_name = "PATTERN")] pub ends_with: Option, // 2^64-1 is max possible nonce per [eip-2681](https://eips.ethereum.org/EIPS/eip-2681). /// Generate a vanity contract address created by the generated keypair with the specified /// nonce. #[arg(long)] pub nonce: Option, /// Path to save the generated vanity contract address to. /// /// If provided, the generated vanity addresses will appended to a JSON array in the specified /// file. #[arg( long, value_hint = clap::ValueHint::FilePath, value_name = "PATH", )] pub save_path: Option, } /// WalletData contains address and private_key information for a wallet. #[derive(Serialize, Deserialize)] struct WalletData { address: String, private_key: String, } /// Wallets is a collection of WalletData. #[derive(Default, Serialize, Deserialize)] struct Wallets { wallets: Vec, } impl WalletData { pub fn new(wallet: &PrivateKeySigner) -> Self { Self { address: wallet.address().to_checksum(None), private_key: format!("0x{}", hex::encode(wallet.credential().to_bytes())), } } } impl VanityArgs { pub fn run(self) -> Result { let Self { starts_with, ends_with, nonce, save_path } = self; let mut left_exact_hex = None; let mut left_regex = None; if let Some(prefix) = starts_with { match parse_pattern(&prefix, true)? { Either::Left(left) => left_exact_hex = Some(left), Either::Right(re) => left_regex = Some(re), } } let mut right_exact_hex = None; let mut right_regex = None; if let Some(suffix) = ends_with { match parse_pattern(&suffix, false)? { Either::Left(right) => right_exact_hex = Some(right), Either::Right(re) => right_regex = Some(re), } } macro_rules! find_vanity { ($m:ident, $nonce:ident) => { if let Some(nonce) = $nonce { find_vanity_address_with_nonce($m, nonce) } else { find_vanity_address($m) } }; } sh_println!("Starting to generate vanity address...")?; let timer = Instant::now(); let wallet = match (left_exact_hex, left_regex, right_exact_hex, right_regex) { (Some(left), _, Some(right), _) => { let matcher = HexMatcher { left, right }; find_vanity!(matcher, nonce) } (Some(left), _, _, Some(right)) => { let matcher = LeftExactRightRegexMatcher { left, right }; find_vanity!(matcher, nonce) } (_, Some(left), _, Some(right)) => { let matcher = RegexMatcher { left, right }; find_vanity!(matcher, nonce) } (_, Some(left), Some(right), _) => { let matcher = LeftRegexRightExactMatcher { left, right }; find_vanity!(matcher, nonce) } (Some(left), None, None, None) => { let matcher = LeftHexMatcher { left }; find_vanity!(matcher, nonce) } (None, None, Some(right), None) => { let matcher = RightHexMatcher { right }; find_vanity!(matcher, nonce) } (None, Some(re), None, None) => { let matcher = SingleRegexMatcher { re }; find_vanity!(matcher, nonce) } (None, None, None, Some(re)) => { let matcher = SingleRegexMatcher { re }; find_vanity!(matcher, nonce) } _ => unreachable!(), } .expect("failed to generate vanity wallet"); // If a save path is provided, save the generated vanity wallet to the specified path. if let Some(save_path) = save_path { save_wallet_to_file(&wallet, &save_path)?; } sh_println!( "Successfully found vanity address in {:.3} seconds.{}{}\nAddress: {}\nPrivate Key: 0x{}", timer.elapsed().as_secs_f64(), if nonce.is_some() { "\nContract address: " } else { "" }, if let Some(nonce_val) = nonce { wallet.address().create(nonce_val).to_checksum(None) } else { String::new() }, wallet.address().to_checksum(None), hex::encode(wallet.credential().to_bytes()), )?; Ok(wallet) } } /// Saves the specified `wallet` to a 'vanity_addresses.json' file at the given `save_path`. /// If the file exists, the wallet data is appended to the existing content; /// otherwise, a new file is created. fn save_wallet_to_file(wallet: &PrivateKeySigner, path: &Path) -> Result<()> { let mut wallets = if path.exists() { let data = fs::read_to_string(path)?; serde_json::from_str::(&data).unwrap_or_default() } else { Wallets::default() }; wallets.wallets.push(WalletData::new(wallet)); fs::write(path, serde_json::to_string_pretty(&wallets)?)?; Ok(()) } /// Generates random wallets until `matcher` matches the wallet address, returning the wallet. pub fn find_vanity_address(matcher: T) -> Option { wallet_generator().find_any(create_matcher(matcher)).map(|(key, _)| key.into()) } /// Generates random wallets until `matcher` matches the contract address created at `nonce`, /// returning the wallet. pub fn find_vanity_address_with_nonce( matcher: T, nonce: u64, ) -> Option { wallet_generator().find_any(create_nonce_matcher(matcher, nonce)).map(|(key, _)| key.into()) } /// Creates a matcher function, which takes a reference to a [GeneratedWallet] and returns /// whether it found a match or not by using `matcher`. #[inline] pub fn create_matcher(matcher: T) -> impl Fn(&GeneratedWallet) -> bool { move |(_, addr)| matcher.is_match(addr) } /// Creates a contract address matcher function that uses the specified nonce. /// The returned function takes a reference to a [GeneratedWallet] and returns /// whether the contract address created with the nonce matches using `matcher`. #[inline] pub fn create_nonce_matcher( matcher: T, nonce: u64, ) -> impl Fn(&GeneratedWallet) -> bool { move |(_, addr)| { let contract_addr = addr.create(nonce); matcher.is_match(&contract_addr) } } /// Returns an infinite parallel iterator which yields a [GeneratedWallet]. #[inline] pub fn wallet_generator() -> iter::Map, impl Fn(()) -> GeneratedWallet> { iter::repeat(()).map(|()| generate_wallet()) } /// Generates a random K-256 signing key and derives its Ethereum address. pub fn generate_wallet() -> GeneratedWallet { let key = SigningKey::random(&mut rand_08::thread_rng()); let address = secret_key_to_address(&key); (key, address) } /// A trait to match vanity addresses. pub trait VanityMatcher: Send + Sync { fn is_match(&self, addr: &Address) -> bool; } /// Matches start and end hex. pub struct HexMatcher { pub left: Vec, pub right: Vec, } impl VanityMatcher for HexMatcher { #[inline] fn is_match(&self, addr: &Address) -> bool { let bytes = addr.as_slice(); bytes.starts_with(&self.left) && bytes.ends_with(&self.right) } } /// Matches only start hex. pub struct LeftHexMatcher { pub left: Vec, } impl VanityMatcher for LeftHexMatcher { #[inline] fn is_match(&self, addr: &Address) -> bool { let bytes = addr.as_slice(); bytes.starts_with(&self.left) } } /// Matches only end hex. pub struct RightHexMatcher { pub right: Vec, } impl VanityMatcher for RightHexMatcher { #[inline] fn is_match(&self, addr: &Address) -> bool { let bytes = addr.as_slice(); bytes.ends_with(&self.right) } } /// Matches start hex and end regex. pub struct LeftExactRightRegexMatcher { pub left: Vec, pub right: Regex, } impl VanityMatcher for LeftExactRightRegexMatcher { #[inline] fn is_match(&self, addr: &Address) -> bool { let bytes = addr.as_slice(); bytes.starts_with(&self.left) && self.right.is_match(&hex::encode(bytes)) } } /// Matches start regex and end hex. pub struct LeftRegexRightExactMatcher { pub left: Regex, pub right: Vec, } impl VanityMatcher for LeftRegexRightExactMatcher { #[inline] fn is_match(&self, addr: &Address) -> bool { let bytes = addr.as_slice(); bytes.ends_with(&self.right) && self.left.is_match(&hex::encode(bytes)) } } /// Matches a single regex. pub struct SingleRegexMatcher { pub re: Regex, } impl VanityMatcher for SingleRegexMatcher { #[inline] fn is_match(&self, addr: &Address) -> bool { let addr = hex::encode(addr); self.re.is_match(&addr) } } /// Matches start and end regex. pub struct RegexMatcher { pub left: Regex, pub right: Regex, } impl VanityMatcher for RegexMatcher { #[inline] fn is_match(&self, addr: &Address) -> bool { let addr = hex::encode(addr); self.left.is_match(&addr) && self.right.is_match(&addr) } } fn parse_pattern(pattern: &str, is_start: bool) -> Result, Regex>> { if let Ok(decoded) = hex::decode(pattern) { if decoded.len() > 20 { return Err(eyre::eyre!("Hex pattern must be less than 20 bytes")); } Ok(Either::Left(decoded)) } else { let (prefix, suffix) = if is_start { ("^", "") } else { ("", "$") }; Ok(Either::Right(Regex::new(&format!("{prefix}{pattern}{suffix}"))?)) } } #[cfg(test)] mod tests { use super::*; #[test] fn find_simple_vanity_start() { let args: VanityArgs = VanityArgs::parse_from(["foundry-cli", "--starts-with", "00"]); let wallet = args.run().unwrap(); let addr = wallet.address(); let addr = format!("{addr:x}"); assert!(addr.starts_with("00")); } #[test] fn find_simple_vanity_start2() { let args: VanityArgs = VanityArgs::parse_from(["foundry-cli", "--starts-with", "9"]); let wallet = args.run().unwrap(); let addr = wallet.address(); let addr = format!("{addr:x}"); assert!(addr.starts_with('9')); } #[test] fn find_simple_vanity_end() { let args: VanityArgs = VanityArgs::parse_from(["foundry-cli", "--ends-with", "00"]); let wallet = args.run().unwrap(); let addr = wallet.address(); let addr = format!("{addr:x}"); assert!(addr.ends_with("00")); } #[test] fn save_path() { let tmp = tempfile::NamedTempFile::new().unwrap(); let args: VanityArgs = VanityArgs::parse_from([ "foundry-cli", "--starts-with", "00", "--save-path", tmp.path().to_str().unwrap(), ]); args.run().unwrap(); assert!(tmp.path().exists()); let s = fs::read_to_string(tmp.path()).unwrap(); let wallets: Wallets = serde_json::from_str(&s).unwrap(); assert!(!wallets.wallets.is_empty()); } } ================================================ FILE: crates/cast/src/debug.rs ================================================ use std::str::FromStr; use alloy_chains::Chain; use alloy_primitives::{Address, Bytes, map::HashMap}; use foundry_cli::utils::{TraceResult, print_traces}; use foundry_common::{ContractsByArtifact, compile::ProjectCompiler, shell}; use foundry_config::Config; use foundry_debugger::Debugger; use foundry_evm::traces::{ CallTraceDecoderBuilder, DebugTraceIdentifier, debug::ContractSources, identifier::{SignaturesIdentifier, TraceIdentifiers}, }; /// labels the traces, conditionally prints them or opens the debugger #[expect(clippy::too_many_arguments)] pub(crate) async fn handle_traces( mut result: TraceResult, config: &Config, chain: Chain, contracts_bytecode: &HashMap, labels: Vec, with_local_artifacts: bool, debug: bool, decode_internal: bool, disable_label: bool, trace_depth: Option, ) -> eyre::Result<()> { let (known_contracts, mut sources) = if with_local_artifacts { let _ = sh_println!("Compiling project to generate artifacts"); let project = config.project()?; let compiler = ProjectCompiler::new(); let output = compiler.compile(&project)?; ( Some(ContractsByArtifact::new( output.artifact_ids().map(|(id, artifact)| (id, artifact.clone().into())), )), ContractSources::from_project_output(&output, project.root(), None)?, ) } else { (None, ContractSources::default()) }; let labels = labels.iter().filter_map(|label_str| { let mut iter = label_str.split(':'); if let Some(addr) = iter.next() && let (Ok(address), Some(label)) = (Address::from_str(addr), iter.next()) { return Some((address, label.to_string())); } None }); let config_labels = config.labels.clone().into_iter(); let mut builder = CallTraceDecoderBuilder::new() .with_labels(labels.chain(config_labels)) .with_signature_identifier(SignaturesIdentifier::from_config(config)?) .with_label_disabled(disable_label); let mut identifier = TraceIdentifiers::new().with_external(config, Some(chain))?; if let Some(contracts) = &known_contracts { builder = builder.with_known_contracts(contracts); identifier = identifier.with_local_and_bytecodes(contracts, contracts_bytecode); } let mut decoder = builder.build(); for (_, trace) in result.traces.as_deref_mut().unwrap_or_default() { decoder.identify(trace, &mut identifier); } if decode_internal || debug { if let Some(ref etherscan_identifier) = identifier.external { sources.merge(etherscan_identifier.get_compiled_contracts().await?); } if debug { let mut debugger = Debugger::builder() .traces(result.traces.expect("missing traces")) .decoder(&decoder) .sources(sources) .build(); debugger.try_run_tui()?; return Ok(()); } decoder.debug_identifier = Some(DebugTraceIdentifier::new(sources)); } print_traces( &mut result, &decoder, shell::verbosity() > 0, shell::verbosity() > 4, trace_depth, ) .await?; Ok(()) } ================================================ FILE: crates/cast/src/errors.rs ================================================ //! Errors for this crate use foundry_config::Chain; use std::fmt; /// An error thrown when resolving a function via signature failed #[derive(Clone, Debug)] pub enum FunctionSignatureError { MissingSignature, MissingEtherscan { sig: String }, UnknownChain(Chain), MissingToAddress, } impl fmt::Display for FunctionSignatureError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::MissingSignature => { writeln!(f, "Function signature must be set") } Self::MissingEtherscan { sig } => { writeln!(f, "Failed to determine function signature for `{sig}`")?; writeln!( f, "To lookup a function signature of a deployed contract by name, a valid ETHERSCAN_API_KEY must be set." )?; write!(f, "\tOr did you mean:\t {sig}()") } Self::UnknownChain(chain) => { write!(f, "Resolving via etherscan requires a known chain. Unknown chain: {chain}") } Self::MissingToAddress => f.write_str("Target address must be set"), } } } impl std::error::Error for FunctionSignatureError {} ================================================ FILE: crates/cast/src/lib.rs ================================================ //! Cast is a Swiss Army knife for interacting with Ethereum applications from the command line. #![cfg_attr(not(test), warn(unused_crate_dependencies))] #![cfg_attr(docsrs, feature(doc_cfg))] #[macro_use] extern crate foundry_common; #[macro_use] extern crate tracing; use alloy_consensus::BlockHeader; use alloy_dyn_abi::{DynSolType, DynSolValue, FunctionExt}; use alloy_eips::Encodable2718; use alloy_ens::NameOrAddress; use alloy_json_abi::Function; use alloy_network::{AnyNetwork, BlockResponse, Network, TransactionBuilder}; use alloy_primitives::{ Address, B256, I256, Keccak256, LogData, Selector, TxHash, U64, U256, hex, utils::{ParseUnits, Unit, keccak256}, }; use alloy_provider::{PendingTransactionBuilder, Provider, network::eip2718::Decodable2718}; use alloy_rlp::{Decodable, Encodable}; use alloy_rpc_types::{ BlockId, BlockNumberOrTag, BlockOverrides, Filter, FilterBlockOption, Log, state::StateOverride, }; use base::{Base, NumberWithBase, ToBase}; use chrono::DateTime; use eyre::{Context, ContextCompat, OptionExt, Result}; use foundry_block_explorers::Client; use foundry_common::{ abi::{coerce_value, encode_function_args, encode_function_args_packed, get_event, get_func}, compile::etherscan_project, flatten, fmt::*, fs, shell, }; use foundry_config::Chain; use foundry_evm::core::bytecode::InstIter; use foundry_primitives::FoundryTxEnvelope; use futures::{FutureExt, StreamExt, future::Either}; use rayon::prelude::*; use serde::Serialize; use std::{ borrow::Cow, fmt::Write, io, marker::PhantomData, path::PathBuf, str::FromStr, sync::atomic::{AtomicBool, Ordering}, }; use tokio::signal::ctrl_c; pub use foundry_evm::*; pub mod args; pub mod cmd; pub mod opts; pub mod base; pub(crate) mod debug; pub mod errors; mod rlp_converter; pub mod tx; use rlp_converter::Item; // TODO: CastContract with common contract initializers? Same for CastProviders? pub struct Cast { provider: P, _phantom: PhantomData, } impl + Clone + Unpin, N: Network> Cast { /// Creates a new Cast instance from the provided client /// /// # Example /// /// ``` /// use alloy_provider::{ProviderBuilder, RootProvider, network::AnyNetwork}; /// use cast::Cast; /// /// # async fn foo() -> eyre::Result<()> { /// let provider = /// ProviderBuilder::<_, _, AnyNetwork>::default().connect("http://localhost:8545").await?; /// let cast = Cast::new(provider); /// # Ok(()) /// # } /// ``` pub fn new(provider: P) -> Self { Self { provider, _phantom: PhantomData } } /// Makes a read-only call to the specified address /// /// # Example /// /// ``` /// use alloy_primitives::{Address, U256, Bytes}; /// use alloy_rpc_types::{TransactionRequest, BlockOverrides, state::{StateOverride, AccountOverride}}; /// use alloy_serde::WithOtherFields; /// use cast::Cast; /// use alloy_provider::{RootProvider, ProviderBuilder, network::AnyNetwork}; /// use std::{str::FromStr, collections::HashMap}; /// use alloy_rpc_types::state::StateOverridesBuilder; /// use alloy_sol_types::{sol, SolCall}; /// /// sol!( /// function greeting(uint256 i) public returns (string); /// ); /// /// # async fn foo() -> eyre::Result<()> { /// let alloy_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()); /// let tx = TransactionRequest::default().to(to).input(bytes.into()); /// let tx = WithOtherFields::new(tx); /// /// // Create state overrides /// let mut state_override = StateOverride::default(); /// let mut account_override = AccountOverride::default(); /// 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, Some(state_override_object), Some(block_override_object)).await?; /// println!("{}", data); /// # Ok(()) /// # } /// ``` pub async fn call( &self, req: &N::TransactionRequest, func: Option<&Function>, block: Option, state_override: Option, block_override: Option, ) -> Result { let mut call = self .provider .call(req.clone()) .block(block.unwrap_or_default()) .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()) { Ok(decoded) => decoded, Err(err) => { // ensure the address is a contract if res.is_empty() { // check that the recipient is a contract that can be called if let Some(addr) = req.to() { if let Ok(code) = self .provider .get_code_at(addr) .block_id(block.unwrap_or_default()) .await && code.is_empty() { eyre::bail!("contract {addr:?} does not have any code") } } else if req.to().is_none() { eyre::bail!("tx req is a contract deployment"); } else { eyre::bail!("recipient is None"); } } return Err(err).wrap_err( "could not decode output; did you specify the wrong function return data type?" ); } }; } // handle case when return type is not specified Ok(if decoded.is_empty() { res.to_string() } else if shell::is_json() { let tokens = decoded .into_iter() .map(|value| serialize_value_as_json(value, None)) .collect::>>()?; serde_json::to_string_pretty(&tokens).unwrap() } else { // seth compatible user-friendly return type conversions decoded.iter().map(format_token).collect::>().join("\n") }) } /// Generates an access list for the specified transaction /// /// # Example /// /// ``` /// use cast::{Cast}; /// use alloy_primitives::{Address, U256, Bytes}; /// use alloy_rpc_types::{TransactionRequest}; /// use alloy_serde::WithOtherFields; /// use alloy_provider::{RootProvider, ProviderBuilder, network::AnyNetwork}; /// use std::str::FromStr; /// use alloy_sol_types::{sol, SolCall}; /// /// sol!( /// function greeting(uint256 i) public returns (string); /// ); /// /// # async fn foo() -> eyre::Result<()> { /// 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()); /// let tx = TransactionRequest::default().to(to).input(bytes.into()); /// let tx = WithOtherFields::new(tx); /// let cast = Cast::new(&provider); /// let access_list = cast.access_list(&tx, None).await?; /// println!("{}", access_list); /// # Ok(()) /// # } /// ``` pub async fn access_list( &self, req: &N::TransactionRequest, block: Option, ) -> Result { let access_list = self.provider.create_access_list(req).block_id(block.unwrap_or_default()).await?; let res = if shell::is_json() { serde_json::to_string(&access_list)? } else { let mut s = vec![format!("gas used: {}", access_list.gas_used), "access list:".to_string()]; for al in access_list.access_list.0 { s.push(format!("- address: {}", &al.address.to_checksum(None))); if !al.storage_keys.is_empty() { s.push(" keys:".to_string()); for key in al.storage_keys { s.push(format!(" {key:?}")); } } } s.join("\n") }; Ok(res) } pub async fn balance(&self, who: Address, block: Option) -> Result { Ok(self.provider.get_balance(who).block_id(block.unwrap_or_default()).await?) } /// Publishes a raw transaction to the network /// /// # Example /// /// ``` /// use alloy_provider::{ProviderBuilder, RootProvider, network::AnyNetwork}; /// use cast::Cast; /// /// # async fn foo() -> eyre::Result<()> { /// let provider = /// ProviderBuilder::<_, _, AnyNetwork>::default().connect("http://localhost:8545").await?; /// let cast = Cast::new(provider); /// let res = cast.publish("0x1234".to_string()).await?; /// println!("{:?}", res); /// # Ok(()) /// # } /// ``` pub async fn publish(&self, raw_tx: String) -> Result> { let tx = hex::decode(strip_0x(&raw_tx))?; let res = self.provider.send_raw_transaction(&tx).await?; Ok(res) } pub async fn chain_id(&self) -> Result { Ok(self.provider.get_chain_id().await?) } pub async fn block_number(&self) -> Result { Ok(self.provider.get_block_number().await?) } pub async fn gas_price(&self) -> Result { Ok(self.provider.get_gas_price().await?) } /// # Example /// /// ``` /// use alloy_primitives::Address; /// use alloy_provider::{ProviderBuilder, RootProvider, network::AnyNetwork}; /// use cast::Cast; /// use std::str::FromStr; /// /// # async fn foo() -> eyre::Result<()> { /// let provider = /// 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?; /// println!("{}", nonce); /// # Ok(()) /// # } /// ``` pub async fn nonce(&self, who: Address, block: Option) -> Result { Ok(self.provider.get_transaction_count(who).block_id(block.unwrap_or_default()).await?) } /// #Example /// /// ``` /// use alloy_primitives::{Address, FixedBytes}; /// use alloy_provider::{network::AnyNetwork, ProviderBuilder, RootProvider}; /// use cast::Cast; /// use std::str::FromStr; /// /// # async fn foo() -> eyre::Result<()> { /// let provider = /// 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")?]; /// let codehash = cast.codehash(addr, slots, None).await?; /// println!("{}", codehash); /// # Ok(()) /// # } pub async fn codehash( &self, who: Address, slots: Vec, block: Option, ) -> Result { Ok(self .provider .get_proof(who, slots) .block_id(block.unwrap_or_default()) .await? .code_hash .to_string()) } /// #Example /// /// ``` /// use alloy_primitives::{Address, FixedBytes}; /// use alloy_provider::{network::AnyNetwork, ProviderBuilder, RootProvider}; /// use cast::Cast; /// use std::str::FromStr; /// /// # async fn foo() -> eyre::Result<()> { /// let provider = /// 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")?]; /// let storage_root = cast.storage_root(addr, slots, None).await?; /// println!("{}", storage_root); /// # Ok(()) /// # } pub async fn storage_root( &self, who: Address, slots: Vec, block: Option, ) -> Result { Ok(self .provider .get_proof(who, slots) .block_id(block.unwrap_or_default()) .await? .storage_hash .to_string()) } /// # Example /// /// ``` /// use alloy_primitives::Address; /// use alloy_provider::{ProviderBuilder, RootProvider, network::AnyNetwork}; /// use cast::Cast; /// use std::str::FromStr; /// /// # async fn foo() -> eyre::Result<()> { /// let provider = /// 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?; /// println!("{}", implementation); /// # Ok(()) /// # } /// ``` pub async fn implementation( &self, who: Address, is_beacon: bool, block: Option, ) -> Result { let slot = match is_beacon { true => { // Use the beacon slot : bytes32(uint256(keccak256('eip1967.proxy.beacon')) - 1) B256::from_str( "0xa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b35133d50", )? } false => { // Use the implementation slot : // bytes32(uint256(keccak256('eip1967.proxy.implementation')) - 1) B256::from_str( "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc", )? } }; let value = self .provider .get_storage_at(who, slot.into()) .block_id(block.unwrap_or_default()) .await?; let addr = Address::from_word(value.into()); Ok(format!("{addr:?}")) } /// # Example /// /// ``` /// use alloy_primitives::Address; /// use alloy_provider::{ProviderBuilder, RootProvider, network::AnyNetwork}; /// use cast::Cast; /// use std::str::FromStr; /// /// # async fn foo() -> eyre::Result<()> { /// let provider = /// 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?; /// println!("{}", admin); /// # Ok(()) /// # } /// ``` pub async fn admin(&self, who: Address, block: Option) -> Result { let slot = B256::from_str("0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103")?; let value = self .provider .get_storage_at(who, slot.into()) .block_id(block.unwrap_or_default()) .await?; let addr = Address::from_word(value.into()); Ok(format!("{addr:?}")) } /// # Example /// /// ``` /// use alloy_primitives::{Address, U256}; /// use alloy_provider::{ProviderBuilder, RootProvider, network::AnyNetwork}; /// use cast::Cast; /// use std::str::FromStr; /// /// # async fn foo() -> eyre::Result<()> { /// let provider = /// 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?; /// println!("Computed address for address {addr}: {computed_address}"); /// # Ok(()) /// # } /// ``` pub async fn compute_address(&self, address: Address, nonce: Option) -> Result
{ let unpacked = if let Some(n) = nonce { n } else { self.nonce(address, None).await? }; Ok(address.create(unpacked)) } /// # Example /// /// ``` /// use alloy_primitives::Address; /// use alloy_provider::{ProviderBuilder, RootProvider, network::AnyNetwork}; /// use cast::Cast; /// use std::str::FromStr; /// /// # async fn foo() -> eyre::Result<()> { /// let provider = /// 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?; /// println!("{}", code); /// # Ok(()) /// # } /// ``` pub async fn code( &self, who: Address, block: Option, disassemble: bool, ) -> Result { if disassemble { let code = self.provider.get_code_at(who).block_id(block.unwrap_or_default()).await?.to_vec(); SimpleCast::disassemble(&code) } else { Ok(format!( "{}", self.provider.get_code_at(who).block_id(block.unwrap_or_default()).await? )) } } /// Example /// /// ``` /// use alloy_primitives::Address; /// use alloy_provider::{ProviderBuilder, RootProvider, network::AnyNetwork}; /// use cast::Cast; /// use std::str::FromStr; /// /// # async fn foo() -> eyre::Result<()> { /// let provider = /// 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?; /// println!("{}", codesize); /// # Ok(()) /// # } /// ``` pub async fn codesize(&self, who: Address, block: Option) -> Result { let code = self.provider.get_code_at(who).block_id(block.unwrap_or_default()).await?.to_vec(); Ok(code.len().to_string()) } /// Perform a raw JSON-RPC request /// /// # Example /// /// ``` /// use alloy_provider::{ProviderBuilder, RootProvider, network::AnyNetwork}; /// use cast::Cast; /// /// # async fn foo() -> eyre::Result<()> { /// let provider = /// ProviderBuilder::<_, _, AnyNetwork>::default().connect("http://localhost:8545").await?; /// let cast = Cast::new(provider); /// let result = cast /// .rpc("eth_getBalance", &["0xc94770007dda54cF92009BFF0dE90c06F603a09f", "latest"]) /// .await?; /// println!("{}", result); /// # Ok(()) /// # } /// ``` pub async fn rpc(&self, method: &str, params: V) -> Result where V: alloy_json_rpc::RpcSend, { let res = self .provider .raw_request::(Cow::Owned(method.to_string()), params) .await?; Ok(serde_json::to_string(&res)?) } /// Returns the slot /// /// # Example /// /// ``` /// use alloy_primitives::{Address, B256}; /// use alloy_provider::{ProviderBuilder, RootProvider, network::AnyNetwork}; /// use cast::Cast; /// use std::str::FromStr; /// /// # async fn foo() -> eyre::Result<()> { /// let provider = /// ProviderBuilder::<_, _, AnyNetwork>::default().connect("http://localhost:8545").await?; /// let cast = Cast::new(provider); /// let addr = Address::from_str("0x00000000006c3852cbEf3e08E8dF289169EdE581")?; /// let slot = B256::ZERO; /// let storage = cast.storage(addr, slot, None).await?; /// println!("{}", storage); /// # Ok(()) /// # } /// ``` pub async fn storage( &self, from: Address, slot: B256, block: Option, ) -> Result { Ok(format!( "{:?}", B256::from( self.provider .get_storage_at(from, slot.into()) .block_id(block.unwrap_or_default()) .await? ) )) } pub async fn filter_logs(&self, filter: Filter) -> Result { let logs = self.provider.get_logs(&filter).await?; Self::format_logs(logs) } /// Retrieves logs using chunked requests to handle large block ranges. /// /// Automatically divides large block ranges into smaller chunks to avoid provider limits /// and processes them with controlled concurrency to prevent rate limiting. pub async fn filter_logs_chunked(&self, filter: Filter, chunk_size: u64) -> Result { let logs = self.get_logs_chunked(&filter, chunk_size).await?; Self::format_logs(logs) } fn format_logs(logs: Vec) -> Result { let res = if shell::is_json() { serde_json::to_string(&logs)? } else { let mut s = vec![]; for log in logs { let pretty = log .pretty() .replacen('\n', "- ", 1) // Remove empty first line .replace('\n', "\n "); // Indent s.push(pretty); } s.join("\n") }; Ok(res) } fn extract_block_range(filter: &Filter) -> (Option, Option) { let FilterBlockOption::Range { from_block, to_block } = &filter.block_option else { return (None, None); }; (from_block.and_then(|b| b.as_number()), to_block.and_then(|b| b.as_number())) } /// Retrieves logs with automatic chunking fallback. /// /// First tries to fetch logs for the entire range. If that fails, /// falls back to concurrent chunked requests with rate limiting. async fn get_logs_chunked(&self, filter: &Filter, chunk_size: u64) -> Result> where P: Clone + Unpin, { // Try the full range first if let Ok(logs) = self.provider.get_logs(filter).await { return Ok(logs); } // Fallback: use concurrent chunked approach self.get_logs_chunked_concurrent(filter, chunk_size).await } /// Retrieves logs using concurrent chunked requests with rate limiting. /// /// Divides the block range into chunks and processes them with a maximum of /// 5 concurrent requests. Falls back to single-block queries if chunks fail. async fn get_logs_chunked_concurrent( &self, filter: &Filter, chunk_size: u64, ) -> Result> where P: Clone + Unpin, { let (from_block, to_block) = Self::extract_block_range(filter); let (Some(from), Some(to)) = (from_block, to_block) else { return self.provider.get_logs(filter).await.map_err(Into::into); }; if from >= to { return Ok(vec![]); } // Create chunk ranges using iterator let chunk_ranges: Vec<(u64, u64)> = (from..to) .step_by(chunk_size as usize) .map(|chunk_start| (chunk_start, (chunk_start + chunk_size).min(to))) .collect(); // Process chunks with controlled concurrency using buffered stream let mut all_results: Vec<(u64, Vec)> = futures::stream::iter(chunk_ranges) .map(|(start_block, chunk_end)| { let chunk_filter = filter.clone().from_block(start_block).to_block(chunk_end - 1); let provider = self.provider.clone(); async move { // Try direct chunk request with simplified fallback match provider.get_logs(&chunk_filter).await { Ok(logs) => (start_block, logs), Err(_) => { // Simple fallback: try individual blocks in this chunk let mut fallback_logs = Vec::new(); for single_block in start_block..chunk_end { let single_filter = chunk_filter .clone() .from_block(single_block) .to_block(single_block); if let Ok(logs) = provider.get_logs(&single_filter).await { fallback_logs.extend(logs); } } (start_block, fallback_logs) } } } }) .buffered(5) // Limit to 5 concurrent requests to avoid rate limits .collect() .await; // Sort once at the end by block number and flatten all_results.sort_by_key(|(block_num, _)| *block_num); let mut all_logs = Vec::new(); for (_, logs) in all_results { all_logs.extend(logs); } Ok(all_logs) } /// Converts a block identifier into a block number. /// /// If the block identifier is a block number, then this function returns the block number. If /// the block identifier is a block hash, then this function returns the block number of /// that block hash. If the block identifier is `None`, then this function returns `None`. /// /// # Example /// /// ``` /// use alloy_primitives::fixed_bytes; /// use alloy_provider::{ProviderBuilder, RootProvider, network::AnyNetwork}; /// use alloy_rpc_types::{BlockId, BlockNumberOrTag}; /// use cast::Cast; /// use std::{convert::TryFrom, str::FromStr}; /// /// # async fn foo() -> eyre::Result<()> { /// let provider = /// 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?; /// assert_eq!(block_number, Some(BlockNumberOrTag::Number(5))); /// /// let block_number = cast /// .convert_block_number(Some(BlockId::hash(fixed_bytes!( /// "0000000000000000000000000000000000000000000000000000000000001234" /// )))) /// .await?; /// assert_eq!(block_number, Some(BlockNumberOrTag::Number(4660))); /// /// let block_number = cast.convert_block_number(None).await?; /// assert_eq!(block_number, None); /// # Ok(()) /// # } /// ``` pub async fn convert_block_number( &self, block: Option, ) -> Result, eyre::Error> { match block { Some(block) => match block { BlockId::Number(block_number) => Ok(Some(block_number)), BlockId::Hash(hash) => { let block = self.provider.get_block_by_hash(hash.block_hash).await?; Ok(block.map(|block| block.header().number()).map(BlockNumberOrTag::from)) } }, None => Ok(None), } } /// Sets up a subscription to the given filter and writes the logs to the given output. /// /// # Example /// /// ``` /// use alloy_primitives::Address; /// use alloy_provider::{ProviderBuilder, RootProvider, network::AnyNetwork}; /// use alloy_rpc_types::Filter; /// use alloy_transport::BoxTransport; /// use cast::Cast; /// use std::{io, str::FromStr}; /// /// # async fn foo() -> eyre::Result<()> { /// let provider = /// ProviderBuilder::<_, _, AnyNetwork>::default().connect("wss://localhost:8545").await?; /// let cast = Cast::new(provider); /// /// let filter = /// Filter::new().address(Address::from_str("0x00000000006c3852cbEf3e08E8dF289169EdE581")?); /// let mut output = io::stdout(); /// cast.subscribe(filter, &mut output).await?; /// # Ok(()) /// # } /// ``` pub async fn subscribe(&self, filter: Filter, output: &mut dyn io::Write) -> Result<()> { // Initialize the subscription stream for logs let mut subscription = self.provider.subscribe_logs(&filter).await?.into_stream(); // Check if a to_block is specified, if so, subscribe to blocks let mut block_subscription = if filter.get_to_block().is_some() { Some(self.provider.subscribe_blocks().await?.into_stream()) } else { None }; let format_json = shell::is_json(); let to_block_number = filter.get_to_block(); // If output should be JSON, start with an opening bracket if format_json { write!(output, "[")?; } let mut first = true; loop { tokio::select! { // If block subscription is present, listen to it to avoid blocking indefinitely past the desired to_block block = if let Some(bs) = &mut block_subscription { Either::Left(bs.next().fuse()) } else { Either::Right(futures::future::pending()) } => { if let (Some(block), Some(to_block)) = (block, to_block_number) && block.number() > to_block { break; } }, // Process incoming log log = subscription.next() => { if format_json { if !first { write!(output, ",")?; } first = false; let log_str = serde_json::to_string(&log).unwrap(); write!(output, "{log_str}")?; } else { let log_str = log.pretty() .replacen('\n', "- ", 1) // Remove empty first line .replace('\n', "\n "); // Indent writeln!(output, "{log_str}")?; } }, // Break on cancel signal, to allow for closing JSON bracket _ = ctrl_c() => { break; }, else => break, } } // If output was JSON, end with a closing bracket if format_json { write!(output, "]")?; } Ok(()) } } impl, N: Network> Cast where N::HeaderResponse: UIfmtHeaderExt, N::BlockResponse: UIfmt, { /// # Example /// /// ``` /// use alloy_provider::{ProviderBuilder, RootProvider, network::AnyNetwork}; /// use cast::Cast; /// /// # async fn foo() -> eyre::Result<()> { /// let provider = /// ProviderBuilder::<_, _, AnyNetwork>::default().connect("http://localhost:8545").await?; /// let cast = Cast::new(provider); /// let block = cast.block(5, true, vec![]).await?; /// println!("{}", block); /// # Ok(()) /// # } /// ``` pub async fn block>( &self, block: B, full: bool, fields: Vec, ) -> Result { let block = block.into(); if fields.contains(&"transactions".into()) && !full { eyre::bail!("use --full to view transactions") } let block = self .provider .get_block(block) .kind(full.into()) .await? .ok_or_else(|| eyre::eyre!("block {:?} not found", block))?; Ok(if !fields.is_empty() { let mut result = String::new(); for field in fields { result.push_str( &get_pretty_block_attr::(&block, &field) .unwrap_or_else(|| format!("{field} is not a valid block field")), ); result.push('\n'); } result.trim_end().to_string() } else if shell::is_json() { serde_json::to_value(&block).unwrap().to_string() } else { block.pretty() }) } async fn block_field_as_num>(&self, block: B, field: String) -> Result { Self::block( self, block.into(), false, // Select only select field vec![field], ) .await? .parse() .map_err(Into::into) } pub async fn base_fee>(&self, block: B) -> Result { Self::block_field_as_num(self, block, String::from("baseFeePerGas")).await } pub async fn age>(&self, block: B) -> Result { let timestamp_str = Self::block_field_as_num(self, block, String::from("timestamp")).await?.to_string(); let datetime = DateTime::from_timestamp(timestamp_str.parse::().unwrap(), 0).unwrap(); Ok(datetime.format("%a %b %e %H:%M:%S %Y").to_string()) } pub async fn timestamp>(&self, block: B) -> Result { Self::block_field_as_num(self, block, "timestamp".to_string()).await } pub async fn chain(&self) -> Result<&str> { let genesis_hash = Self::block( self, 0, false, // Select only block hash vec![String::from("hash")], ) .await?; Ok(match &genesis_hash[..] { "0xd4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3" => { match &(Self::block(self, 1920000, false, vec![String::from("hash")]).await?)[..] { "0x94365e3a8c0b35089c1d1195081fe7489b528a84b22199c916180db8b28ade7f" => { "etclive" } _ => "ethlive", } } "0xa3c565fc15c7478862d50ccd6561e3c06b24cc509bf388941c25ea985ce32cb9" => "kovan", "0x41941023680923e0fe4d74a34bdac8141f2540e3ae90623718e47d66d1ca4a2d" => "ropsten", "0x7ca38a1916c42007829c55e69d3e9a73265554b586a499015373241b8a3fa48b" => { "optimism-mainnet" } "0xc1fc15cd51159b1f1e5cbc4b82e85c1447ddfa33c52cf1d98d14fba0d6354be1" => { "optimism-goerli" } "0x02adc9b449ff5f2467b8c674ece7ff9b21319d76c4ad62a67a70d552655927e5" => { "optimism-kovan" } "0x521982bd54239dc71269eefb58601762cc15cfb2978e0becb46af7962ed6bfaa" => "fraxtal", "0x910f5c4084b63fd860d0c2f9a04615115a5a991254700b39ba072290dbd77489" => { "fraxtal-testnet" } "0x7ee576b35482195fc49205cec9af72ce14f003b9ae69f6ba0faef4514be8b442" => { "arbitrum-mainnet" } "0x0cd786a2425d16f152c658316c423e6ce1181e15c3295826d7c9904cba9ce303" => "morden", "0x6341fd3daf94b748c72ced5a5b26028f2474f5f00d824504e4fa37a75767e177" => "rinkeby", "0xbf7e331f7f7c1dd2e05159666b3bf8bc7a8a3a9eb1d518969eab529dd9b88c1a" => "goerli", "0x14c2283285a88fe5fce9bf5c573ab03d6616695d717b12a127188bcacfc743c4" => "kotti", "0xa9c28ce2141b56c474f1dc504bee9b01eb1bd7d1a507580d5519d4437a97de1b" => "polygon-pos", "0x7202b2b53c5a0836e773e319d18922cc756dd67432f9a1f65352b61f4406c697" => { "polygon-pos-amoy-testnet" } "0x81005434635456a16f74ff7023fbe0bf423abbc8a8deb093ffff455c0ad3b741" => "polygon-zkevm", "0x676c1a76a6c5855a32bdf7c61977a0d1510088a4eeac1330466453b3d08b60b9" => { "polygon-zkevm-cardona-testnet" } "0x4f1dd23188aab3a76b463e4af801b52b1248ef073c648cbdc4c9333d3da79756" => "gnosis", "0xada44fd8d2ecab8b08f256af07ad3e777f17fb434f8f8e678b312f576212ba9a" => "chiado", "0x6d3c66c5357ec91d5c43af47e234a939b22557cbb552dc45bebbceeed90fbe34" => "bsctest", "0x0d21840abff46b96c84b2ac9e10e4f5cdaeb5693cb665db62a2f3b02d2d57b5b" => "bsc", "0x31ced5b9beb7f8782b014660da0cb18cc409f121f408186886e1ca3e8eeca96b" => { match &(Self::block(self, 1, false, vec![String::from("hash")]).await?)[..] { "0x738639479dc82d199365626f90caa82f7eafcfe9ed354b456fb3d294597ceb53" => { "avalanche-fuji" } _ => "avalanche", } } "0x23a2658170ba70d014ba0d0d2709f8fbfe2fa660cd868c5f282f991eecbe38ee" => "ink", "0xe5fd5cf0be56af58ad5751b401410d6b7a09d830fa459789746a3d0dd1c79834" => "ink-sepolia", _ => "unknown", }) } } impl, N: Network> Cast where N::Header: Encodable, { /// # Example /// /// ``` /// use alloy_provider::{ProviderBuilder, RootProvider, network::Ethereum}; /// use cast::Cast; /// /// # async fn foo() -> eyre::Result<()> { /// let provider = /// ProviderBuilder::<_, _, Ethereum>::default().connect("http://localhost:8545").await?; /// let cast = Cast::new(provider); /// let block = cast.block_raw(5, true).await?; /// println!("{}", block); /// # Ok(()) /// # } /// ``` pub async fn block_raw>(&self, block: B, full: bool) -> Result { let block_id = block.into(); let block = self .provider .get_block(block_id) .kind(full.into()) .await? .ok_or_else(|| eyre::eyre!("block {:?} not found", block_id))?; let encoded = alloy_rlp::encode(block.header().as_ref()); Ok(format!("0x{}", hex::encode(encoded))) } } impl, N: Network> Cast where N::TxEnvelope: Serialize + UIfmtSignatureExt, N::TransactionResponse: UIfmt, { /// # Example /// /// ``` /// use alloy_provider::{ProviderBuilder, RootProvider, network::AnyNetwork}; /// use cast::Cast; /// /// # async fn foo() -> eyre::Result<()> { /// let provider = /// ProviderBuilder::<_, _, AnyNetwork>::default().connect("http://localhost:8545").await?; /// let cast = Cast::new(provider); /// let tx_hash = "0xf8d1713ea15a81482958fb7ddf884baee8d3bcc478c5f2f604e008dc788ee4fc"; /// let tx = cast.transaction(Some(tx_hash.to_string()), None, None, None, false, false).await?; /// println!("{}", tx); /// # Ok(()) /// # } /// ``` pub async fn transaction( &self, tx_hash: Option, from: Option, nonce: Option, field: Option, raw: bool, to_request: bool, ) -> Result { let tx = if let Some(tx_hash) = tx_hash { let tx_hash = TxHash::from_str(&tx_hash).wrap_err("invalid tx hash")?; self.provider .get_transaction_by_hash(tx_hash) .await? .ok_or_else(|| eyre::eyre!("tx not found: {:?}", tx_hash))? } else if let Some(from) = from { // If nonce is not provided, uses 0. let nonce = U64::from(nonce.unwrap_or_default()); let from = from.resolve(self.provider.root()).await?; self.provider .raw_request::<_, Option>( "eth_getTransactionBySenderAndNonce".into(), (from, nonce), ) .await? .ok_or_else(|| { eyre::eyre!("tx not found for sender {from} and nonce {:?}", nonce.to::()) })? } else { eyre::bail!("tx hash or from address is required") }; Ok(if raw { let encoded = tx.as_ref().encoded_2718(); format!("0x{}", hex::encode(encoded)) } else if let Some(ref field) = field { get_pretty_tx_attr::(&tx, field.as_str()) .ok_or_else(|| eyre::eyre!("invalid tx field: {}", field.to_string()))? } else if shell::is_json() { // to_value first to sort json object keys serde_json::to_value(&tx)?.to_string() } else if to_request { serde_json::to_string_pretty(&Into::::into(tx))? } else { tx.pretty() }) } } pub struct SimpleCast; impl SimpleCast { /// Returns the maximum value of the given integer type /// /// # Example /// /// ``` /// use alloy_primitives::{I256, U256}; /// use cast::SimpleCast; /// /// assert_eq!(SimpleCast::max_int("uint256")?, U256::MAX.to_string()); /// assert_eq!(SimpleCast::max_int("int256")?, I256::MAX.to_string()); /// assert_eq!(SimpleCast::max_int("int32")?, i32::MAX.to_string()); /// # Ok::<(), eyre::Report>(()) /// ``` pub fn max_int(s: &str) -> Result { Self::max_min_int::(s) } /// Returns the maximum value of the given integer type /// /// # Example /// /// ``` /// use alloy_primitives::{I256, U256}; /// use cast::SimpleCast; /// /// assert_eq!(SimpleCast::min_int("uint256")?, "0"); /// assert_eq!(SimpleCast::min_int("int256")?, I256::MIN.to_string()); /// assert_eq!(SimpleCast::min_int("int32")?, i32::MIN.to_string()); /// # Ok::<(), eyre::Report>(()) /// ``` pub fn min_int(s: &str) -> Result { Self::max_min_int::(s) } fn max_min_int(s: &str) -> Result { let ty = DynSolType::parse(s).wrap_err("Invalid type, expected `(u)int`")?; match ty { DynSolType::Int(n) => { let mask = U256::from(1).wrapping_shl(n - 1); let max = (U256::MAX & mask).saturating_sub(U256::from(1)); if MAX { Ok(max.to_string()) } else { let min = I256::from_raw(max).wrapping_neg() + I256::MINUS_ONE; Ok(min.to_string()) } } DynSolType::Uint(n) => { if MAX { let mut max = U256::MAX; if n < 256 { max &= U256::from(1).wrapping_shl(n).wrapping_sub(U256::from(1)); } Ok(max.to_string()) } else { Ok("0".to_string()) } } _ => Err(eyre::eyre!("Type is not int/uint: {s}")), } } /// Converts UTF-8 text input to hex /// /// # Example /// /// ``` /// use cast::SimpleCast as Cast; /// /// assert_eq!(Cast::from_utf8("yo"), "0x796f"); /// assert_eq!(Cast::from_utf8("Hello, World!"), "0x48656c6c6f2c20576f726c6421"); /// assert_eq!(Cast::from_utf8("TurboDappTools"), "0x547572626f44617070546f6f6c73"); /// # Ok::<_, eyre::Report>(()) /// ``` pub fn from_utf8(s: &str) -> String { hex::encode_prefixed(s) } /// Converts hex input to UTF-8 text /// /// # Example /// /// ``` /// use cast::SimpleCast as Cast; /// /// assert_eq!(Cast::to_utf8("0x796f")?, "yo"); /// assert_eq!(Cast::to_utf8("0x48656c6c6f2c20576f726c6421")?, "Hello, World!"); /// assert_eq!(Cast::to_utf8("0x547572626f44617070546f6f6c73")?, "TurboDappTools"); /// assert_eq!(Cast::to_utf8("0xe4bda0e5a5bd")?, "你好"); /// # Ok::<_, eyre::Report>(()) /// ``` pub fn to_utf8(s: &str) -> Result { let bytes = hex::decode(s)?; Ok(String::from_utf8_lossy(bytes.as_ref()).to_string()) } /// Converts hex data into text data /// /// # Example /// /// ``` /// use cast::SimpleCast as Cast; /// /// assert_eq!(Cast::to_ascii("0x796f")?, "yo"); /// assert_eq!(Cast::to_ascii("48656c6c6f2c20576f726c6421")?, "Hello, World!"); /// assert_eq!(Cast::to_ascii("0x547572626f44617070546f6f6c73")?, "TurboDappTools"); /// # Ok::<_, eyre::Report>(()) /// ``` pub fn to_ascii(hex: &str) -> Result { let bytes = hex::decode(hex)?; if !bytes.iter().all(u8::is_ascii) { return Err(eyre::eyre!("Invalid ASCII bytes")); } Ok(String::from_utf8(bytes).unwrap()) } /// Converts fixed point number into specified number of decimals /// ``` /// use alloy_primitives::U256; /// use cast::SimpleCast as Cast; /// /// assert_eq!(Cast::from_fixed_point("10", "0")?, "10"); /// assert_eq!(Cast::from_fixed_point("1.0", "1")?, "10"); /// assert_eq!(Cast::from_fixed_point("0.10", "2")?, "10"); /// assert_eq!(Cast::from_fixed_point("0.010", "3")?, "10"); /// # Ok::<_, eyre::Report>(()) /// ``` pub fn from_fixed_point(value: &str, decimals: &str) -> Result { let units: Unit = Unit::from_str(decimals)?; let n = ParseUnits::parse_units(value, units)?; Ok(n.to_string()) } /// Converts integers with specified decimals into fixed point numbers /// /// # Example /// /// ``` /// use alloy_primitives::U256; /// use cast::SimpleCast as Cast; /// /// assert_eq!(Cast::to_fixed_point("10", "0")?, "10."); /// assert_eq!(Cast::to_fixed_point("10", "1")?, "1.0"); /// assert_eq!(Cast::to_fixed_point("10", "2")?, "0.10"); /// assert_eq!(Cast::to_fixed_point("10", "3")?, "0.010"); /// /// assert_eq!(Cast::to_fixed_point("-10", "0")?, "-10."); /// assert_eq!(Cast::to_fixed_point("-10", "1")?, "-1.0"); /// assert_eq!(Cast::to_fixed_point("-10", "2")?, "-0.10"); /// assert_eq!(Cast::to_fixed_point("-10", "3")?, "-0.010"); /// # Ok::<_, eyre::Report>(()) /// ``` pub fn to_fixed_point(value: &str, decimals: &str) -> Result { let (sign, mut value, value_len) = { let number = NumberWithBase::parse_int(value, None)?; let sign = if number.is_nonnegative() { "" } else { "-" }; let value = format!("{number:#}"); let value_stripped = value.strip_prefix('-').unwrap_or(&value).to_string(); let value_len = value_stripped.len(); (sign, value_stripped, value_len) }; let decimals = NumberWithBase::parse_uint(decimals, None)?.number().to::(); let value = if decimals >= value_len { // Add "0." and pad with 0s format!("0.{value:0>decimals$}") } else { // Insert decimal at -idx (i.e 1 => decimal idx = -1) value.insert(value_len - decimals, '.'); value }; Ok(format!("{sign}{value}")) } /// Concatencates hex strings /// /// # Example /// /// ``` /// use cast::SimpleCast as Cast; /// /// assert_eq!(Cast::concat_hex(["0x00", "0x01"]), "0x0001"); /// assert_eq!(Cast::concat_hex(["1", "2"]), "0x12"); /// # Ok::<_, eyre::Report>(()) /// ``` pub fn concat_hex>(values: impl IntoIterator) -> String { let mut out = String::new(); for s in values { let s = s.as_ref(); out.push_str(strip_0x(s)) } format!("0x{out}") } /// Converts a number into uint256 hex string with 0x prefix /// /// # Example /// /// ``` /// use cast::SimpleCast as Cast; /// /// assert_eq!( /// Cast::to_uint256("100")?, /// "0x0000000000000000000000000000000000000000000000000000000000000064" /// ); /// assert_eq!( /// Cast::to_uint256("192038293923")?, /// "0x0000000000000000000000000000000000000000000000000000002cb65fd1a3" /// ); /// assert_eq!( /// Cast::to_uint256( /// "115792089237316195423570985008687907853269984665640564039457584007913129639935" /// )?, /// "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" /// ); /// # Ok::<_, eyre::Report>(()) /// ``` pub fn to_uint256(value: &str) -> Result { let n = NumberWithBase::parse_uint(value, None)?; Ok(format!("{n:#066x}")) } /// Converts a number into int256 hex string with 0x prefix /// /// # Example /// /// ``` /// use cast::SimpleCast as Cast; /// /// assert_eq!( /// Cast::to_int256("0")?, /// "0x0000000000000000000000000000000000000000000000000000000000000000" /// ); /// assert_eq!( /// Cast::to_int256("100")?, /// "0x0000000000000000000000000000000000000000000000000000000000000064" /// ); /// assert_eq!( /// Cast::to_int256("-100")?, /// "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff9c" /// ); /// assert_eq!( /// Cast::to_int256("192038293923")?, /// "0x0000000000000000000000000000000000000000000000000000002cb65fd1a3" /// ); /// assert_eq!( /// Cast::to_int256("-192038293923")?, /// "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffd349a02e5d" /// ); /// assert_eq!( /// Cast::to_int256( /// "57896044618658097711785492504343953926634992332820282019728792003956564819967" /// )?, /// "0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" /// ); /// assert_eq!( /// Cast::to_int256( /// "-57896044618658097711785492504343953926634992332820282019728792003956564819968" /// )?, /// "0x8000000000000000000000000000000000000000000000000000000000000000" /// ); /// # Ok::<_, eyre::Report>(()) /// ``` pub fn to_int256(value: &str) -> Result { let n = NumberWithBase::parse_int(value, None)?; Ok(format!("{n:#066x}")) } /// Converts an eth amount into a specified unit /// /// # Example /// /// ``` /// use cast::SimpleCast as Cast; /// /// assert_eq!(Cast::to_unit("1 wei", "wei")?, "1"); /// assert_eq!(Cast::to_unit("1", "wei")?, "1"); /// assert_eq!(Cast::to_unit("1ether", "wei")?, "1000000000000000000"); /// # Ok::<_, eyre::Report>(()) /// ``` pub fn to_unit(value: &str, unit: &str) -> Result { let value = DynSolType::coerce_str(&DynSolType::Uint(256), value)? .as_uint() .wrap_err("Could not convert to uint")? .0; let unit = unit.parse().wrap_err("could not parse units")?; Ok(Self::format_unit_as_string(value, unit)) } /// Convert a number into a uint with arbitrary decimals. /// /// # Example /// /// ``` /// use cast::SimpleCast as Cast; /// /// # fn main() -> eyre::Result<()> { /// assert_eq!(Cast::parse_units("1.0", 6)?, "1000000"); // USDC (6 decimals) /// assert_eq!(Cast::parse_units("2.5", 6)?, "2500000"); /// assert_eq!(Cast::parse_units("1.0", 12)?, "1000000000000"); // 12 decimals /// assert_eq!(Cast::parse_units("1.23", 3)?, "1230"); // 3 decimals /// /// # Ok(()) /// # } /// ``` pub fn parse_units(value: &str, unit: u8) -> Result { let unit = Unit::new(unit).ok_or_else(|| eyre::eyre!("invalid unit"))?; Ok(ParseUnits::parse_units(value, unit)?.to_string()) } /// Format a number from smallest unit to decimal with arbitrary decimals. /// /// # Example /// /// ``` /// use cast::SimpleCast as Cast; /// /// # fn main() -> eyre::Result<()> { /// assert_eq!(Cast::format_units("1000000", 6)?, "1"); // USDC (6 decimals) /// assert_eq!(Cast::format_units("2500000", 6)?, "2.500000"); /// assert_eq!(Cast::format_units("1000000000000", 12)?, "1"); // 12 decimals /// assert_eq!(Cast::format_units("1230", 3)?, "1.230"); // 3 decimals /// /// # Ok(()) /// # } /// ``` pub fn format_units(value: &str, unit: u8) -> Result { let value = NumberWithBase::parse_int(value, None)?.number(); let unit = Unit::new(unit).ok_or_else(|| eyre::eyre!("invalid unit"))?; Ok(Self::format_unit_as_string(value, unit)) } // Helper function to format units as a string fn format_unit_as_string(value: U256, unit: Unit) -> String { let mut formatted = ParseUnits::U256(value).format_units(unit); // Trim empty fractional part. if let Some(dot) = formatted.find('.') { let fractional = &formatted[dot + 1..]; if fractional.chars().all(|c: char| c == '0') { formatted = formatted[..dot].to_string(); } } formatted } /// Converts wei into an eth amount /// /// # Example /// /// ``` /// use cast::SimpleCast as Cast; /// /// assert_eq!(Cast::from_wei("1", "gwei")?, "0.000000001"); /// assert_eq!(Cast::from_wei("12340000005", "gwei")?, "12.340000005"); /// assert_eq!(Cast::from_wei("10", "ether")?, "0.000000000000000010"); /// assert_eq!(Cast::from_wei("100", "eth")?, "0.000000000000000100"); /// assert_eq!(Cast::from_wei("17", "ether")?, "0.000000000000000017"); /// # Ok::<_, eyre::Report>(()) /// ``` pub fn from_wei(value: &str, unit: &str) -> Result { let value = NumberWithBase::parse_int(value, None)?.number(); Ok(ParseUnits::U256(value).format_units(unit.parse()?)) } /// Converts an eth amount into wei /// /// # Example /// /// ``` /// use cast::SimpleCast as Cast; /// /// assert_eq!(Cast::to_wei("100", "gwei")?, "100000000000"); /// assert_eq!(Cast::to_wei("100", "eth")?, "100000000000000000000"); /// assert_eq!(Cast::to_wei("1000", "ether")?, "1000000000000000000000"); /// # Ok::<_, eyre::Report>(()) /// ``` pub fn to_wei(value: &str, unit: &str) -> Result { let unit = unit.parse().wrap_err("could not parse units")?; Ok(ParseUnits::parse_units(value, unit)?.to_string()) } // Decodes RLP encoded data with validation for canonical integer representation /// /// # Examples /// ``` /// use cast::SimpleCast as Cast; /// /// assert_eq!(Cast::from_rlp("0xc0", false).unwrap(), "[]"); /// assert_eq!(Cast::from_rlp("0x0f", false).unwrap(), "\"0x0f\""); /// assert_eq!(Cast::from_rlp("0x33", false).unwrap(), "\"0x33\""); /// assert_eq!(Cast::from_rlp("0xc161", false).unwrap(), "[\"0x61\"]"); /// assert_eq!(Cast::from_rlp("820002", true).is_err(), true); /// assert_eq!(Cast::from_rlp("820002", false).unwrap(), "\"0x0002\""); /// assert_eq!(Cast::from_rlp("00", true).is_err(), true); /// assert_eq!(Cast::from_rlp("00", false).unwrap(), "\"0x00\""); /// # Ok::<_, eyre::Report>(()) /// ``` pub fn from_rlp(value: impl AsRef, as_int: bool) -> Result { let bytes = hex::decode(value.as_ref()).wrap_err("Could not decode hex")?; if as_int { return Ok(U256::decode(&mut &bytes[..])?.to_string()); } let item = Item::decode(&mut &bytes[..]).wrap_err("Could not decode rlp")?; Ok(item.to_string()) } /// Encodes hex data or list of hex data to hexadecimal rlp /// /// # Example /// /// ``` /// use cast::SimpleCast as Cast; /// /// assert_eq!(Cast::to_rlp("[]").unwrap(), "0xc0".to_string()); /// assert_eq!(Cast::to_rlp("0x22").unwrap(), "0x22".to_string()); /// assert_eq!(Cast::to_rlp("[\"0x61\"]",).unwrap(), "0xc161".to_string()); /// assert_eq!(Cast::to_rlp("[\"0xf1\", \"f2\"]").unwrap(), "0xc481f181f2".to_string()); /// # Ok::<_, eyre::Report>(()) /// ``` pub fn to_rlp(value: &str) -> Result { let val = serde_json::from_str(value) .unwrap_or_else(|_| serde_json::Value::String(value.to_string())); let item = Item::value_to_item(&val)?; Ok(format!("0x{}", hex::encode(alloy_rlp::encode(item)))) } /// Converts a number of one base to another /// /// # Example /// /// ``` /// use alloy_primitives::I256; /// use cast::SimpleCast as Cast; /// /// assert_eq!(Cast::to_base("100", Some("10"), "16")?, "0x64"); /// assert_eq!(Cast::to_base("100", Some("10"), "oct")?, "0o144"); /// assert_eq!(Cast::to_base("100", Some("10"), "binary")?, "0b1100100"); /// /// assert_eq!(Cast::to_base("0xffffffffffffffff", None, "10")?, u64::MAX.to_string()); /// assert_eq!( /// Cast::to_base("0xffffffffffffffffffffffffffffffff", None, "dec")?, /// u128::MAX.to_string() /// ); /// // U256::MAX overflows as internally it is being parsed as I256 /// assert_eq!( /// Cast::to_base( /// "0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", /// None, /// "decimal" /// )?, /// I256::MAX.to_string() /// ); /// # Ok::<_, eyre::Report>(()) /// ``` pub fn to_base(value: &str, base_in: Option<&str>, base_out: &str) -> Result { let base_in = Base::unwrap_or_detect(base_in, value)?; let base_out: Base = base_out.parse()?; if base_in == base_out { return Ok(value.to_string()); } let mut n = NumberWithBase::parse_int(value, Some(&base_in.to_string()))?; n.set_base(base_out); // Use Debug fmt Ok(format!("{n:#?}")) } /// Converts hexdata into bytes32 value /// /// # Example /// /// ``` /// use cast::SimpleCast as Cast; /// /// let bytes = Cast::to_bytes32("1234")?; /// assert_eq!(bytes, "0x1234000000000000000000000000000000000000000000000000000000000000"); /// /// let bytes = Cast::to_bytes32("0x1234")?; /// assert_eq!(bytes, "0x1234000000000000000000000000000000000000000000000000000000000000"); /// /// let err = Cast::to_bytes32("0x123400000000000000000000000000000000000000000000000000000000000011").unwrap_err(); /// assert_eq!(err.to_string(), "string >32 bytes"); /// # Ok::<_, eyre::Report>(()) pub fn to_bytes32(s: &str) -> Result { let s = strip_0x(s); if s.len() > 64 { eyre::bail!("string >32 bytes"); } let padded = format!("{s:0<64}"); Ok(padded.parse::()?.to_string()) } /// Encodes string into bytes32 value pub fn format_bytes32_string(s: &str) -> Result { let str_bytes: &[u8] = s.as_bytes(); eyre::ensure!(str_bytes.len() <= 32, "bytes32 strings must not exceed 32 bytes in length"); let mut bytes32: [u8; 32] = [0u8; 32]; bytes32[..str_bytes.len()].copy_from_slice(str_bytes); Ok(hex::encode_prefixed(bytes32)) } /// Pads hex data to a specified length /// /// # Example /// /// ``` /// use cast::SimpleCast as Cast; /// /// let padded = Cast::pad("abcd", true, 20)?; /// assert_eq!(padded, "0xabcd000000000000000000000000000000000000"); /// /// let padded = Cast::pad("abcd", false, 20)?; /// assert_eq!(padded, "0x000000000000000000000000000000000000abcd"); /// /// let padded = Cast::pad("0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", true, 32)?; /// assert_eq!(padded, "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2000000000000000000000000"); /// /// let padded = Cast::pad("0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", false, 32)?; /// assert_eq!(padded, "0x000000000000000000000000C02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"); /// /// let err = Cast::pad("1234", false, 1).unwrap_err(); /// assert_eq!(err.to_string(), "input length exceeds target length"); /// /// let err = Cast::pad("foobar", false, 32).unwrap_err(); /// assert_eq!(err.to_string(), "input is not a valid hex"); /// /// # Ok::<_, eyre::Report>(()) /// ``` pub fn pad(s: &str, right: bool, len: usize) -> Result { let s = strip_0x(s); let hex_len = len * 2; // Validate input if s.len() > hex_len { eyre::bail!("input length exceeds target length"); } if !s.chars().all(|c| c.is_ascii_hexdigit()) { eyre::bail!("input is not a valid hex"); } Ok(if right { format!("0x{s:0hex_len$}") }) } /// Decodes string from bytes32 value pub fn parse_bytes32_string(s: &str) -> Result { let bytes = hex::decode(s)?; eyre::ensure!(bytes.len() == 32, "expected 32 byte hex-string"); let len = bytes.iter().take_while(|x| **x != 0).count(); Ok(std::str::from_utf8(&bytes[..len])?.into()) } /// Decodes checksummed address from bytes32 value pub fn parse_bytes32_address(s: &str) -> Result { let s = strip_0x(s); if s.len() != 64 { eyre::bail!("expected 64 byte hex-string, got {s}"); } let s = if let Some(stripped) = s.strip_prefix("000000000000000000000000") { stripped } else { return Err(eyre::eyre!("Not convertible to address, there are non-zero bytes")); }; let lowercase_address_string = format!("0x{s}"); let lowercase_address = Address::from_str(&lowercase_address_string)?; Ok(lowercase_address.to_checksum(None)) } /// Decodes abi-encoded hex input or output /// /// When `input=true`, `calldata` string MUST not be prefixed with function selector /// /// # Example /// /// ``` /// use cast::SimpleCast as Cast; /// use alloy_primitives::hex; /// /// // Passing `input = false` will decode the data as the output type. /// // The input data types and the full function sig are ignored, i.e. /// // you could also pass `balanceOf()(uint256)` and it'd still work. /// let data = "0x0000000000000000000000000000000000000000000000000000000000000001"; /// let sig = "balanceOf(address, uint256)(uint256)"; /// let decoded = Cast::abi_decode(sig, data, false)?[0].as_uint().unwrap().0.to_string(); /// assert_eq!(decoded, "1"); /// /// // Passing `input = true` will decode the data with the input function signature. /// // We exclude the "prefixed" function selector from the data field (the first 4 bytes). /// let data = "0x0000000000000000000000008dbd1b711dc621e1404633da156fcc779e1c6f3e000000000000000000000000d9f3c9cc99548bf3b44a43e0a2d07399eb918adc000000000000000000000000000000000000000000000000000000000000002a000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000"; /// let sig = "safeTransferFrom(address, address, uint256, uint256, bytes)"; /// let decoded = Cast::abi_decode(sig, data, true)?; /// let decoded = [ /// decoded[0].as_address().unwrap().to_string().to_lowercase(), /// decoded[1].as_address().unwrap().to_string().to_lowercase(), /// decoded[2].as_uint().unwrap().0.to_string(), /// decoded[3].as_uint().unwrap().0.to_string(), /// hex::encode(decoded[4].as_bytes().unwrap()) /// ] /// .into_iter() /// .collect::>(); /// /// assert_eq!( /// decoded, /// vec!["0x8dbd1b711dc621e1404633da156fcc779e1c6f3e", "0xd9f3c9cc99548bf3b44a43e0a2d07399eb918adc", "42", "1", ""] /// ); /// # Ok::<_, eyre::Report>(()) /// ``` pub fn abi_decode(sig: &str, calldata: &str, input: bool) -> Result> { foundry_common::abi::abi_decode_calldata(sig, calldata, input, false) } /// Decodes calldata-encoded hex input or output /// /// Similar to `abi_decode`, but `calldata` string MUST be prefixed with function selector /// /// # Example /// /// ``` /// use cast::SimpleCast as Cast; /// use alloy_primitives::hex; /// /// // Passing `input = false` will decode the data as the output type. /// // The input data types and the full function sig are ignored, i.e. /// // you could also pass `balanceOf()(uint256)` and it'd still work. /// let data = "0x0000000000000000000000000000000000000000000000000000000000000001"; /// let sig = "balanceOf(address, uint256)(uint256)"; /// let decoded = Cast::calldata_decode(sig, data, false)?[0].as_uint().unwrap().0.to_string(); /// assert_eq!(decoded, "1"); /// /// // Passing `input = true` will decode the data with the input function signature. /// let data = "0xf242432a0000000000000000000000008dbd1b711dc621e1404633da156fcc779e1c6f3e000000000000000000000000d9f3c9cc99548bf3b44a43e0a2d07399eb918adc000000000000000000000000000000000000000000000000000000000000002a000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000"; /// let sig = "safeTransferFrom(address, address, uint256, uint256, bytes)"; /// let decoded = Cast::calldata_decode(sig, data, true)?; /// let decoded = [ /// decoded[0].as_address().unwrap().to_string().to_lowercase(), /// decoded[1].as_address().unwrap().to_string().to_lowercase(), /// decoded[2].as_uint().unwrap().0.to_string(), /// decoded[3].as_uint().unwrap().0.to_string(), /// hex::encode(decoded[4].as_bytes().unwrap()), /// ] /// .into_iter() /// .collect::>(); /// assert_eq!( /// decoded, /// vec!["0x8dbd1b711dc621e1404633da156fcc779e1c6f3e", "0xd9f3c9cc99548bf3b44a43e0a2d07399eb918adc", "42", "1", ""] /// ); /// # Ok::<_, eyre::Report>(()) /// ``` pub fn calldata_decode(sig: &str, calldata: &str, input: bool) -> Result> { foundry_common::abi::abi_decode_calldata(sig, calldata, input, true) } /// Performs ABI encoding based off of the function signature. Does not include /// the function selector in the result. /// /// # Example /// /// ``` /// use cast::SimpleCast as Cast; /// /// assert_eq!( /// "0x0000000000000000000000000000000000000000000000000000000000000001", /// Cast::abi_encode("f(uint a)", &["1"]).unwrap().as_str() /// ); /// assert_eq!( /// "0x0000000000000000000000000000000000000000000000000000000000000001", /// Cast::abi_encode("constructor(uint a)", &["1"]).unwrap().as_str() /// ); /// # Ok::<_, eyre::Report>(()) /// ``` pub fn abi_encode(sig: &str, args: &[impl AsRef]) -> Result { let func = get_func(sig)?; match encode_function_args(&func, args) { Ok(res) => Ok(hex::encode_prefixed(&res[4..])), Err(e) => eyre::bail!("Could not ABI encode the function and arguments: {e}"), } } /// Performs packed ABI encoding based off of the function signature or tuple. /// /// # Examplez /// /// ``` /// use cast::SimpleCast as Cast; /// /// assert_eq!( /// "0x0000000000000000000000000000000000000000000000000000000000000064000000000000000000000000000000000000000000000000000000000000012c00000000000000c8", /// Cast::abi_encode_packed("(uint128[] a, uint64 b)", &["[100, 300]", "200"]).unwrap().as_str() /// ); /// /// assert_eq!( /// "0x8dbd1b711dc621e1404633da156fcc779e1c6f3e68656c6c6f20776f726c64", /// Cast::abi_encode_packed("foo(address a, string b)", &["0x8dbd1b711dc621e1404633da156fcc779e1c6f3e", "hello world"]).unwrap().as_str() /// ); /// # Ok::<_, eyre::Report>(()) /// ``` pub fn abi_encode_packed(sig: &str, args: &[impl AsRef]) -> Result { // If the signature is a tuple, we need to prefix it to make it a function let sig = if sig.trim_start().starts_with('(') { format!("foo{sig}") } else { sig.to_string() }; let func = get_func(sig.as_str())?; let encoded = match encode_function_args_packed(&func, args) { Ok(res) => hex::encode(res), Err(e) => eyre::bail!("Could not ABI encode the function and arguments: {e}"), }; Ok(format!("0x{encoded}")) } /// Performs ABI encoding of an event to produce the topics and data. /// /// # Example /// /// ``` /// use alloy_primitives::hex; /// use cast::SimpleCast as Cast; /// /// let log_data = Cast::abi_encode_event( /// "Transfer(address indexed from, address indexed to, uint256 value)", /// &[ /// "0x1234567890123456789012345678901234567890", /// "0xabcdefabcdefabcdefabcdefabcdefabcdefabcd", /// "1000", /// ], /// ) /// .unwrap(); /// /// // topic0 is the event selector /// assert_eq!(log_data.topics().len(), 3); /// assert_eq!( /// log_data.topics()[0].to_string(), /// "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef" /// ); /// assert_eq!( /// log_data.topics()[1].to_string(), /// "0x0000000000000000000000001234567890123456789012345678901234567890" /// ); /// assert_eq!( /// log_data.topics()[2].to_string(), /// "0x000000000000000000000000abcdefabcdefabcdefabcdefabcdefabcdefabcd" /// ); /// assert_eq!( /// hex::encode_prefixed(log_data.data), /// "0x00000000000000000000000000000000000000000000000000000000000003e8" /// ); /// # Ok::<_, eyre::Report>(()) /// ``` pub fn abi_encode_event(sig: &str, args: &[impl AsRef]) -> Result { let event = get_event(sig)?; let tokens = std::iter::zip(&event.inputs, args) .map(|(input, arg)| coerce_value(&input.ty, arg.as_ref())) .collect::>>()?; let mut topics = vec![event.selector()]; let mut data_tokens: Vec = Vec::new(); for (input, token) in event.inputs.iter().zip(tokens) { if input.indexed { let ty = DynSolType::parse(&input.ty)?; if matches!( ty, DynSolType::String | DynSolType::Bytes | DynSolType::Array(_) | DynSolType::Tuple(_) ) { // For dynamic types, hash the encoded value let encoded = token.abi_encode(); let hash = keccak256(encoded); topics.push(hash); } else { // For fixed-size types, encode directly to 32 bytes let mut encoded = [0u8; 32]; let token_encoded = token.abi_encode(); if token_encoded.len() <= 32 { let start = 32 - token_encoded.len(); encoded[start..].copy_from_slice(&token_encoded); } topics.push(B256::from(encoded)); } } else { // Non-indexed parameters go into data data_tokens.extend_from_slice(&token.abi_encode()); } } Ok(LogData::new_unchecked(topics, data_tokens.into())) } /// Performs ABI encoding to produce the hexadecimal calldata with the given arguments. /// /// # Example /// /// ``` /// use cast::SimpleCast as Cast; /// /// assert_eq!( /// "0xb3de648b0000000000000000000000000000000000000000000000000000000000000001", /// Cast::calldata_encode("f(uint256 a)", &["1"]).unwrap().as_str() /// ); /// # Ok::<_, eyre::Report>(()) /// ``` pub fn calldata_encode(sig: impl AsRef, args: &[impl AsRef]) -> Result { let func = get_func(sig.as_ref())?; let calldata = encode_function_args(&func, args)?; Ok(hex::encode_prefixed(calldata)) } /// Returns the slot number for a given mapping key and slot. /// /// Given `mapping(k => v) m`, for a key `k` the slot number of its associated `v` is /// `keccak256(concat(h(k), p))`, where `h` is the padding function for `k`'s type, and `p` /// is slot number of the mapping `m`. /// /// See [the Solidity documentation](https://docs.soliditylang.org/en/latest/internals/layout_in_storage.html#mappings-and-dynamic-arrays) /// for more details. /// /// # Example /// /// ``` /// # use cast::SimpleCast as Cast; /// /// // Value types. /// assert_eq!( /// Cast::index("address", "0xD0074F4E6490ae3f888d1d4f7E3E43326bD3f0f5", "2").unwrap().as_str(), /// "0x9525a448a9000053a4d151336329d6563b7e80b24f8e628e95527f218e8ab5fb" /// ); /// assert_eq!( /// Cast::index("uint256", "42", "6").unwrap().as_str(), /// "0xfc808b0f31a1e6b9cf25ff6289feae9b51017b392cc8e25620a94a38dcdafcc1" /// ); /// /// // Strings and byte arrays. /// assert_eq!( /// Cast::index("string", "hello", "1").unwrap().as_str(), /// "0x8404bb4d805e9ca2bd5dd5c43a107e935c8ec393caa7851b353b3192cd5379ae" /// ); /// # Ok::<_, eyre::Report>(()) /// ``` pub fn index(key_type: &str, key: &str, slot_number: &str) -> Result { let mut hasher = Keccak256::new(); let k_ty = DynSolType::parse(key_type).wrap_err("Could not parse type")?; let k = k_ty.coerce_str(key).wrap_err("Could not parse value")?; match k_ty { // For value types, `h` pads the value to 32 bytes in the same way as when storing the // value in memory. DynSolType::Bool | DynSolType::Int(_) | DynSolType::Uint(_) | DynSolType::FixedBytes(_) | DynSolType::Address | DynSolType::Function => hasher.update(k.as_word().unwrap()), // For strings and byte arrays, `h(k)` is just the unpadded data. DynSolType::String | DynSolType::Bytes => hasher.update(k.as_packed_seq().unwrap()), DynSolType::Array(..) | DynSolType::FixedArray(..) | DynSolType::Tuple(..) | DynSolType::CustomStruct { .. } => { eyre::bail!("Type `{k_ty}` is not supported as a mapping key") } } let p = DynSolType::Uint(256) .coerce_str(slot_number) .wrap_err("Could not parse slot number")?; let p = p.as_word().unwrap(); hasher.update(p); let location = hasher.finalize(); Ok(location.to_string()) } /// Keccak-256 hashes arbitrary data /// /// # Example /// /// ``` /// use cast::SimpleCast as Cast; /// /// assert_eq!( /// Cast::keccak("foo")?, /// "0x41b1a0649752af1b28b3dc29a1556eee781e4a4c3a1f7f53f90fa834de098c4d" /// ); /// assert_eq!( /// Cast::keccak("123abc")?, /// "0xb1f1c74a1ba56f07a892ea1110a39349d40f66ca01d245e704621033cb7046a4" /// ); /// assert_eq!( /// Cast::keccak("0x12")?, /// "0x5fa2358263196dbbf23d1ca7a509451f7a2f64c15837bfbb81298b1e3e24e4fa" /// ); /// assert_eq!( /// Cast::keccak("12")?, /// "0x7f8b6b088b6d74c2852fc86c796dca07b44eed6fb3daf5e6b59f7c364db14528" /// ); /// # Ok::<_, eyre::Report>(()) /// ``` pub fn keccak(data: &str) -> Result { // Hex-decode if data starts with 0x. let hash = if data.starts_with("0x") { keccak256(hex::decode(data.trim_end())?) } else { keccak256(data) }; Ok(hash.to_string()) } /// Performs the left shift operation (<<) on a number /// /// # Example /// /// ``` /// use cast::SimpleCast as Cast; /// /// assert_eq!(Cast::left_shift("16", "10", Some("10"), "hex")?, "0x4000"); /// assert_eq!(Cast::left_shift("255", "16", Some("dec"), "hex")?, "0xff0000"); /// assert_eq!(Cast::left_shift("0xff", "16", None, "hex")?, "0xff0000"); /// # Ok::<_, eyre::Report>(()) /// ``` pub fn left_shift( value: &str, bits: &str, base_in: Option<&str>, base_out: &str, ) -> Result { let base_out: Base = base_out.parse()?; let value = NumberWithBase::parse_uint(value, base_in)?; let bits = NumberWithBase::parse_uint(bits, None)?; let res = value.number() << bits.number(); Ok(res.to_base(base_out, true)?) } /// Performs the right shift operation (>>) on a number /// /// # Example /// /// ``` /// use cast::SimpleCast as Cast; /// /// assert_eq!(Cast::right_shift("0x4000", "10", None, "dec")?, "16"); /// assert_eq!(Cast::right_shift("16711680", "16", Some("10"), "hex")?, "0xff"); /// assert_eq!(Cast::right_shift("0xff0000", "16", None, "hex")?, "0xff"); /// # Ok::<(), eyre::Report>(()) /// ``` pub fn right_shift( value: &str, bits: &str, base_in: Option<&str>, base_out: &str, ) -> Result { let base_out: Base = base_out.parse()?; let value = NumberWithBase::parse_uint(value, base_in)?; let bits = NumberWithBase::parse_uint(bits, None)?; let res = value.number().wrapping_shr(bits.number().saturating_to()); Ok(res.to_base(base_out, true)?) } /// Fetches source code of verified contracts from etherscan. /// /// # Example /// /// ``` /// # use cast::SimpleCast as Cast; /// # use foundry_config::NamedChain; /// # async fn foo() -> eyre::Result<()> { /// assert_eq!( /// "/* /// - Bytecode Verification performed was compared on second iteration - /// This file is part of the DAO.....", /// Cast::etherscan_source( /// NamedChain::Mainnet.into(), /// "0xBB9bc244D798123fDe783fCc1C72d3Bb8C189413".to_string(), /// Some("".to_string()), /// None, /// None /// ) /// .await /// .unwrap() /// .as_str() /// ); /// # Ok(()) /// # } /// ``` pub async fn etherscan_source( chain: Chain, contract_address: String, etherscan_api_key: Option, explorer_api_url: Option, explorer_url: Option, ) -> Result { let client = explorer_client(chain, etherscan_api_key, explorer_api_url, explorer_url)?; let metadata = client.contract_source_code(contract_address.parse()?).await?; Ok(metadata.source_code()) } /// Fetches the source code of verified contracts from etherscan and expands the resulting /// files to a directory for easy perusal. /// /// # Example /// /// ``` /// # use cast::SimpleCast as Cast; /// # use foundry_config::NamedChain; /// # use std::path::PathBuf; /// # async fn expand() -> eyre::Result<()> { /// Cast::expand_etherscan_source_to_directory( /// NamedChain::Mainnet.into(), /// "0xBB9bc244D798123fDe783fCc1C72d3Bb8C189413".to_string(), /// Some("".to_string()), /// PathBuf::from("output_dir"), /// None, /// None, /// ) /// .await?; /// # Ok(()) /// # } /// ``` pub async fn expand_etherscan_source_to_directory( chain: Chain, contract_address: String, etherscan_api_key: Option, output_directory: PathBuf, explorer_api_url: Option, explorer_url: Option, ) -> eyre::Result<()> { let client = explorer_client(chain, etherscan_api_key, explorer_api_url, explorer_url)?; let meta = client.contract_source_code(contract_address.parse()?).await?; let source_tree = meta.source_tree(); source_tree.write_to(&output_directory)?; Ok(()) } /// Fetches the source code of verified contracts from etherscan, flattens it and writes it to /// the given path or stdout. pub async fn etherscan_source_flatten( chain: Chain, contract_address: String, etherscan_api_key: Option, output_path: Option, explorer_api_url: Option, explorer_url: Option, ) -> Result<()> { let client = explorer_client(chain, etherscan_api_key, explorer_api_url, explorer_url)?; let metadata = client.contract_source_code(contract_address.parse()?).await?; let Some(metadata) = metadata.items.first() else { eyre::bail!("Empty contract source code") }; let tmp = tempfile::tempdir()?; let project = etherscan_project(metadata, tmp.path())?; let target_path = project.find_contract_path(&metadata.contract_name)?; let flattened = flatten(project, &target_path)?; if let Some(path) = output_path { fs::create_dir_all(path.parent().unwrap())?; fs::write(&path, flattened)?; sh_println!("Flattened file written at {}", path.display())? } else { sh_println!("{flattened}")? } Ok(()) } /// Disassembles hex encoded bytecode into individual / human readable opcodes /// /// # Example /// /// ``` /// use alloy_primitives::hex; /// use cast::SimpleCast as Cast; /// /// # async fn foo() -> eyre::Result<()> { /// let bytecode = "0x608060405260043610603f57600035"; /// let opcodes = Cast::disassemble(&hex::decode(bytecode)?)?; /// println!("{}", opcodes); /// # Ok(()) /// # } /// ``` pub fn disassemble(code: &[u8]) -> Result { let mut output = String::new(); for (pc, inst) in InstIter::new(code).with_pc() { writeln!(output, "{pc:08x}: {inst}")?; } Ok(output) } /// Gets the selector for a given function signature /// Optimizes if the `optimize` parameter is set to a number of leading zeroes /// /// # Example /// /// ``` /// use cast::SimpleCast as Cast; /// /// assert_eq!(Cast::get_selector("foo(address,uint256)", 0)?.0, String::from("0xbd0d639f")); /// # Ok::<(), eyre::Error>(()) /// ``` pub fn get_selector(signature: &str, optimize: usize) -> Result<(String, String)> { if optimize > 4 { eyre::bail!("number of leading zeroes must not be greater than 4"); } if optimize == 0 { let selector = get_func(signature)?.selector(); return Ok((selector.to_string(), String::from(signature))); } let Some((name, params)) = signature.split_once('(') else { eyre::bail!("invalid function signature"); }; let num_threads = rayon::current_num_threads(); let found = AtomicBool::new(false); let result: Option<(u32, String, String)> = (0..num_threads).into_par_iter().find_map_any(|i| { let nonce_start = i as u32; let nonce_step = num_threads as u32; let mut nonce = nonce_start; while nonce < u32::MAX && !found.load(Ordering::Relaxed) { let input = format!("{name}{nonce}({params}"); let hash = keccak256(input.as_bytes()); let selector = &hash[..4]; if selector.iter().take_while(|&&byte| byte == 0).count() == optimize { found.store(true, Ordering::Relaxed); return Some((nonce, hex::encode_prefixed(selector), input)); } nonce += nonce_step; } None }); match result { Some((_nonce, selector, signature)) => Ok((selector, signature)), None => eyre::bail!("No selector found"), } } /// Extracts function selectors, arguments and state mutability from bytecode /// /// # Example /// /// ``` /// use alloy_primitives::fixed_bytes; /// use cast::SimpleCast as Cast; /// /// let bytecode = "6080604052348015600e575f80fd5b50600436106026575f3560e01c80632125b65b14602a575b5f80fd5b603a6035366004603c565b505050565b005b5f805f60608486031215604d575f80fd5b833563ffffffff81168114605f575f80fd5b925060208401356001600160a01b03811681146079575f80fd5b915060408401356001600160e01b03811681146093575f80fd5b80915050925092509256"; /// let functions = Cast::extract_functions(bytecode)?; /// assert_eq!(functions, vec![(fixed_bytes!("0x2125b65b"), "uint32,address,uint224".to_string(), "pure")]); /// # Ok::<(), eyre::Report>(()) /// ``` pub fn extract_functions(bytecode: &str) -> Result> { let code = hex::decode(bytecode)?; let info = evmole::contract_info( evmole::ContractInfoArgs::new(&code) .with_selectors() .with_arguments() .with_state_mutability(), ); Ok(info .functions .expect("functions extraction was requested") .into_iter() .map(|f| { ( f.selector.into(), f.arguments .expect("arguments extraction was requested") .into_iter() .map(|t| t.sol_type_name().to_string()) .collect::>() .join(","), f.state_mutability .expect("state_mutability extraction was requested") .as_json_str(), ) }) .collect()) } /// Decodes a raw EIP2718 transaction payload /// Returns details about the typed transaction and ECSDA signature components /// /// # Example /// /// ``` /// use cast::SimpleCast as Cast; /// /// let tx = "0x02f8f582a86a82058d8459682f008508351050808303fd84948e42f2f4101563bf679975178e880fd87d3efd4e80b884659ac74b00000000000000000000000080f0c1c49891dcfdd40b6e0f960f84e6042bcb6f000000000000000000000000b97ef9ef8734c71904d8002f8b6bc66dd9c48a6e00000000000000000000000000000000000000000000000000000000007ff4e20000000000000000000000000000000000000000000000000000000000000064c001a05d429597befe2835396206781b199122f2e8297327ed4a05483339e7a8b2022aa04c23a7f70fb29dda1b4ee342fb10a625e9b8ddc6a603fb4e170d4f6f37700cb8"; /// let tx_envelope = Cast::decode_raw_transaction(&tx)?; /// # Ok::<(), eyre::Report>(()) pub fn decode_raw_transaction(tx: &str) -> Result { let tx_hex = hex::decode(tx)?; let tx = Decodable2718::decode_2718(&mut tx_hex.as_slice())?; Ok(tx) } } pub(crate) fn strip_0x(s: &str) -> &str { s.strip_prefix("0x").unwrap_or(s) } fn explorer_client( chain: Chain, api_key: Option, api_url: Option, explorer_url: Option, ) -> Result { let mut builder = Client::builder(); let deduced = chain.etherscan_urls(); let explorer_url = explorer_url .or(deduced.map(|d| d.1.to_string())) .ok_or_eyre("Please provide the explorer browser URL using `--explorer-url`")?; builder = builder.with_url(explorer_url)?; let api_url = api_url .or(deduced.map(|d| d.0.to_string())) .ok_or_eyre("Please provide the explorer API URL using `--explorer-api-url`")?; builder = builder.with_api_url(api_url)?; if let Some(api_key) = api_key { builder = builder.with_api_key(api_key); } builder.build().map_err(Into::into) } #[cfg(test)] mod tests { use super::{DynSolValue, SimpleCast as Cast, serialize_value_as_json}; use alloy_primitives::hex; #[test] fn simple_selector() { assert_eq!("0xc2985578", Cast::get_selector("foo()", 0).unwrap().0.as_str()) } #[test] fn selector_with_arg() { assert_eq!("0xbd0d639f", Cast::get_selector("foo(address,uint256)", 0).unwrap().0.as_str()) } #[test] fn calldata_uint() { assert_eq!( "0xb3de648b0000000000000000000000000000000000000000000000000000000000000001", Cast::calldata_encode("f(uint256 a)", &["1"]).unwrap().as_str() ); } // #[test] fn calldata_array() { assert_eq!( "0xcde2baba0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000", Cast::calldata_encode("propose(string[])", &["[\"\"]"]).unwrap().as_str() ); } #[test] fn calldata_bool() { assert_eq!( "0x6fae94120000000000000000000000000000000000000000000000000000000000000000", Cast::calldata_encode("bar(bool)", &["false"]).unwrap().as_str() ); } #[test] fn abi_decode() { let data = "0x0000000000000000000000000000000000000000000000000000000000000001"; let sig = "balanceOf(address, uint256)(uint256)"; assert_eq!( "1", Cast::abi_decode(sig, data, false).unwrap()[0].as_uint().unwrap().0.to_string() ); let data = "0x0000000000000000000000008dbd1b711dc621e1404633da156fcc779e1c6f3e000000000000000000000000d9f3c9cc99548bf3b44a43e0a2d07399eb918adc000000000000000000000000000000000000000000000000000000000000002a000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000"; let sig = "safeTransferFrom(address,address,uint256,uint256,bytes)"; let decoded = Cast::abi_decode(sig, data, true).unwrap(); let decoded = [ decoded[0] .as_address() .unwrap() .to_string() .strip_prefix("0x") .unwrap() .to_owned() .to_lowercase(), decoded[1] .as_address() .unwrap() .to_string() .strip_prefix("0x") .unwrap() .to_owned() .to_lowercase(), decoded[2].as_uint().unwrap().0.to_string(), decoded[3].as_uint().unwrap().0.to_string(), hex::encode(decoded[4].as_bytes().unwrap()), ] .to_vec(); assert_eq!( decoded, vec![ "8dbd1b711dc621e1404633da156fcc779e1c6f3e", "d9f3c9cc99548bf3b44a43e0a2d07399eb918adc", "42", "1", "" ] ); } #[test] fn calldata_decode() { let data = "0x0000000000000000000000000000000000000000000000000000000000000001"; let sig = "balanceOf(address, uint256)(uint256)"; let decoded = Cast::calldata_decode(sig, data, false).unwrap()[0].as_uint().unwrap().0.to_string(); assert_eq!(decoded, "1"); // Passing `input = true` will decode the data with the input function signature. // We exclude the "prefixed" function selector from the data field (the first 4 bytes). let data = "0xf242432a0000000000000000000000008dbd1b711dc621e1404633da156fcc779e1c6f3e000000000000000000000000d9f3c9cc99548bf3b44a43e0a2d07399eb918adc000000000000000000000000000000000000000000000000000000000000002a000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000"; let sig = "safeTransferFrom(address, address, uint256, uint256, bytes)"; let decoded = Cast::calldata_decode(sig, data, true).unwrap(); let decoded = [ decoded[0].as_address().unwrap().to_string().to_lowercase(), decoded[1].as_address().unwrap().to_string().to_lowercase(), decoded[2].as_uint().unwrap().0.to_string(), decoded[3].as_uint().unwrap().0.to_string(), hex::encode(decoded[4].as_bytes().unwrap()), ] .into_iter() .collect::>(); assert_eq!( decoded, vec![ "0x8dbd1b711dc621e1404633da156fcc779e1c6f3e", "0xd9f3c9cc99548bf3b44a43e0a2d07399eb918adc", "42", "1", "" ] ); } #[test] fn calldata_decode_nested_json() { let calldata = "0xdb5b0ed700000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000006772bf190000000000000000000000000000000000000000000000000000000000020716000000000000000000000000af9d27ffe4d51ed54ac8eec78f2785d7e11e5ab100000000000000000000000000000000000000000000000000000000000002c0000000000000000000000000000000000000000000000000000000000000000404366a6dc4b2f348a85e0066e46f0cc206fca6512e0ed7f17ca7afb88e9a4c27000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000093922dee6e380c28a50c008ab167b7800bb24c2026cd1b22f1c6fb884ceed7400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000060f85e59ecad6c1a6be343a945abedb7d5b5bfad7817c4d8cc668da7d391faf700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000093dfbf04395fbec1f1aed4ad0f9d3ba880ff58a60485df5d33f8f5e0fb73188600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000aa334a426ea9e21d5f84eb2d4723ca56b92382b9260ab2b6769b7c23d437b6b512322a25cecc954127e60cf91ef056ac1da25f90b73be81c3ff1872fa48d10c7ef1ccb4087bbeedb54b1417a24abbb76f6cd57010a65bb03c7b6602b1eaf0e32c67c54168232d4edc0bfa1b815b2af2a2d0a5c109d675a4f2de684e51df9abb324ab1b19a81bac80f9ce3a45095f3df3a7cf69ef18fc08e94ac3cbc1c7effeacca68e3bfe5d81e26a659b5"; let sig = "sequenceBatchesValidium((bytes32,bytes32,uint64,bytes32)[],uint64,uint64,address,bytes)"; let decoded = Cast::calldata_decode(sig, calldata, true).unwrap(); let json_value = serialize_value_as_json(DynSolValue::Array(decoded), None).unwrap(); let expected = serde_json::json!([ [ [ "0x04366a6dc4b2f348a85e0066e46f0cc206fca6512e0ed7f17ca7afb88e9a4c27", "0x0000000000000000000000000000000000000000000000000000000000000000", 0, "0x0000000000000000000000000000000000000000000000000000000000000000" ], [ "0x093922dee6e380c28a50c008ab167b7800bb24c2026cd1b22f1c6fb884ceed74", "0x0000000000000000000000000000000000000000000000000000000000000000", 0, "0x0000000000000000000000000000000000000000000000000000000000000000" ], [ "0x60f85e59ecad6c1a6be343a945abedb7d5b5bfad7817c4d8cc668da7d391faf7", "0x0000000000000000000000000000000000000000000000000000000000000000", 0, "0x0000000000000000000000000000000000000000000000000000000000000000" ], [ "0x93dfbf04395fbec1f1aed4ad0f9d3ba880ff58a60485df5d33f8f5e0fb731886", "0x0000000000000000000000000000000000000000000000000000000000000000", 0, "0x0000000000000000000000000000000000000000000000000000000000000000" ] ], 1735573273, 132886, "0xAF9d27ffe4d51eD54AC8eEc78f2785D7E11E5ab1", "0x334a426ea9e21d5f84eb2d4723ca56b92382b9260ab2b6769b7c23d437b6b512322a25cecc954127e60cf91ef056ac1da25f90b73be81c3ff1872fa48d10c7ef1ccb4087bbeedb54b1417a24abbb76f6cd57010a65bb03c7b6602b1eaf0e32c67c54168232d4edc0bfa1b815b2af2a2d0a5c109d675a4f2de684e51df9abb324ab1b19a81bac80f9ce3a45095f3df3a7cf69ef18fc08e94ac3cbc1c7effeacca68e3bfe5d81e26a659b5" ]); assert_eq!(json_value, expected); } #[test] fn concat_hex() { assert_eq!(Cast::concat_hex(["0x00", "0x01"]), "0x0001"); assert_eq!(Cast::concat_hex(["1", "2"]), "0x12"); } #[test] fn from_rlp() { let rlp = "0xf8b1a02b5df5f0757397573e8ff34a8b987b21680357de1f6c8d10273aa528a851eaca8080a02838ac1d2d2721ba883169179b48480b2ba4f43d70fcf806956746bd9e83f90380a0e46fff283b0ab96a32a7cc375cecc3ed7b6303a43d64e0a12eceb0bc6bd8754980a01d818c1c414c665a9c9a0e0c0ef1ef87cacb380b8c1f6223cb2a68a4b2d023f5808080a0236e8f61ecde6abfebc6c529441f782f62469d8a2cc47b7aace2c136bd3b1ff08080808080"; let item = Cast::from_rlp(rlp, false).unwrap(); assert_eq!( item, r#"["0x2b5df5f0757397573e8ff34a8b987b21680357de1f6c8d10273aa528a851eaca","0x","0x","0x2838ac1d2d2721ba883169179b48480b2ba4f43d70fcf806956746bd9e83f903","0x","0xe46fff283b0ab96a32a7cc375cecc3ed7b6303a43d64e0a12eceb0bc6bd87549","0x","0x1d818c1c414c665a9c9a0e0c0ef1ef87cacb380b8c1f6223cb2a68a4b2d023f5","0x","0x","0x","0x236e8f61ecde6abfebc6c529441f782f62469d8a2cc47b7aace2c136bd3b1ff0","0x","0x","0x","0x","0x"]"# ) } #[test] fn disassemble_incomplete_sequence() { let incomplete = &hex!("60"); // PUSH1 let disassembled = Cast::disassemble(incomplete).unwrap(); assert_eq!(disassembled, "00000000: PUSH1\n"); let complete = &hex!("6000"); // PUSH1 0x00 let disassembled = Cast::disassemble(complete).unwrap(); assert_eq!(disassembled, "00000000: PUSH1 0x00\n"); let incomplete = &hex!("7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"); // PUSH32 with 31 bytes let disassembled = Cast::disassemble(incomplete).unwrap(); assert_eq!(disassembled, "00000000: PUSH32\n"); let complete = &hex!("7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"); // PUSH32 with 32 bytes let disassembled = Cast::disassemble(complete).unwrap(); assert_eq!( disassembled, "00000000: PUSH32 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\n" ); } } ================================================ FILE: crates/cast/src/opts.rs ================================================ use crate::cmd::{ access_list::AccessListArgs, artifact::ArtifactArgs, b2e_payload::B2EPayloadArgs, bind::BindArgs, call::CallArgs, constructor_args::ConstructorArgsArgs, create2::Create2Args, creation_code::CreationCodeArgs, da_estimate::DAEstimateArgs, erc20::Erc20Subcommand, estimate::EstimateArgs, find_block::FindBlockArgs, interface::InterfaceArgs, logs::LogsArgs, mktx::MakeTxArgs, rpc::RpcArgs, run::RunArgs, send::SendTxArgs, storage::StorageArgs, trace::TraceArgs, txpool::TxPoolSubcommands, wallet::WalletSubcommands, }; use alloy_ens::NameOrAddress; use alloy_primitives::{Address, B256, Selector, U256}; use alloy_rpc_types::BlockId; use clap::{ArgAction, Parser, Subcommand, ValueHint}; use eyre::Result; use foundry_cli::opts::{EtherscanOpts, GlobalArgs, NetworkVariant, RpcOpts}; 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. #[derive(Parser)] #[command( name = "cast", version = SHORT_VERSION, long_version = LONG_VERSION, after_help = "Find more information in the book: https://getfoundry.sh/cast/overview", next_display_order = None, )] pub struct Cast { /// Include the global arguments. #[command(flatten)] pub global: GlobalArgs, #[command(subcommand)] pub cmd: CastSubcommand, } #[derive(Subcommand)] pub enum CastSubcommand { /// Prints the maximum value of the given integer type. #[command(visible_aliases = &["--max-int", "maxi"])] MaxInt { /// The integer type to get the maximum value of. #[arg(default_value = "int256")] r#type: String, }, /// Prints the minimum value of the given integer type. #[command(visible_aliases = &["--min-int", "mini"])] MinInt { /// The integer type to get the minimum value of. #[arg(default_value = "int256")] r#type: String, }, /// Prints the maximum value of the given integer type. #[command(visible_aliases = &["--max-uint", "maxu"])] MaxUint { /// The unsigned integer type to get the maximum value of. #[arg(default_value = "uint256")] r#type: String, }, /// Prints the zero address. #[command(visible_aliases = &["--address-zero", "az"])] AddressZero, /// Prints the zero hash. #[command(visible_aliases = &["--hash-zero", "hz"])] HashZero, /// Convert UTF8 text to hex. #[command( visible_aliases = &[ "--from-ascii", "--from-utf8", "from-ascii", "fu", "fa"] )] FromUtf8 { /// The text to convert. text: Option, }, /// Concatenate hex strings. #[command(visible_aliases = &["--concat-hex", "ch"])] ConcatHex { /// The data to concatenate. data: Vec, }, /// Convert binary data into hex data. #[command(visible_aliases = &["--from-bin", "from-binx", "fb"])] FromBin, /// Normalize the input to lowercase, 0x-prefixed hex. /// /// The input can be: /// - mixed case hex with or without 0x prefix /// - 0x prefixed hex, concatenated with a ':' /// - an absolute path to file /// - @tag, where the tag is defined in an environment variable #[command(visible_aliases = &["--to-hexdata", "thd", "2hd"])] ToHexdata { /// The input to normalize. input: Option, }, /// Convert an address to a checksummed format (EIP-55). #[command( visible_aliases = &["--to-checksum-address", "--to-checksum", "to-checksum", "ta", "2a"] )] ToCheckSumAddress { /// The address to convert. address: Option
, /// EIP-155 chain ID to encode the address using EIP-1191. chain_id: Option, }, /// Convert hex data to an ASCII string. #[command(visible_aliases = &["--to-ascii", "tas", "2as"])] ToAscii { /// The hex data to convert. hexdata: Option, }, /// Convert hex data to a utf-8 string. #[command(visible_aliases = &["--to-utf8", "tu8", "2u8"])] ToUtf8 { /// The hex data to convert. hexdata: Option, }, /// Convert a fixed point number into an integer. #[command(visible_aliases = &["--from-fix", "ff"])] FromFixedPoint { /// The number of decimals to use. decimals: Option, /// The value to convert. #[arg(allow_hyphen_values = true)] value: Option, }, /// Right-pads hex data to 32 bytes. #[command(visible_aliases = &["--to-bytes32", "tb", "2b"])] ToBytes32 { /// The hex data to convert. bytes: Option, }, /// Pads hex data to a specified length. #[command(visible_aliases = &["pd"])] Pad { /// The hex data to pad. data: Option, /// Right-pad the data (instead of left-pad). #[arg(long)] right: bool, /// Left-pad the data (default). #[arg(long, conflicts_with = "right")] left: bool, /// Target length in bytes (default: 32). #[arg(long, default_value = "32")] len: usize, }, /// Convert an integer into a fixed point number. #[command(visible_aliases = &["--to-fix", "tf", "2f"])] ToFixedPoint { /// The number of decimals to use. decimals: Option, /// The value to convert. #[arg(allow_hyphen_values = true)] value: Option, }, /// Convert a number to a hex-encoded uint256. #[command(name = "to-uint256", visible_aliases = &["--to-uint256", "tu", "2u"])] ToUint256 { /// The value to convert. value: Option, }, /// Convert a number to a hex-encoded int256. #[command(name = "to-int256", visible_aliases = &["--to-int256", "ti", "2i"])] ToInt256 { /// The value to convert. value: Option, }, /// Perform a left shifting operation #[command(name = "shl")] LeftShift { /// The value to shift. value: String, /// The number of bits to shift. bits: String, /// The input base. #[arg(long)] base_in: Option, /// The output base. #[arg(long, default_value = "16")] base_out: String, }, /// Perform a right shifting operation #[command(name = "shr")] RightShift { /// The value to shift. value: String, /// The number of bits to shift. bits: String, /// The input base, #[arg(long)] base_in: Option, /// The output base, #[arg(long, default_value = "16")] base_out: String, }, /// Convert an ETH amount into another unit (ether, gwei or wei). /// /// Examples: /// - 1ether wei /// - "1 ether" wei /// - 1ether /// - 1 gwei /// - 1gwei ether #[command(visible_aliases = &["--to-unit", "tun", "2un"])] ToUnit { /// The value to convert. value: Option, /// The unit to convert to (ether, gwei, wei). #[arg(default_value = "wei")] unit: String, }, /// Convert a number from decimal to smallest unit with arbitrary decimals. /// /// Examples: /// - 1.0 6 (for USDC, result: 1000000) /// - 2.5 12 (for 12 decimals token, result: 2500000000000) /// - 1.23 3 (for 3 decimals token, result: 1230) #[command(visible_aliases = &["--parse-units", "pun"])] ParseUnits { /// The value to convert. value: Option, /// The unit to convert to. #[arg(default_value = "18")] unit: u8, }, /// Format a number from smallest unit to decimal with arbitrary decimals. /// /// Examples: /// - 1000000 6 (for USDC, result: 1.0) /// - 2500000000000 12 (for 12 decimals, result: 2.5) /// - 1230 3 (for 3 decimals, result: 1.23) #[command(visible_aliases = &["--format-units", "fun"])] FormatUnits { /// The value to format. value: Option, /// The unit to format to. #[arg(default_value = "18")] unit: u8, }, /// Convert an ETH amount to wei. /// /// Consider using --to-unit. #[command(visible_aliases = &["--to-wei", "tw", "2w"])] ToWei { /// The value to convert. #[arg(allow_hyphen_values = true)] value: Option, /// The unit to convert from (ether, gwei, wei). #[arg(default_value = "eth")] unit: String, }, /// Convert wei into an ETH amount. /// /// Consider using --to-unit. #[command(visible_aliases = &["--from-wei", "fw"])] FromWei { /// The value to convert. #[arg(allow_hyphen_values = true)] value: Option, /// The unit to convert from (ether, gwei, wei). #[arg(default_value = "eth")] unit: String, }, /// RLP encodes hex data, or an array of hex data. /// /// Accepts a hex-encoded string, or an array of hex-encoded strings. /// Can be arbitrarily recursive. /// /// Examples: /// - `cast to-rlp "[]"` -> `0xc0` /// - `cast to-rlp "0x22"` -> `0x22` /// - `cast to-rlp "[\"0x61\"]"` -> `0xc161` /// - `cast to-rlp "[\"0xf1\", \"f2\"]"` -> `0xc481f181f2` #[command(visible_aliases = &["--to-rlp"])] ToRlp { /// The value to convert. /// /// This is a hex-encoded string, or an array of hex-encoded strings. /// Can be arbitrarily recursive. value: Option, }, /// Decodes RLP hex-encoded data. #[command(visible_aliases = &["--from-rlp"])] FromRlp { /// The RLP hex-encoded data. value: Option, /// Decode the RLP data as int #[arg(long, alias = "int")] as_int: bool, }, /// Converts a number of one base to another #[command(visible_aliases = &["--to-hex", "th", "2h"])] ToHex(ToBaseArgs), /// Converts a number of one base to decimal #[command(visible_aliases = &["--to-dec", "td", "2d"])] ToDec(ToBaseArgs), /// Converts a number of one base to another #[command( visible_aliases = &["--to-base", "--to-radix", "to-radix", "tr", "2r"] )] ToBase { #[command(flatten)] base: ToBaseArgs, /// The output base. #[arg(value_name = "BASE")] base_out: Option, }, /// Create an access list for a transaction. #[command(visible_aliases = &["ac", "acl"])] AccessList(AccessListArgs), /// Get logs by signature or topic. #[command(visible_alias = "l")] Logs(LogsArgs), /// Get information about a block. #[command(visible_alias = "bl")] Block { /// The block height to query at. /// /// Can also be the tags earliest, finalized, safe, latest, or pending. block: Option, /// If specified, only get the given field of the block. #[arg(short, long = "field", aliases = ["fields"], num_args = 0.., action = ArgAction::Append, value_delimiter = ',')] fields: Vec, /// Print the raw RLP encoded block header. #[arg(long, conflicts_with = "fields")] raw: bool, #[arg(long, env = "CAST_FULL_BLOCK")] full: bool, #[command(flatten)] rpc: RpcOpts, /// Specify the Network for correct encoding. #[arg(long, short, num_args = 1, value_name = "NETWORK")] network: Option, }, /// Get the latest block number. #[command(visible_alias = "bn")] BlockNumber { /// The hash or tag to query. If not specified, the latest number is returned. block: Option, #[command(flatten)] rpc: RpcOpts, }, /// Perform a call on an account without publishing a transaction. #[command(visible_alias = "c")] Call(CallArgs), /// ABI-encode a function with arguments. #[command(name = "calldata", visible_alias = "cd")] CalldataEncode { /// The function signature in the format `()()` sig: String, /// The arguments to encode. #[arg(allow_hyphen_values = true)] args: Vec, // Path to file containing arguments to encode. #[arg(long, value_name = "PATH")] file: Option, }, /// Get the symbolic name of the current chain. Chain { #[command(flatten)] rpc: RpcOpts, }, /// Get the Ethereum chain ID. #[command(visible_aliases = &["ci", "cid"])] ChainId { #[command(flatten)] rpc: RpcOpts, }, /// Get the current client version. #[command(visible_alias = "cl")] Client { #[command(flatten)] rpc: RpcOpts, }, /// Compute the contract address from a given nonce and deployer address. #[command(visible_alias = "ca")] ComputeAddress { /// The deployer address. address: Option
, /// The nonce of the deployer address. #[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, }, /// Disassembles a hex-encoded bytecode into a human-readable representation. #[command(visible_alias = "da")] Disassemble { /// The hex-encoded bytecode. bytecode: Option, }, /// Build and sign a transaction. #[command(name = "mktx", visible_alias = "m")] MakeTx(MakeTxArgs), /// Calculate the ENS namehash of a name. #[command(visible_aliases = &["na", "nh"])] Namehash { name: Option }, /// Get information about a transaction. #[command(visible_alias = "t")] Tx { /// The transaction hash. tx_hash: Option, /// The sender of the transaction. #[arg(long, value_parser = NameOrAddress::from_str)] from: Option, /// Nonce of the transaction. #[arg(long)] nonce: Option, /// If specified, only get the given field of the transaction. If "raw", the RLP encoded /// transaction will be printed. field: Option, /// Print the raw RLP encoded transaction. #[arg(long, conflicts_with = "field")] raw: bool, #[command(flatten)] rpc: RpcOpts, /// If specified, the transaction will be converted to a TransactionRequest JSON format. #[arg(long)] to_request: bool, /// Specify the Network for correct encoding. #[arg(long, short, num_args = 1, value_name = "NETWORK")] network: Option, }, /// Get the transaction receipt for a transaction. #[command(visible_alias = "re")] Receipt { /// The transaction hash. tx_hash: String, /// If specified, only get the given field of the transaction. field: Option, /// The number of confirmations until the receipt is fetched #[arg(long, default_value = "1")] confirmations: u64, /// Exit immediately if the transaction was not found. #[arg(id = "async", long = "async", env = "CAST_ASYNC", alias = "cast-async")] cast_async: bool, #[command(flatten)] rpc: RpcOpts, }, /// Sign and publish a transaction. #[command(name = "send", visible_alias = "s")] SendTx(SendTxArgs), /// Publish a raw transaction to the network. #[command(name = "publish", visible_alias = "p")] PublishTx { /// The raw transaction raw_tx: String, /// Only print the transaction hash and exit immediately. #[arg(id = "async", long = "async", env = "CAST_ASYNC", alias = "cast-async")] cast_async: bool, #[command(flatten)] rpc: RpcOpts, }, /// Estimate the gas cost of a transaction. #[command(visible_alias = "e")] Estimate(EstimateArgs), /// Decode ABI-encoded input data. /// /// Similar to `abi-decode --input`, but function selector MUST be prefixed in `calldata` /// string #[command(visible_aliases = &["calldata-decode", "--calldata-decode", "cdd"])] DecodeCalldata { /// The function signature in the format `()()`. sig: String, /// The ABI-encoded calldata. #[arg(required_unless_present = "file", index = 2)] calldata: Option, /// Load ABI-encoded calldata from a file instead. #[arg(long = "file", short = 'f', conflicts_with = "calldata")] file: Option, }, /// Decode ABI-encoded string. /// /// Similar to `calldata-decode --input`, but the function argument is a `string` #[command(visible_aliases = &["string-decode", "--string-decode", "sd"])] DecodeString { /// The ABI-encoded string. data: String, }, /// Decode event data. #[command(visible_aliases = &["event-decode", "--event-decode", "ed"])] DecodeEvent { /// The event signature. If none provided then tries to decode from local cache or . #[arg(long, visible_alias = "event-sig")] sig: Option, /// The event data to decode. data: String, }, /// Decode custom error data. #[command(visible_aliases = &["error-decode", "--error-decode", "erd"])] DecodeError { /// The error signature. If none provided then tries to decode from local cache or . #[arg(long, visible_alias = "error-sig")] sig: Option, /// The error data to decode. data: String, }, /// Decode ABI-encoded input or output data. /// /// Defaults to decoding output data. To decode input data pass --input. /// /// When passing `--input`, function selector must NOT be prefixed in `calldata` string #[command(name = "decode-abi", visible_aliases = &["abi-decode", "--abi-decode", "ad"])] DecodeAbi { /// The function signature in the format `()()`. sig: String, /// The ABI-encoded calldata. calldata: String, /// Whether to decode the input or output data. #[arg(long, short, help_heading = "Decode input data instead of output data")] input: bool, }, /// ABI encode the given function argument, excluding the selector. #[command(visible_alias = "ae")] AbiEncode { /// The function signature. sig: String, /// Whether to use packed encoding. #[arg(long)] packed: bool, /// The arguments of the function. #[arg(allow_hyphen_values = true)] args: Vec, }, /// ABI encode an event and its arguments to generate topics and data. #[command(visible_alias = "aee")] AbiEncodeEvent { /// The event signature. sig: String, /// The arguments of the event. #[arg(allow_hyphen_values = true)] args: Vec, }, /// Compute the storage slot for an entry in a mapping. #[command(visible_alias = "in")] Index { /// The mapping key type. key_type: String, /// The mapping key. key: String, /// The storage slot of the mapping. slot_number: String, }, /// Compute storage slots as specified by `ERC-7201: Namespaced Storage Layout`. #[command(name = "index-erc7201", alias = "index-erc-7201", visible_aliases = &["index7201", "in7201"])] IndexErc7201 { /// The arbitrary identifier. id: Option, /// The formula ID. Currently the only supported formula is `erc7201`. #[arg(long, default_value = "erc7201")] formula_id: String, }, /// Fetch the EIP-1967 implementation for a contract /// Can read from the implementation slot or the beacon slot. #[command(visible_alias = "impl")] Implementation { /// The block height to query at. /// /// Can also be the tags earliest, finalized, safe, latest, or pending. #[arg(long, short = 'B')] block: Option, /// Fetch the implementation from the beacon slot. /// /// If not specified, the implementation slot is used. #[arg(long)] beacon: bool, /// The address for which the implementation will be fetched. #[arg(value_parser = NameOrAddress::from_str)] who: NameOrAddress, #[command(flatten)] rpc: RpcOpts, }, /// Fetch the EIP-1967 admin account #[command(visible_alias = "adm")] Admin { /// The block height to query at. /// /// Can also be the tags earliest, finalized, safe, latest, or pending. #[arg(long, short = 'B')] block: Option, /// The address from which the admin account will be fetched. #[arg(value_parser = NameOrAddress::from_str)] who: NameOrAddress, #[command(flatten)] rpc: RpcOpts, }, /// Get the function signatures for the given selector from . #[command(name = "4byte", visible_aliases = &["4", "4b"])] FourByte { /// The function selector. selector: Option, }, /// Decode ABI-encoded calldata using . #[command(name = "4byte-calldata", aliases = &["4byte-decode", "4d", "4bd"], visible_aliases = &["4c", "4bc"])] FourByteCalldata { /// The ABI-encoded calldata. calldata: Option, }, /// Get the event signature for a given topic 0 from . #[command(name = "4byte-event", visible_aliases = &["4e", "4be", "topic0-event", "t0e"])] FourByteEvent { /// Topic 0 #[arg(value_name = "TOPIC_0")] topic: Option, }, /// Upload the given signatures to . /// /// Example inputs: /// - "transfer(address,uint256)" /// - "function transfer(address,uint256)" /// - "function transfer(address,uint256)" "event Transfer(address,address,uint256)" /// - "./out/Contract.sol/Contract.json" #[command(visible_aliases = &["ups"])] UploadSignature { /// The signatures to upload. /// /// Prefix with 'function', 'event', or 'error'. Defaults to function if no prefix given. /// Can also take paths to contract artifact JSON. signatures: Vec, }, /// Pretty print calldata. /// /// Tries to decode the calldata using unless --offline is passed. #[command(visible_alias = "pc")] PrettyCalldata { /// The calldata. calldata: Option, /// Skip the lookup. #[arg(long, short)] offline: bool, }, /// Get the timestamp of a block. #[command(visible_alias = "a")] Age { /// The block height to query at. /// /// Can also be the tags earliest, finalized, safe, latest, or pending. block: Option, #[command(flatten)] rpc: RpcOpts, }, /// Get the balance of an account in wei. #[command(visible_alias = "b")] Balance { /// The block height to query at. /// /// Can also be the tags earliest, finalized, safe, latest, or pending. #[arg(long, short = 'B')] block: Option, /// The account to query. #[arg(value_parser = NameOrAddress::from_str)] who: NameOrAddress, /// Format the balance in ether. #[arg(long, short)] ether: bool, #[command(flatten)] rpc: RpcOpts, /// erc20 address to query, with the method `balanceOf(address) return (uint256)`, alias /// with '--erc721' #[arg(long, alias = "erc721")] erc20: Option
, }, /// Get the basefee of a block. #[command(visible_aliases = &["ba", "fee", "basefee"])] BaseFee { /// The block height to query at. /// /// Can also be the tags earliest, finalized, safe, latest, or pending. block: Option, #[command(flatten)] rpc: RpcOpts, }, /// Get the runtime bytecode of a contract. #[command(visible_alias = "co")] Code { /// The block height to query at. /// /// Can also be the tags earliest, finalized, safe, latest, or pending. #[arg(long, short = 'B')] block: Option, /// The contract address. #[arg(value_parser = NameOrAddress::from_str)] who: NameOrAddress, /// Disassemble bytecodes. #[arg(long, short)] disassemble: bool, #[command(flatten)] rpc: RpcOpts, }, /// Get the runtime bytecode size of a contract. #[command(visible_alias = "cs")] Codesize { /// The block height to query at. /// /// Can also be the tags earliest, finalized, safe, latest, or pending. #[arg(long, short = 'B')] block: Option, /// The contract address. #[arg(value_parser = NameOrAddress::from_str)] who: NameOrAddress, #[command(flatten)] rpc: RpcOpts, }, /// Get the current gas price. #[command(visible_alias = "g")] GasPrice { #[command(flatten)] rpc: RpcOpts, }, /// Generate event signatures from event string. #[command(visible_alias = "se")] SigEvent { /// The event string. event_string: Option, }, /// Hash arbitrary data using Keccak-256. #[command(visible_aliases = &["k", "keccak256"])] Keccak { /// The data to hash. data: Option, }, /// Hash a message according to EIP-191. #[command(visible_aliases = &["--hash-message", "hm"])] HashMessage { /// The message to hash. message: Option, }, /// Perform an ENS lookup. #[command(visible_alias = "rn")] ResolveName { /// The name to lookup. who: Option, /// Perform a reverse lookup to verify that the name is correct. #[arg(long)] verify: bool, #[command(flatten)] rpc: RpcOpts, }, /// Perform an ENS reverse lookup. #[command(visible_alias = "la")] LookupAddress { /// The account to perform the lookup for. who: Option
, /// Perform a normal lookup to verify that the address is correct. #[arg(long)] verify: bool, #[command(flatten)] rpc: RpcOpts, }, /// Get the raw value of a contract's storage slot. #[command(visible_alias = "st")] Storage(StorageArgs), /// Generate a storage proof for a given storage slot. #[command(visible_alias = "pr")] Proof { /// The contract address. #[arg(value_parser = NameOrAddress::from_str)] address: NameOrAddress, /// The storage slot numbers (hex or decimal). #[arg(value_parser = parse_slot)] slots: Vec, /// The block height to query at. /// /// Can also be the tags earliest, finalized, safe, latest, or pending. #[arg(long, short = 'B')] block: Option, #[command(flatten)] rpc: RpcOpts, }, /// Get the nonce for an account. #[command(visible_alias = "n")] Nonce { /// The block height to query at. /// /// Can also be the tags earliest, finalized, safe, latest, or pending. #[arg(long, short = 'B')] block: Option, /// The address to get the nonce for. #[arg(value_parser = NameOrAddress::from_str)] who: NameOrAddress, #[command(flatten)] rpc: RpcOpts, }, /// Get the codehash for an account. #[command()] Codehash { /// The block height to query at. /// /// Can also be the tags earliest, finalized, safe, latest, or pending. #[arg(long, short = 'B')] block: Option, /// The address to get the codehash for. #[arg(value_parser = NameOrAddress::from_str)] who: NameOrAddress, /// The storage slot numbers (hex or decimal). #[arg(value_parser = parse_slot)] slots: Vec, #[command(flatten)] rpc: RpcOpts, }, /// Get the storage root for an account. #[command(visible_alias = "sr")] StorageRoot { /// The block height to query at. /// /// Can also be the tags earliest, finalized, safe, latest, or pending. #[arg(long, short = 'B')] block: Option, /// The address to get the storage root for. #[arg(value_parser = NameOrAddress::from_str)] who: NameOrAddress, /// The storage slot numbers (hex or decimal). #[arg(value_parser = parse_slot)] slots: Vec, #[command(flatten)] rpc: RpcOpts, }, /// Get the source code of a contract from a block explorer. #[command(visible_aliases = &["et", "src"])] Source { /// The contract's address. address: String, /// Whether to flatten the source code. #[arg(long, short)] flatten: bool, /// The output directory/file to expand source tree into. #[arg(short, value_hint = ValueHint::DirPath, alias = "path")] directory: Option, #[command(flatten)] etherscan: EtherscanOpts, /// Alternative explorer API URL to use that adheres to the Etherscan API. If not provided, /// defaults to Etherscan. #[arg(long, env = "EXPLORER_API_URL")] explorer_api_url: Option, /// Alternative explorer browser URL. #[arg(long, env = "EXPLORER_URL")] explorer_url: Option, }, /// Wallet management utilities. #[command(visible_alias = "w")] Wallet { #[command(subcommand)] command: WalletSubcommands, }, /// Download a contract creation code from Etherscan and RPC. #[command(visible_alias = "cc")] CreationCode(CreationCodeArgs), /// Generate an artifact file, that can be used to deploy a contract locally. #[command(visible_alias = "ar")] Artifact(ArtifactArgs), /// Display constructor arguments used for the contract initialization. #[command(visible_alias = "cra")] ConstructorArgs(ConstructorArgsArgs), /// Generate a Solidity interface from a given ABI. /// /// Currently does not support ABI encoder v2. #[command(visible_alias = "i")] Interface(InterfaceArgs), /// Generate a rust binding from a given ABI. #[command(visible_alias = "bi")] Bind(BindArgs), /// Convert Beacon payload to execution payload. #[command(visible_alias = "b2e")] B2EPayload(B2EPayloadArgs), /// Get the selector for a function. #[command(visible_alias = "si")] Sig { /// The function signature, e.g. transfer(address,uint256). sig: Option, /// Optimize signature to contain provided amount of leading zeroes in selector. optimize: Option, }, /// Generate a deterministic contract address using CREATE2. #[command(visible_alias = "c2")] Create2(Create2Args), /// Get the block number closest to the provided timestamp. #[command(visible_alias = "f")] FindBlock(FindBlockArgs), /// Generate shell completions script. #[command(visible_alias = "com")] Completions { #[arg(value_enum)] shell: foundry_cli::clap::Shell, }, /// Runs a published transaction in a local environment and prints the trace. #[command(visible_alias = "r")] Run(RunArgs), /// Perform a raw JSON-RPC request. #[command(visible_alias = "rp")] Rpc(RpcArgs), /// Formats a string into bytes32 encoding. #[command(name = "format-bytes32-string", visible_aliases = &["--format-bytes32-string"])] FormatBytes32String { /// The string to format. string: Option, }, /// Parses a string from bytes32 encoding. #[command(name = "parse-bytes32-string", visible_aliases = &["--parse-bytes32-string"])] ParseBytes32String { /// The string to parse. bytes: Option, }, #[command(name = "parse-bytes32-address", visible_aliases = &["--parse-bytes32-address"])] #[command(about = "Parses a checksummed address from bytes32 encoding.")] ParseBytes32Address { #[arg(value_name = "BYTES")] bytes: Option, }, /// Decodes a raw signed EIP 2718 typed transaction #[command(visible_aliases = &["dt", "decode-tx"])] DecodeTransaction { tx: Option }, /// Recovery an EIP-7702 authority from a Authorization JSON string. #[command(visible_aliases = &["decode-auth"])] RecoverAuthority { auth: String }, /// Extracts function selectors and arguments from bytecode #[command(visible_alias = "sel")] Selectors { /// The hex-encoded bytecode. bytecode: Option, /// Resolve the function signatures for the extracted selectors using #[arg(long, short)] resolve: bool, }, /// 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), /// ERC20 token operations. #[command(visible_alias = "erc20")] Erc20Token { #[command(subcommand)] command: Erc20Subcommand, }, #[command(name = "trace")] Trace(TraceArgs), } /// CLI arguments for `cast --to-base`. #[derive(Debug, Parser)] pub struct ToBaseArgs { /// The value to convert. #[arg(allow_hyphen_values = true)] pub value: Option, /// The input base. #[arg(long, short = 'i')] pub base_in: Option, } pub fn parse_slot(s: &str) -> Result { let slot = U256::from_str(s).map_err(|e| eyre::eyre!("Could not parse slot number: {e}"))?; Ok(B256::from(slot)) } #[cfg(test)] mod tests { use super::*; use crate::SimpleCast; use alloy_rpc_types::{BlockNumberOrTag, RpcBlockHash}; use clap::CommandFactory; #[test] fn verify_cli() { Cast::command().debug_assert(); } #[test] fn parse_proof_slot() { let args: Cast = Cast::parse_from([ "foundry-cli", "proof", "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", "0", "1", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x1", "0x01", ]); match args.cmd { CastSubcommand::Proof { slots, .. } => { assert_eq!( slots, vec![ B256::ZERO, U256::from(1).into(), B256::ZERO, U256::from(1).into(), U256::from(1).into() ] ); } _ => unreachable!(), }; } #[test] fn parse_call_data() { let args: Cast = Cast::parse_from([ "foundry-cli", "calldata", "f()", "5c9d55b78febcc2061715ba4f57ecf8ea2711f2c", "2", ]); match args.cmd { CastSubcommand::CalldataEncode { args, .. } => { assert_eq!( args, vec!["5c9d55b78febcc2061715ba4f57ecf8ea2711f2c".to_string(), "2".to_string()] ) } _ => unreachable!(), }; } #[test] fn parse_call_data_with_file() { let args: Cast = Cast::parse_from(["foundry-cli", "calldata", "f()", "--file", "test.txt"]); match args.cmd { CastSubcommand::CalldataEncode { sig, file, args } => { assert_eq!(sig, "f()".to_string()); assert_eq!(file, Some(PathBuf::from("test.txt"))); assert!(args.is_empty()); } _ => unreachable!(), }; } // #[test] fn parse_signature() { let args: Cast = Cast::parse_from([ "foundry-cli", "sig", "__$_$__$$$$$__$$_$$$_$$__$$___$$(address,address,uint256)", ]); match args.cmd { CastSubcommand::Sig { sig, .. } => { let sig = sig.unwrap(); assert_eq!( sig, "__$_$__$$$$$__$$_$$$_$$__$$___$$(address,address,uint256)".to_string() ); let selector = SimpleCast::get_selector(&sig, 0).unwrap(); assert_eq!(selector.0, "0x23b872dd".to_string()); } _ => unreachable!(), }; } #[test] fn parse_block_ids() { struct TestCase { input: String, expect: BlockId, } let test_cases = [ TestCase { input: "0".to_string(), expect: BlockId::Number(BlockNumberOrTag::Number(0u64)), }, TestCase { input: "0x56462c47c03df160f66819f0a79ea07def1569f8aac0fe91bb3a081159b61b4a" .to_string(), expect: BlockId::Hash(RpcBlockHash::from_hash( "0x56462c47c03df160f66819f0a79ea07def1569f8aac0fe91bb3a081159b61b4a" .parse() .unwrap(), None, )), }, TestCase { input: "latest".to_string(), expect: BlockId::Number(BlockNumberOrTag::Latest), }, TestCase { input: "earliest".to_string(), expect: BlockId::Number(BlockNumberOrTag::Earliest), }, TestCase { input: "pending".to_string(), expect: BlockId::Number(BlockNumberOrTag::Pending), }, TestCase { input: "safe".to_string(), expect: BlockId::Number(BlockNumberOrTag::Safe) }, TestCase { input: "finalized".to_string(), expect: BlockId::Number(BlockNumberOrTag::Finalized), }, ]; for test in test_cases { let result: BlockId = test.input.parse().unwrap(); assert_eq!(result, test.expect); } } } ================================================ FILE: crates/cast/src/rlp_converter.rs ================================================ use alloy_primitives::{U256, hex}; use alloy_rlp::{Buf, Decodable, Encodable, Header}; use eyre::Context; use serde_json::Value; use std::fmt; /// Arbitrary nested data. /// /// - `Item::Array(vec![])` is equivalent to `[]`. /// - `Item::Array(vec![Item::Data(vec![])])` is equivalent to `[""]` or `[null]`. #[derive(Clone, Debug, PartialEq, Eq)] pub enum Item { Data(Vec), Array(Vec), } impl Encodable for Item { fn encode(&self, out: &mut dyn alloy_rlp::BufMut) { match self { Self::Array(arr) => arr.encode(out), Self::Data(data) => <[u8]>::encode(data, out), } } } impl Decodable for Item { fn decode(buf: &mut &[u8]) -> alloy_rlp::Result { let h = Header::decode(buf)?; if buf.len() < h.payload_length { return Err(alloy_rlp::Error::InputTooShort); } let mut d = &buf[..h.payload_length]; let r = if h.list { let view = &mut d; let mut v = Vec::new(); while !view.is_empty() { v.push(Self::decode(view)?); } Ok(Self::Array(v)) } else { Ok(Self::Data(d.to_vec())) }; buf.advance(h.payload_length); r } } impl Item { pub(crate) fn value_to_item(value: &Value) -> eyre::Result { match value { Value::Null => Ok(Self::Data(vec![])), Value::Bool(_) => { eyre::bail!("RLP input can not contain booleans") } Value::Number(n) => { Ok(Self::Data(n.to_string().parse::()?.to_be_bytes_trimmed_vec())) } Value::String(s) => Ok(Self::Data(hex::decode(s).wrap_err("Could not decode hex")?)), Value::Array(values) => values.iter().map(Self::value_to_item).collect(), Value::Object(_) => { eyre::bail!("RLP input can not contain objects") } } } } impl FromIterator for Item { fn from_iter>(iter: T) -> Self { Self::Array(Vec::from_iter(iter)) } } // Display as hex values impl fmt::Display for Item { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> std::fmt::Result { match self { Self::Data(dat) => { write!(f, "\"0x{}\"", hex::encode(dat))?; } Self::Array(items) => { f.write_str("[")?; for (i, item) in items.iter().enumerate() { if i > 0 { f.write_str(",")?; } fmt::Display::fmt(item, f)?; } f.write_str("]")?; } }; Ok(()) } } #[cfg(test)] mod test { use crate::rlp_converter::Item; use alloy_primitives::hex; use alloy_rlp::{Bytes, Decodable}; use serde_json::Result as JsonResult; // https://en.wikipedia.org/wiki/Set-theoretic_definition_of_natural_numbers fn array_von_neuman() -> Item { Item::Array(vec![ Item::Array(vec![]), Item::Array(vec![Item::Array(vec![])]), Item::Array(vec![Item::Array(vec![]), Item::Array(vec![Item::Array(vec![])])]), ]) } #[test] #[expect(clippy::disallowed_macros)] fn encode_decode_test() -> alloy_rlp::Result<()> { let parameters = vec![ (1, b"\xc0".to_vec(), Item::Array(vec![])), (2, b"\xc1\x80".to_vec(), Item::Array(vec![Item::Data(vec![])])), (3, b"\xc4\x83dog".to_vec(), Item::Array(vec![Item::Data(vec![0x64, 0x6f, 0x67])])), ( 4, b"\xc5\xc4\x83dog".to_vec(), Item::Array(vec![Item::Array(vec![Item::Data(vec![0x64, 0x6f, 0x67])])]), ), ( 5, b"\xc8\x83dog\x83cat".to_vec(), Item::Array(vec![ Item::Data(vec![0x64, 0x6f, 0x67]), Item::Data(vec![0x63, 0x61, 0x74]), ]), ), (6, b"\xc7\xc0\xc1\xc0\xc3\xc0\xc1\xc0".to_vec(), array_von_neuman()), ( 7, b"\xcd\x83\x6c\x6f\x6c\xc3\xc2\xc1\xc0\xc4\x83\x6f\x6c\x6f".to_vec(), Item::Array(vec![ Item::Data(vec![b'\x6c', b'\x6f', b'\x6c']), Item::Array(vec![Item::Array(vec![Item::Array(vec![Item::Array(vec![])])])]), Item::Array(vec![Item::Data(vec![b'\x6f', b'\x6c', b'\x6f'])]), ]), ), ]; for params in parameters { let encoded = alloy_rlp::encode(¶ms.2); assert_eq!(Item::decode(&mut &encoded[..])?, params.2); let decoded = Item::decode(&mut ¶ms.1[..])?; assert_eq!(alloy_rlp::encode(&decoded), params.1); println!("case {} validated", params.0) } Ok(()) } #[test] #[expect(clippy::disallowed_macros)] fn deserialize_from_str_test_hex() -> JsonResult<()> { let parameters = vec![ (1, "[\"\"]", Item::Array(vec![Item::Data(vec![])])), (2, "[\"0x646f67\"]", Item::Array(vec![Item::Data(vec![0x64, 0x6f, 0x67])])), ( 3, "[[\"646f67\"]]", Item::Array(vec![Item::Array(vec![Item::Data(vec![0x64, 0x6f, 0x67])])]), ), ( 4, "[\"646f67\",\"0x636174\"]", Item::Array(vec![ Item::Data(vec![0x64, 0x6f, 0x67]), Item::Data(vec![0x63, 0x61, 0x74]), ]), ), (6, "[[],[[]],[[],[[]]]]", array_von_neuman()), ]; for params in parameters { let val = serde_json::from_str(params.1)?; let item = Item::value_to_item(&val).unwrap(); assert_eq!(item, params.2); println!("case {} validated", params.0); } Ok(()) } #[test] fn rlp_data() { // let hex_val_rlp = hex!("820002"); let item = Item::decode(&mut &hex_val_rlp[..]).unwrap(); let data = hex!("0002"); let encoded = alloy_rlp::encode(&data[..]); let decoded: Bytes = alloy_rlp::decode_exact(&encoded[..]).unwrap(); assert_eq!(Item::Data(decoded.to_vec()), item); let hex_val_rlp = hex!("00"); let item = Item::decode(&mut &hex_val_rlp[..]).unwrap(); let data = hex!("00"); let encoded = alloy_rlp::encode(&data[..]); let decoded: Bytes = alloy_rlp::decode_exact(&encoded[..]).unwrap(); assert_eq!(Item::Data(decoded.to_vec()), item); } } ================================================ FILE: crates/cast/src/tx.rs ================================================ use crate::traces::identifier::SignaturesIdentifier; use alloy_consensus::{SidecarBuilder, SimpleCoder}; use alloy_dyn_abi::ErrorExt; use alloy_ens::NameOrAddress; use alloy_json_abi::Function; use alloy_network::{Network, TransactionBuilder}; use alloy_primitives::{Address, B256, Bytes, TxHash, TxKind, U256, hex}; use alloy_provider::{PendingTransactionBuilder, Provider}; use alloy_rpc_types::{AccessList, Authorization, TransactionInputKind}; use alloy_signer::Signer; use alloy_transport::TransportError; use clap::Args; use eyre::{Result, WrapErr}; use foundry_cli::{ opts::{CliAuthorizationList, EthereumOpts, TransactionOpts}, utils::{self, parse_function_args}, }; use foundry_common::{ TransactionReceiptWithRevertReason, fmt::*, get_pretty_receipt_w_reason_attr, shell, }; use foundry_config::{Chain, Config}; use foundry_primitives::FoundryTransactionBuilder; use foundry_wallets::{BrowserWalletOpts, WalletOpts, WalletSigner}; use itertools::Itertools; use serde_json::value::RawValue; use std::{fmt::Write, marker::PhantomData, str::FromStr, time::Duration}; #[derive(Debug, Clone, Args)] pub struct SendTxOpts { /// Only print the transaction hash and exit immediately. #[arg(id = "async", long = "async", alias = "cast-async", env = "CAST_ASYNC")] pub cast_async: bool, /// Wait for transaction receipt synchronously instead of polling. /// Note: uses `eth_sendTransactionSync` which may not be supported by all clients. #[arg(long, conflicts_with = "async")] pub sync: bool, /// The number of confirmations until the receipt is fetched. #[arg(long, default_value = "1")] pub confirmations: u64, /// Timeout for sending the transaction. #[arg(long, env = "ETH_TIMEOUT")] pub timeout: Option, /// Polling interval for transaction receipts (in seconds). #[arg(long, alias = "poll-interval", env = "ETH_POLL_INTERVAL")] pub poll_interval: Option, /// Ethereum options #[command(flatten)] pub eth: EthereumOpts, /// Browser wallet options #[command(flatten)] pub browser: BrowserWalletOpts, } /// Different sender kinds used by [`CastTxBuilder`]. pub enum SenderKind<'a> { /// An address without signer. Used for read-only calls and transactions sent through unlocked /// accounts. Address(Address), /// A reference to a signer. Signer(&'a WalletSigner), /// An owned signer. OwnedSigner(Box), } impl SenderKind<'_> { /// Resolves the name to an Ethereum Address. pub fn address(&self) -> Address { match self { Self::Address(addr) => *addr, Self::Signer(signer) => signer.address(), Self::OwnedSigner(signer) => signer.address(), } } /// Resolves the sender from the wallet options. /// /// This function prefers the `from` field and may return a different address from the /// configured signer /// If from is specified, returns it /// If from is not specified, but there is a signer configured, returns the signer's address /// If from is not specified and there is no signer configured, returns zero address pub async fn from_wallet_opts(opts: WalletOpts) -> Result { if let Some(from) = opts.from { Ok(from.into()) } else if let Ok(signer) = opts.signer().await { Ok(Self::OwnedSigner(Box::new(signer))) } else { Ok(Address::ZERO.into()) } } /// Returns the signer if available. pub fn as_signer(&self) -> Option<&WalletSigner> { match self { Self::Signer(signer) => Some(signer), Self::OwnedSigner(signer) => Some(signer.as_ref()), _ => None, } } } impl From
for SenderKind<'_> { fn from(addr: Address) -> Self { Self::Address(addr) } } impl<'a> From<&'a WalletSigner> for SenderKind<'a> { fn from(signer: &'a WalletSigner) -> Self { Self::Signer(signer) } } impl From for SenderKind<'_> { fn from(signer: WalletSigner) -> Self { Self::OwnedSigner(Box::new(signer)) } } /// Prevents a misconfigured hwlib from sending a transaction that defies user-specified --from pub fn validate_from_address( specified_from: Option
, signer_address: Address, ) -> Result<()> { if let Some(specified_from) = specified_from && specified_from != signer_address { eyre::bail!( "\ The specified sender via CLI/env vars does not match the sender configured via the hardware wallet's HD Path. Please use the `--hd-path ` parameter to specify the BIP32 Path which corresponds to the sender, or let foundry automatically detect it by not specifying any sender address." ) } Ok(()) } /// Initial state. #[derive(Debug)] pub struct InitState; /// State with known [TxKind]. #[derive(Debug)] pub struct ToState { to: Option
, } /// State with known input for the transaction. #[derive(Debug)] pub struct InputState { kind: TxKind, input: Vec, func: Option, } pub struct CastTxSender { provider: P, _phantom: PhantomData, } impl> CastTxSender where N::TransactionRequest: FoundryTransactionBuilder, N::ReceiptResponse: UIfmt + UIfmtReceiptExt, { /// Creates a new Cast instance responsible for sending transactions. pub fn new(provider: P) -> Self { Self { provider, _phantom: PhantomData } } /// Sends a transaction and waits for receipt synchronously pub async fn send_sync(&self, tx: N::TransactionRequest) -> Result { let mut receipt = TransactionReceiptWithRevertReason:: { receipt: self.provider.send_transaction_sync(tx).await?, revert_reason: None, }; // Allow to fail silently let _ = receipt.update_revert_reason(&self.provider).await; self.format_receipt(receipt, None) } /// Sends a transaction to the specified address /// /// # Example /// /// ``` /// use cast::tx::CastTxSender; /// use alloy_primitives::{Address, U256, Bytes}; /// use alloy_serde::WithOtherFields; /// use alloy_rpc_types::{TransactionRequest}; /// use alloy_provider::{RootProvider, ProviderBuilder, network::AnyNetwork}; /// use std::str::FromStr; /// use alloy_sol_types::{sol, SolCall}; /// /// /// sol!( /// function greet(string greeting) public; /// ); /// /// # async fn foo() -> eyre::Result<()> { /// 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(); /// let bytes = Bytes::from_iter(greeting.iter()); /// let gas = U256::from_str("200000").unwrap(); /// let value = U256::from_str("1").unwrap(); /// let nonce = U256::from_str("1").unwrap(); /// let tx = TransactionRequest::default().to(to).input(bytes.into()).from(from); /// let tx = WithOtherFields::new(tx); /// let cast = CastTxSender::new(provider); /// let data = cast.send(tx).await?; /// println!("{:#?}", data); /// # Ok(()) /// # } /// ``` pub async fn send(&self, tx: N::TransactionRequest) -> Result> { let res = self.provider.send_transaction(tx).await?; Ok(res) } /// Sends a raw RLP-encoded transaction via `eth_sendRawTransaction`. /// /// Used for transaction types that the standard Alloy network stack doesn't understand /// (e.g., Tempo transactions). pub async fn send_raw(&self, raw_tx: &[u8]) -> Result> { let res = self.provider.send_raw_transaction(raw_tx).await?; Ok(res) } /// Prints the transaction hash (if async) or waits for the receipt and prints it. /// /// This is the shared "output" path used by both the normal send flow and the browser wallet /// flow (which sends the transaction out-of-band and only has a tx hash). pub async fn print_tx_result( &self, tx_hash: B256, cast_async: bool, confs: u64, timeout: u64, ) -> Result<()> { if cast_async { sh_println!("{tx_hash:#x}")?; } else { let receipt = self.receipt(format!("{tx_hash:#x}"), None, confs, Some(timeout), false).await?; sh_println!("{receipt}")?; } Ok(()) } /// # Example /// /// ``` /// use alloy_provider::{ProviderBuilder, RootProvider, network::AnyNetwork}; /// use cast::tx::CastTxSender; /// /// async fn foo() -> eyre::Result<()> { /// let provider = /// ProviderBuilder::<_, _, AnyNetwork>::default().connect("http://localhost:8545").await?; /// let cast = CastTxSender::new(provider); /// let tx_hash = "0xf8d1713ea15a81482958fb7ddf884baee8d3bcc478c5f2f604e008dc788ee4fc"; /// let receipt = cast.receipt(tx_hash.to_string(), None, 1, None, false).await?; /// println!("{}", receipt); /// # Ok(()) /// # } /// ``` pub async fn receipt( &self, tx_hash: String, field: Option, confs: u64, timeout: Option, cast_async: bool, ) -> Result { let tx_hash = TxHash::from_str(&tx_hash).wrap_err("invalid tx hash")?; let mut receipt = TransactionReceiptWithRevertReason:: { receipt: match self.provider.get_transaction_receipt(tx_hash).await? { Some(r) => r, None => { // if the async flag is provided, immediately exit if no tx is found, otherwise // try to poll for it if cast_async { eyre::bail!("tx not found: {:?}", tx_hash) } else { PendingTransactionBuilder::::new(self.provider.root().clone(), tx_hash) .with_required_confirmations(confs) .with_timeout(timeout.map(Duration::from_secs)) .get_receipt() .await? } } }, revert_reason: None, }; // Allow to fail silently let _ = receipt.update_revert_reason(&self.provider).await; self.format_receipt(receipt, field) } /// Helper method to format transaction receipts consistently fn format_receipt( &self, receipt: TransactionReceiptWithRevertReason, field: Option, ) -> Result { Ok(if let Some(ref field) = field { get_pretty_receipt_w_reason_attr(&receipt, field) .ok_or_else(|| eyre::eyre!("invalid receipt field: {}", field))? } else if shell::is_json() { // to_value first to sort json object keys serde_json::to_value(&receipt)?.to_string() } else { receipt.pretty() }) } } /// Builder type constructing generic TransactionRequest from cast send/mktx inputs. /// /// It is implemented as a stateful builder with expected state transition of [InitState] -> /// [ToState] -> [InputState]. #[derive(Debug)] pub struct CastTxBuilder { provider: P, tx: N::TransactionRequest, /// Whether the transaction should be sent as a legacy transaction. legacy: bool, blob: bool, /// Whether the blob transaction should use EIP-4844 (legacy) format instead of EIP-7594. eip4844: bool, /// Whether to fill gas, fees and nonce. Set to `false` for read-only calls /// (eth_call, eth_estimateGas, eth_createAccessList). fill: bool, auth: Vec, chain: Chain, etherscan_api_key: Option, access_list: Option>, state: S, } impl> CastTxBuilder where N::TransactionRequest: FoundryTransactionBuilder, { /// Creates a new instance of [CastTxBuilder] filling transaction with fields present in /// provided [TransactionOpts]. pub async fn new(provider: P, tx_opts: TransactionOpts, config: &Config) -> Result { let mut tx = N::TransactionRequest::default(); let chain = utils::get_chain(config.chain, &provider).await?; let etherscan_api_key = config.get_etherscan_api_key(Some(chain)); // 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_empty()); // Apply gas, value, fee, and network-specific options. tx_opts.apply::(&mut tx, legacy); Ok(Self { provider, tx, legacy, blob: tx_opts.blob, eip4844: tx_opts.eip4844, fill: true, chain, etherscan_api_key, auth: tx_opts.auth, access_list: tx_opts.access_list, state: InitState, }) } /// Sets [TxKind] for this builder and changes state to [ToState]. pub async fn with_to(self, to: Option) -> Result> { let to = if let Some(to) = to { Some(to.resolve(&self.provider).await?) } else { None }; Ok(CastTxBuilder { provider: self.provider, tx: self.tx, legacy: self.legacy, blob: self.blob, eip4844: self.eip4844, fill: self.fill, chain: self.chain, etherscan_api_key: self.etherscan_api_key, auth: self.auth, access_list: self.access_list, state: ToState { to }, }) } } impl> CastTxBuilder where N::TransactionRequest: FoundryTransactionBuilder, { /// Accepts user-provided code, sig and args params and constructs calldata for the transaction. /// If code is present, input will be set to code + encoded constructor arguments. If no code is /// present, input is set to just provided arguments. pub async fn with_code_sig_and_args( self, code: Option, sig: Option, args: Vec, ) -> Result> { let (mut args, func) = if let Some(sig) = sig { parse_function_args( &sig, args, self.state.to, self.chain, &self.provider, self.etherscan_api_key.as_deref(), ) .await? } else { (Vec::new(), None) }; let input = if let Some(code) = &code { let mut code = hex::decode(code)?; code.append(&mut args); code } else { args }; if self.state.to.is_none() && code.is_none() { let has_value = self.tx.value().is_some_and(|v| !v.is_zero()); let has_auth = !self.auth.is_empty(); // We only allow user to omit the recipient address if transaction is an EIP-7702 tx // without a value. if !has_auth || has_value { eyre::bail!("Must specify a recipient address or contract code to deploy"); } } Ok(CastTxBuilder { provider: self.provider, tx: self.tx, legacy: self.legacy, blob: self.blob, eip4844: self.eip4844, fill: self.fill, chain: self.chain, etherscan_api_key: self.etherscan_api_key, auth: self.auth, access_list: self.access_list, state: InputState { kind: self.state.to.into(), input, func }, }) } } impl> CastTxBuilder where N::TransactionRequest: FoundryTransactionBuilder, { /// Builds the TransactionRequest. Fills gas, fees and nonce unless [`raw`](Self::raw) was /// called. pub async fn build( self, sender: impl Into>, ) -> Result<(N::TransactionRequest, Option)> { let fill = self.fill; self._build(sender, fill).await } async fn _build( mut self, sender: impl Into>, fill: bool, ) -> Result<(N::TransactionRequest, Option)> { // prepare let sender = sender.into(); self.prepare(&sender); // resolve let tx_nonce = self.resolve_nonce(sender.address(), fill).await?; self.resolve_auth(&sender, tx_nonce).await?; self.resolve_access_list().await?; // fill if fill { self.fill_fees().await?; } Ok((self.tx, self.state.func)) } /// Sets the core transaction fields from the builder state: kind, input, from, and chain id. fn prepare(&mut self, sender: &SenderKind<'_>) { self.tx.set_kind(self.state.kind); // We set both fields to the same value because some nodes only accept the legacy // `data` field: https://github.com/foundry-rs/foundry/issues/7764#issuecomment-2210453249 self.tx.set_input_kind(self.state.input.clone(), TransactionInputKind::Both); self.tx.set_from(sender.address()); self.tx.set_chain_id(self.chain.id()); } /// Resolves the transaction nonce. Returns the existing nonce or fetches one from the /// provider. Only sets it on the transaction when `fill` is true. async fn resolve_nonce(&mut self, from: Address, fill: bool) -> Result { if let Some(nonce) = self.tx.nonce() { Ok(nonce) } else { let nonce = self.provider.get_transaction_count(from).await?; if fill { self.tx.set_nonce(nonce); } Ok(nonce) } } /// Resolves the access list. Fetches from the provider if `--access-list` was passed without /// a value. async fn resolve_access_list(&mut self) -> Result<()> { if let Some(access_list) = match self.access_list.take() { None => None, Some(None) => Some(self.provider.create_access_list(&self.tx).await?.access_list), Some(Some(access_list)) => Some(access_list), } { self.tx.set_access_list(access_list); } Ok(()) } /// Parses the passed --auth values and sets the authorization list on the transaction. /// /// If a signer is available in `sender`, address-based auths will be signed. /// If no signer is available, all auths must be pre-signed. async fn resolve_auth(&mut self, sender: &SenderKind<'_>, tx_nonce: u64) -> Result<()> { if self.auth.is_empty() { return Ok(()); } let auths = std::mem::take(&mut self.auth); // Validate that at most one address-based auth is provided (multiple addresses are // almost always unintended). let address_auth_count = auths.iter().filter(|a| matches!(a, CliAuthorizationList::Address(_))).count(); if address_auth_count > 1 { eyre::bail!( "Multiple address-based authorizations provided. Only one address can be specified; \ use pre-signed authorizations (hex-encoded) for multiple authorizations." ); } let mut signed_auths = Vec::with_capacity(auths.len()); for auth in auths { let signed_auth = match auth { CliAuthorizationList::Address(address) => { let auth = Authorization { chain_id: U256::from(self.chain.id()), nonce: tx_nonce + 1, address, }; let Some(signer) = sender.as_signer() else { eyre::bail!( "No signer available to sign authorization. \ Provide a pre-signed authorization (hex-encoded) instead." ); }; let signature = signer.sign_hash(&auth.signature_hash()).await?; auth.into_signed(signature) } CliAuthorizationList::Signed(auth) => auth, }; signed_auths.push(signed_auth); } self.tx.set_authorization_list(signed_auths); Ok(()) } /// Fills gas price, EIP-1559 fees, blob fees, and gas limit from the provider. /// /// Only fills values that haven't been explicitly set by the user. async fn fill_fees(&mut self) -> Result<()> { if self.legacy && self.tx.gas_price().is_none() { self.tx.set_gas_price(self.provider.get_gas_price().await?); } if self.blob && self.tx.max_fee_per_blob_gas().is_none() { self.tx.set_max_fee_per_blob_gas(self.provider.get_blob_base_fee().await?) } if !self.legacy && (self.tx.max_fee_per_gas().is_none() || self.tx.max_priority_fee_per_gas().is_none()) { let estimate = self.provider.estimate_eip1559_fees().await?; if self.tx.max_fee_per_gas().is_none() { self.tx.set_max_fee_per_gas(estimate.max_fee_per_gas); } if self.tx.max_priority_fee_per_gas().is_none() { self.tx.set_max_priority_fee_per_gas(estimate.max_priority_fee_per_gas); } } if self.tx.gas_limit().is_none() { self.estimate_gas().await?; } Ok(()) } /// Estimate tx gas from provider call. Tries to decode custom error if execution reverted. async fn estimate_gas(&mut self) -> Result<()> { match self.provider.estimate_gas(self.tx.clone()).await { Ok(estimated) => { self.tx.set_gas_limit(estimated); Ok(()) } Err(err) => { if let TransportError::ErrorResp(payload) = &err { // If execution reverted with code 3 during provider gas estimation then try // to decode custom errors and append it to the error message. if payload.code == 3 && let Some(data) = &payload.data && let Ok(Some(decoded_error)) = decode_execution_revert(data).await { eyre::bail!("Failed to estimate gas: {}: {}", err, decoded_error) } } eyre::bail!("Failed to estimate gas: {}", err) } } } /// Populates the blob sidecar for the transaction if any blob data was provided. pub fn with_blob_data(mut self, blob_data: Option>) -> Result { let Some(blob_data) = blob_data else { return Ok(self) }; let mut coder = SidecarBuilder::::default(); coder.ingest(&blob_data); if self.eip4844 { let sidecar = coder.build_4844()?; self.tx.set_blob_sidecar_4844(sidecar); } else { let sidecar = coder.build_7594()?; self.tx.set_blob_sidecar_7594(sidecar); } Ok(self) } /// Skips gas, fee and nonce filling. Use for read-only calls /// (eth_call, eth_estimateGas, eth_createAccessList). pub fn raw(mut self) -> Self { self.fill = false; self } } /// Helper function that tries to decode custom error name and inputs from error payload data. async fn decode_execution_revert(data: &RawValue) -> Result> { let err_data = serde_json::from_str::(data.get())?; let Some(selector) = err_data.get(..4) else { return Ok(None) }; if let Some(known_error) = SignaturesIdentifier::new(false)?.identify_error(selector.try_into().unwrap()).await { let mut decoded_error = known_error.name.clone(); if !known_error.inputs.is_empty() && let Ok(error) = known_error.decode_error(&err_data) { write!(decoded_error, "({})", format_tokens(&error.body).format(", "))?; } return Ok(Some(decoded_error)); } Ok(None) } ================================================ FILE: crates/cast/tests/cli/erc20.rs ================================================ //! Contains various tests for checking cast erc20 subcommands use alloy_primitives::U256; use anvil::NodeConfig; use foundry_test_utils::util::OutputExt; mod anvil_const { /// First Anvil account pub const PK1: &str = "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80"; pub const ADDR1: &str = "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266"; /// Second Anvil account pub const _PK2: &str = "0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d"; pub const ADDR2: &str = "0x70997970C51812dc3A010C7d01b50e0d17dc79C8"; /// Contract address deploying from ADDR1 with nonce 0 pub const TOKEN: &str = "0x5FbDB2315678afecb367f032d93F642f64180aa3"; } fn get_u256_from_cmd(cmd: &mut foundry_test_utils::TestCommand, args: &[&str]) -> U256 { let output = cmd.cast_fuse().args(args).assert_success().get_output().stdout_lossy(); // Parse balance from output (format: "100000000000000000000 [1e20]") output.split_whitespace().next().unwrap().parse().unwrap() } fn get_balance( cmd: &mut foundry_test_utils::TestCommand, token: &str, address: &str, rpc: &str, ) -> U256 { get_u256_from_cmd(cmd, &["erc20", "balance", token, address, "--rpc-url", rpc]) } fn get_allowance( cmd: &mut foundry_test_utils::TestCommand, token: &str, owner: &str, spender: &str, rpc: &str, ) -> U256 { get_u256_from_cmd(cmd, &["erc20", "allowance", token, owner, spender, "--rpc-url", rpc]) } /// Helper function to deploy TestToken contract fn deploy_test_token( cmd: &mut foundry_test_utils::TestCommand, rpc: &str, private_key: &str, ) -> String { cmd.args([ "create", "--private-key", private_key, "--rpc-url", rpc, "--broadcast", "src/TestToken.sol:TestToken", ]) .assert_success(); // Return the standard deployment address (nonce 0 from first account) anvil_const::TOKEN.to_string() } /// Helper to setup anvil node and deploy test token async fn setup_token_test( prj: &foundry_test_utils::TestProject, cmd: &mut foundry_test_utils::TestCommand, ) -> (String, String) { let (_, handle) = anvil::spawn(NodeConfig::test()).await; let rpc = handle.http_endpoint(); // Deploy TestToken contract foundry_test_utils::util::initialize(prj.root()); prj.add_source("TestToken.sol", include_str!("../fixtures/TestToken.sol")); let token = deploy_test_token(cmd, &rpc, anvil_const::PK1); (rpc, token) } // tests that `balance` and `transfer` commands works correctly forgetest_async!(erc20_transfer_approve_success, |prj, cmd| { let (rpc, token) = setup_token_test(&prj, &mut cmd).await; // Test constants let transfer_amount = U256::from(100_000_000_000_000_000_000u128); // 100 tokens (18 decimals) let initial_supply = U256::from(1_000_000_000_000_000_000_000u128); // 1000 tokens total supply // Verify initial balances let addr1_balance_before = get_balance(&mut cmd, &token, anvil_const::ADDR1, &rpc); let addr2_balance_before = get_balance(&mut cmd, &token, anvil_const::ADDR2, &rpc); assert_eq!(addr1_balance_before, initial_supply); assert_eq!(addr2_balance_before, U256::ZERO); // Test ERC20 transfer from ADDR1 to ADDR2 cmd.cast_fuse() .args([ "erc20", "transfer", &token, anvil_const::ADDR2, &transfer_amount.to_string(), "--rpc-url", &rpc, "--private-key", anvil_const::PK1, ]) .assert_success(); // Verify balance changes after transfer let addr1_balance_after = get_balance(&mut cmd, &token, anvil_const::ADDR1, &rpc); let addr2_balance_after = get_balance(&mut cmd, &token, anvil_const::ADDR2, &rpc); assert_eq!(addr1_balance_after, addr1_balance_before - transfer_amount); assert_eq!(addr2_balance_after, addr2_balance_before + transfer_amount); }); // tests that `approve` and `allowance` commands works correctly forgetest_async!(erc20_approval_allowance, |prj, cmd| { let (rpc, token) = setup_token_test(&prj, &mut cmd).await; // ADDR1 approves ADDR2 to spend their tokens let approve_amount = U256::from(50_000_000_000_000_000_000u128); // 50 tokens cmd.cast_fuse() .args([ "erc20", "approve", &token, anvil_const::ADDR2, &approve_amount.to_string(), "--rpc-url", &rpc, "--private-key", anvil_const::PK1, ]) .assert_success(); // Verify allowance was set let allowance = get_allowance(&mut cmd, &token, anvil_const::ADDR1, anvil_const::ADDR2, &rpc); assert_eq!(allowance, approve_amount); }); // tests that `name`, `symbol`, `decimals`, and `totalSupply` commands work correctly forgetest_async!(erc20_metadata_success, |prj, cmd| { let (rpc, token) = setup_token_test(&prj, &mut cmd).await; // Test name let output = cmd .cast_fuse() .args(["erc20", "name", &token, "--rpc-url", &rpc]) .assert_success() .get_output() .stdout_lossy(); assert_eq!(output.trim(), "Test Token"); // Test symbol let output = cmd .cast_fuse() .args(["erc20", "symbol", &token, "--rpc-url", &rpc]) .assert_success() .get_output() .stdout_lossy(); assert_eq!(output.trim(), "TEST"); // Test decimals let output = cmd .cast_fuse() .args(["erc20", "decimals", &token, "--rpc-url", &rpc]) .assert_success() .get_output() .stdout_lossy(); assert_eq!(output.trim(), "18"); // Test totalSupply let output = cmd .cast_fuse() .args(["erc20", "total-supply", &token, "--rpc-url", &rpc]) .assert_success() .get_output() .stdout_lossy(); let total_supply: U256 = output.split_whitespace().next().unwrap().parse().unwrap(); assert_eq!(total_supply, U256::from(1_000_000_000_000_000_000_000u128)); }); // tests that `mint` command works correctly forgetest_async!(erc20_mint_success, |prj, cmd| { let (rpc, token) = setup_token_test(&prj, &mut cmd).await; let mint_amount = U256::from(500_000_000_000_000_000_000u128); // 500 tokens let initial_supply = U256::from(1_000_000_000_000_000_000_000u128); // 1000 tokens // Get initial balance and supply let addr2_balance_before = get_balance(&mut cmd, &token, anvil_const::ADDR2, &rpc); assert_eq!(addr2_balance_before, U256::ZERO); // Mint tokens to ADDR2 (only owner can mint) cmd.cast_fuse() .args([ "erc20", "mint", &token, anvil_const::ADDR2, &mint_amount.to_string(), "--rpc-url", &rpc, "--private-key", anvil_const::PK1, // PK1 is the owner/deployer ]) .assert_success(); // Verify balance increased let addr2_balance_after = get_balance(&mut cmd, &token, anvil_const::ADDR2, &rpc); assert_eq!(addr2_balance_after, mint_amount); // Verify totalSupply increased let output = cmd .cast_fuse() .args(["erc20", "total-supply", &token, "--rpc-url", &rpc]) .assert_success() .get_output() .stdout_lossy(); let total_supply: U256 = output.split_whitespace().next().unwrap().parse().unwrap(); assert_eq!(total_supply, initial_supply + mint_amount); }); // tests that `burn` command works correctly forgetest_async!(erc20_burn_success, |prj, cmd| { let (rpc, token) = setup_token_test(&prj, &mut cmd).await; let burn_amount = U256::from(200_000_000_000_000_000_000u128); // 200 tokens let initial_supply = U256::from(1_000_000_000_000_000_000_000u128); // 1000 tokens // Get initial balance let addr1_balance_before = get_balance(&mut cmd, &token, anvil_const::ADDR1, &rpc); assert_eq!(addr1_balance_before, initial_supply); // Burn tokens from ADDR1 cmd.cast_fuse() .args([ "erc20", "burn", &token, &burn_amount.to_string(), "--rpc-url", &rpc, "--private-key", anvil_const::PK1, ]) .assert_success(); // Verify balance decreased let addr1_balance_after = get_balance(&mut cmd, &token, anvil_const::ADDR1, &rpc); assert_eq!(addr1_balance_after, addr1_balance_before - burn_amount); // Verify totalSupply decreased let output = cmd .cast_fuse() .args(["erc20", "total-supply", &token, "--rpc-url", &rpc]) .assert_success() .get_output() .stdout_lossy(); let total_supply: U256 = output.split_whitespace().next().unwrap().parse().unwrap(); assert_eq!(total_supply, initial_supply - burn_amount); }); // tests that `transfer` command works with gas options forgetest_async!(erc20_transfer_with_gas_opts, |prj, cmd| { let (rpc, token) = setup_token_test(&prj, &mut cmd).await; let transfer_amount = U256::from(100_000_000_000_000_000_000u128); // 100 tokens // Transfer with explicit gas limit and gas price cmd.cast_fuse() .args([ "erc20", "transfer", &token, anvil_const::ADDR2, &transfer_amount.to_string(), "--rpc-url", &rpc, "--private-key", anvil_const::PK1, "--gas-limit", "100000", "--gas-price", "2000000000", ]) .assert_success(); // Verify transfer succeeded let balance = get_balance(&mut cmd, &token, anvil_const::ADDR2, &rpc); assert_eq!(balance, transfer_amount); }); // tests that `transfer` command fails with insufficient gas limit forgetest_async!(erc20_transfer_insufficient_gas, |prj, cmd| { let (rpc, token) = setup_token_test(&prj, &mut cmd).await; let transfer_amount = U256::from(50_000_000_000_000_000_000u128); // 50 tokens // Transfer with insufficient gas limit (ERC20 transfer needs ~50k gas) cmd.cast_fuse() .args([ "erc20", "transfer", &token, anvil_const::ADDR2, &transfer_amount.to_string(), "--rpc-url", &rpc, "--private-key", anvil_const::PK1, "--gas-limit", "1000", // Way too low for ERC20 transfer ]) .assert_failure(); // Verify transfer did NOT occur let balance = get_balance(&mut cmd, &token, anvil_const::ADDR2, &rpc); assert_eq!(balance, U256::ZERO); }); // tests that `transfer` command fails with incorrect nonce forgetest_async!(erc20_transfer_incorrect_nonce, |prj, cmd| { let (rpc, token) = setup_token_test(&prj, &mut cmd).await; let transfer_amount = U256::from(50_000_000_000_000_000_000u128); // 50 tokens cmd.cast_fuse() .args([ "erc20", "transfer", &token, anvil_const::ADDR2, &transfer_amount.to_string(), "--rpc-url", &rpc, "--private-key", anvil_const::PK1, ]) .assert_success(); // Transfer with nonce too low cmd.cast_fuse() .args([ "erc20", "transfer", &token, anvil_const::ADDR2, &transfer_amount.to_string(), "--rpc-url", &rpc, "--private-key", anvil_const::PK1, "--nonce", "0", // Too low nonce ]) .assert_failure(); // Verify transfer did NOT occur let balance = get_balance(&mut cmd, &token, anvil_const::ADDR2, &rpc); assert_eq!(balance, transfer_amount); // 2nd transfer failed }); // tests that the --curl flag outputs a valid curl command for cast erc20 balance casttest!(curl_erc20_balance, |_prj, cmd| { let rpc = "https://eth.example.com"; let token = "0xdead000000000000000000000000000000000000"; let owner = "0xbeef000000000000000000000000000000000000"; let output = cmd .args(["erc20", "balance", token, owner, "--rpc-url", rpc, "--curl"]) .assert_success() .get_output() .stdout_lossy(); // Verify curl command structure assert!(output.contains("curl -X POST")); assert!(output.contains("eth_call")); assert!(output.contains(rpc)); }); // tests that the --curl flag outputs a valid curl command for cast erc20 name casttest!(curl_erc20_name, |_prj, cmd| { let rpc = "https://eth.example.com"; let token = "0xdead000000000000000000000000000000000000"; let output = cmd .args(["erc20", "name", token, "--rpc-url", rpc, "--curl"]) .assert_success() .get_output() .stdout_lossy(); // Verify curl command structure assert!(output.contains("curl -X POST")); assert!(output.contains("eth_call")); assert!(output.contains(rpc)); }); // tests that the --curl flag outputs a valid curl command for cast erc20 decimals casttest!(curl_erc20_decimals, |_prj, cmd| { let rpc = "https://eth.example.com"; let token = "0xdead000000000000000000000000000000000000"; let output = cmd .args(["erc20", "decimals", token, "--rpc-url", rpc, "--curl"]) .assert_success() .get_output() .stdout_lossy(); // Verify curl command structure assert!(output.contains("curl -X POST")); assert!(output.contains("eth_call")); assert!(output.contains(rpc)); }); // tests that the --curl flag outputs a valid curl command for cast erc20 total-supply casttest!(curl_erc20_total_supply, |_prj, cmd| { let rpc = "https://eth.example.com"; let token = "0xdead000000000000000000000000000000000000"; let output = cmd .args(["erc20", "total-supply", token, "--rpc-url", rpc, "--curl"]) .assert_success() .get_output() .stdout_lossy(); // Verify curl command structure assert!(output.contains("curl -X POST")); assert!(output.contains("eth_call")); assert!(output.contains(rpc)); }); // tests that the --curl flag outputs a valid curl command for erc20 balance casttest!(erc20_curl_balance, |_prj, cmd| { let rpc = "https://eth.example.com"; let token = "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"; // USDC let owner = "0xdead000000000000000000000000000000000000"; let output = cmd .args(["erc20", "balance", token, owner, "--rpc-url", rpc, "--curl"]) .assert_success() .get_output() .stdout_lossy(); // Verify curl command structure assert!(output.contains("curl -X POST")); assert!(output.contains("eth_call")); assert!(output.contains(rpc)); }); // tests that the --curl flag outputs a valid curl command for erc20 name casttest!(erc20_curl_name, |_prj, cmd| { let rpc = "https://eth.example.com"; let token = "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"; // USDC let output = cmd .args(["erc20", "name", token, "--rpc-url", rpc, "--curl"]) .assert_success() .get_output() .stdout_lossy(); // Verify curl command structure assert!(output.contains("curl -X POST")); assert!(output.contains("eth_call")); assert!(output.contains(rpc)); }); // tests that the --curl flag outputs a valid curl command for erc20 decimals casttest!(erc20_curl_decimals, |_prj, cmd| { let rpc = "https://eth.example.com"; let token = "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"; // USDC let output = cmd .args(["erc20", "decimals", token, "--rpc-url", rpc, "--curl"]) .assert_success() .get_output() .stdout_lossy(); // Verify curl command structure assert!(output.contains("curl -X POST")); assert!(output.contains("eth_call")); assert!(output.contains(rpc)); }); // tests that the --curl flag outputs a valid curl command for erc20 total-supply casttest!(erc20_curl_total_supply, |_prj, cmd| { let rpc = "https://eth.example.com"; let token = "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"; // USDC let output = cmd .args(["erc20", "total-supply", token, "--rpc-url", rpc, "--curl"]) .assert_success() .get_output() .stdout_lossy(); // Verify curl command structure assert!(output.contains("curl -X POST")); assert!(output.contains("eth_call")); assert!(output.contains(rpc)); }); // tests that `balance` command works correctly with --json flag forgetest_async!(erc20_balance_json, |prj, cmd| { let (rpc, token) = setup_token_test(&prj, &mut cmd).await; let output = cmd .cast_fuse() .args(["--json", "erc20", "balance", &token, anvil_const::ADDR1, "--rpc-url", &rpc]) .assert_success() .get_output() .stdout_lossy(); let balance_str: String = serde_json::from_str(&output).expect("valid json string"); let balance: U256 = balance_str.parse().unwrap(); assert_eq!(balance, U256::from(1_000_000_000_000_000_000_000u128)); }); // tests that `allowance` command works correctly with --json flag forgetest_async!(erc20_allowance_json, |prj, cmd| { let (rpc, token) = setup_token_test(&prj, &mut cmd).await; // First approve some tokens let approve_amount = U256::from(50_000_000_000_000_000_000u128); cmd.cast_fuse() .args([ "erc20", "approve", &token, anvil_const::ADDR2, &approve_amount.to_string(), "--rpc-url", &rpc, "--private-key", anvil_const::PK1, ]) .assert_success(); // Check allowance with JSON flag let output = cmd .cast_fuse() .args([ "--json", "erc20", "allowance", &token, anvil_const::ADDR1, anvil_const::ADDR2, "--rpc-url", &rpc, ]) .assert_success() .get_output() .stdout_lossy(); let allowance_str: String = serde_json::from_str(&output).expect("valid json string"); let allowance: U256 = allowance_str.parse().unwrap(); assert_eq!(allowance, approve_amount); }); // tests that `name`, `symbol`, `decimals`, and `totalSupply` commands work correctly with --json // flag forgetest_async!(erc20_metadata_json, |prj, cmd| { let (rpc, token) = setup_token_test(&prj, &mut cmd).await; // Test name with --json let output = cmd .cast_fuse() .args(["--json", "erc20", "name", &token, "--rpc-url", &rpc]) .assert_success() .get_output() .stdout_lossy(); let name: String = serde_json::from_str(&output).expect("valid json string"); assert_eq!(name, "Test Token"); // Test symbol with --json let output = cmd .cast_fuse() .args(["--json", "erc20", "symbol", &token, "--rpc-url", &rpc]) .assert_success() .get_output() .stdout_lossy(); let symbol: String = serde_json::from_str(&output).expect("valid json string"); assert_eq!(symbol, "TEST"); // Test decimals with --json let output = cmd .cast_fuse() .args(["--json", "erc20", "decimals", &token, "--rpc-url", &rpc]) .assert_success() .get_output() .stdout_lossy(); let decimals: u8 = output.trim().parse().expect("valid number"); assert_eq!(decimals, 18); // Test totalSupply with --json let output = cmd .cast_fuse() .args(["--json", "erc20", "total-supply", &token, "--rpc-url", &rpc]) .assert_success() .get_output() .stdout_lossy(); let total_supply_str: String = serde_json::from_str(&output).expect("valid json string"); let total_supply: U256 = total_supply_str.parse().unwrap(); assert_eq!(total_supply, U256::from(1_000_000_000_000_000_000_000u128)); }); ================================================ FILE: crates/cast/tests/cli/main.rs ================================================ //! Contains various tests for checking cast commands use alloy_chains::NamedChain; use alloy_hardforks::EthereumHardfork; use alloy_network::{TransactionBuilder, TransactionResponse}; use alloy_primitives::{B256, Bytes, U256, address, b256, hex}; use alloy_provider::{Provider, ProviderBuilder}; use alloy_rpc_types::{Authorization, BlockNumberOrTag, Index, TransactionRequest}; use alloy_signer::Signer; use alloy_signer_local::PrivateKeySigner; use anvil::NodeConfig; use foundry_test_utils::{ rpc::{ next_etherscan_api_key, next_http_archive_rpc_url, next_http_rpc_endpoint, next_rpc_endpoint, next_ws_rpc_endpoint, }, snapbox::IntoData as _, str, util::OutputExt, }; use serde_json::json; use std::{fs, path::Path, str::FromStr}; #[macro_use] extern crate foundry_test_utils; mod erc20; mod selectors; casttest!(print_short_version, |_prj, cmd| { cmd.arg("-V").assert_success().stdout_eq(str![[r#" cast [..]-[..] ([..] [..]) "#]]); }); casttest!(print_long_version, |_prj, cmd| { cmd.arg("--version").assert_success().stdout_eq(str![[r#" cast Version: [..] Commit SHA: [..] Build Timestamp: [..] Build Profile: [..] "#]]); }); // tests `--help` is printed to std out casttest!(print_help, |_prj, cmd| { cmd.arg("--help").assert_success().stdout_eq(str![[r#" A Swiss Army knife for interacting with Ethereum applications from the command line Usage: cast[..] Commands: ... Options: -h, --help Print help (see a summary with '-h') -j, --threads Number of threads to use. Specifying 0 defaults to the number of logical cores ... [aliases: --jobs] -V, --version Print version Display options: --color The color of the log messages Possible values: - auto: Intelligently guess whether to use color output (default) - always: Force color output - never: Force disable color output --json Format log messages as JSON --md Format log messages as Markdown -q, --quiet Do not print log messages -v, --verbosity... Verbosity level of the log messages. ... Pass multiple times to increase the verbosity (e.g. -v, -vv, -vvv). ... Depending on the context the verbosity levels have different meanings. ... For example, the verbosity levels of the EVM are: - 2 (-vv): Print logs for all tests. - 3 (-vvv): Print execution traces for failing tests. - 4 (-vvvv): Print execution traces for all tests, and setup traces for failing tests. - 5 (-vvvvv): Print execution and setup traces for all tests, including storage changes and backtraces with line numbers. Find more information in the book: https://getfoundry.sh/cast/overview "#]]); }); // tests that the `cast block` command works correctly casttest!(latest_block, |_prj, cmd| { let eth_rpc_url = next_http_rpc_endpoint(); // Call `cast find-block` cmd.args(["block", "latest", "--rpc-url", eth_rpc_url.as_str()]); cmd.assert_success().stdout_eq(str![[r#" baseFeePerGas [..] difficulty [..] extraData [..] gasLimit [..] gasUsed [..] hash [..] logsBloom [..] miner [..] mixHash [..] nonce [..] number [..] parentHash [..] parentBeaconRoot [..] transactionsRoot [..] receiptsRoot [..] sha3Uncles [..] size [..] stateRoot [..] timestamp [..] withdrawalsRoot [..] totalDifficulty [..] blobGasUsed [..] excessBlobGas [..] requestsHash [..] transactions: [ ... ] "#]]); // cmd.cast_fuse().args([ "block", "15007840", "-f", "hash,timestamp", "--rpc-url", eth_rpc_url.as_str(), ]); cmd.assert_success().stdout_eq(str![[r#" 0x950091817a57e22b6c1f3b951a15f52d41ac89b299cc8f9c89bb6d185f80c415 1655904485 "#]]); }); casttest!(block_raw, |_prj, cmd| { let eth_rpc_url = next_http_rpc_endpoint(); let output = cmd .args(["block", "22934900", "--rpc-url", eth_rpc_url.as_str(), "--raw"]) .assert_success() .get_output() .stdout_lossy() .trim() .to_string(); // Hash the output with keccak256 let hash = alloy_primitives::keccak256(hex::decode(output).unwrap()); // Verify the Mainnet's block #22934900 header hash equals the expected value // obtained with go-ethereum's `block.Header().Hash()` method assert_eq!( hash.to_string(), "0x49fd7f3b9ba5d67fa60197027f09454d4cac945e8f271edcc84c3fd5872446d3" ); }); casttest!(block_raw_tempo, |_prj, cmd| { // https://explore.tempo.xyz/block/8386710 let output = cmd .args([ "block", "8386710", "--rpc-url", "https://rpc.moderato.tempo.xyz", "--raw", "-n", "tempo", ]) .assert_success() .get_output() .stdout_lossy() .trim() .to_string(); let hash = alloy_primitives::keccak256(hex::decode(output).unwrap()); assert_eq!( hash.to_string(), "0xcd6170dc28b888bcb93ed1ad76a6bea4ad9977b678db5d462df83d35ec9b8d15" ); }); // tests that the `cast find-block` command works correctly casttest!(finds_block, |_prj, cmd| { // Construct args let timestamp = "1647843609".to_string(); let eth_rpc_url = next_http_rpc_endpoint(); // Call `cast find-block` // cmd.args(["find-block", "--rpc-url", eth_rpc_url.as_str(), ×tamp]) .assert_success() .stdout_eq(str![[r#" 14428082 "#]]); }); // tests that we can create a new wallet casttest!(new_wallet, |_prj, cmd| { cmd.args(["wallet", "new"]).assert_success().stdout_eq(str![[r#" Successfully created new keypair. [ADDRESS] [PRIVATE_KEY] "#]]); }); // tests that we can create a new wallet (verbose variant) casttest!(new_wallet_verbose, |_prj, cmd| { cmd.args(["wallet", "new", "-v"]).assert_success().stdout_eq(str![[r#" Successfully created new keypair. [ADDRESS] [PUBLIC_KEY] [PRIVATE_KEY] "#]]); }); // tests that we can create a new wallet with json output casttest!(new_wallet_json, |_prj, cmd| { cmd.args(["wallet", "new", "--json"]).assert_success().stdout_eq( str![[r#" [ { "address": "{...}", "private_key": "{...}" } ] "#]] .is_json(), ); }); // tests that we can create a new wallet with json output (verbose variant) casttest!(new_wallet_json_verbose, |_prj, cmd| { cmd.args(["wallet", "new", "--json", "-v"]).assert_success().stdout_eq( str![[r#" [ { "address": "{...}", "public_key": "{...}", "private_key": "{...}" } ] "#]] .is_json(), ); }); // tests that we can create a new wallet with keystore casttest!(new_wallet_keystore_with_password, |_prj, cmd| { cmd.args(["wallet", "new", ".", "test-account", "--unsafe-password", "test"]) .assert_success() .stdout_eq(str![[r#" Created new encrypted keystore file: [..] [ADDRESS] "#]]); }); // tests that we can create a new wallet with keystore (verbose variant) casttest!(new_wallet_keystore_with_password_verbose, |_prj, cmd| { cmd.args(["wallet", "new", ".", "test-account", "--unsafe-password", "test", "-v"]) .assert_success() .stdout_eq(str![[r#" Created new encrypted keystore file: [..] [ADDRESS] [PUBLIC_KEY] "#]]); }); // tests that `cast wallet new` prompts before overwriting an existing keystore file casttest!(new_wallet_keystore_overwrite_protection, |prj, cmd| { // Create the initial keystore cmd.args(["wallet", "new", ".", "test-account", "--unsafe-password", "test"]).assert_success(); // Attempt to overwrite with stdin "n" — should be cancelled cmd.cast_fuse() .current_dir(prj.root()) .args(["wallet", "new", ".", "test-account", "--unsafe-password", "test"]) .stdin("n\n") .assert_failure() .stderr_eq(str![[r#" The following keystore file(s) already exist: - test-account Error: Operation cancelled. No keystores were modified. "#]]); }); // tests that `cast wallet new --force` overwrites existing keystore files without prompting casttest!(new_wallet_keystore_overwrite_force, |prj, cmd| { // Create the initial keystore cmd.args(["wallet", "new", ".", "test-account", "--unsafe-password", "test"]).assert_success(); // Overwrite with --force — should succeed without prompting cmd.cast_fuse() .current_dir(prj.root()) .args(["wallet", "new", ".", "test-account", "--unsafe-password", "test", "--force"]) .assert_success() .stdout_eq(str![[r#" Created new encrypted keystore file: [..] [ADDRESS] "#]]); }); // tests that `cast wallet new -n 2` prompts before overwriting existing keystore files casttest!(new_wallet_keystore_overwrite_protection_multiple, |prj, cmd| { // Create 2 keystores: test-account_1 and test-account_2 cmd.args(["wallet", "new", ".", "test-account", "--unsafe-password", "test", "-n", "2"]) .assert_success(); // Attempt to overwrite with stdin "n" — should list both and cancel cmd.cast_fuse() .current_dir(prj.root()) .args(["wallet", "new", ".", "test-account", "--unsafe-password", "test", "-n", "2"]) .stdin("n\n") .assert_failure() .stderr_eq(str![[r#" The following keystore file(s) already exist: - test-account_1 - test-account_2 Error: Operation cancelled. No keystores were modified. "#]]); }); // tests that we can create a new wallet with default keystore location casttest!(new_wallet_default_keystore, |_prj, cmd| { cmd.args(["wallet", "new", "--unsafe-password", "test"]).assert_success().stdout_eq(str![[ r#" Created new encrypted keystore file: [..] [ADDRESS] "# ]]); // Verify the default keystore directory was created let keystore_path = dirs::home_dir().unwrap().join(".foundry").join("keystores"); assert!(keystore_path.exists()); assert!(keystore_path.is_dir()); }); // tests that we can outputting multiple keys without a keystore path casttest!(new_wallet_multiple_keys, |_prj, cmd| { cmd.args(["wallet", "new", "-n", "2"]).assert_success().stdout_eq(str![[r#" Successfully created new keypair. [ADDRESS] [PRIVATE_KEY] Successfully created new keypair. [ADDRESS] [PRIVATE_KEY] "#]]); }); // tests that we can get the address of a keystore file casttest!(wallet_address_keystore_with_password_file, |_prj, cmd| { let keystore_dir = Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures/keystore"); cmd.args([ "wallet", "address", "--keystore", keystore_dir .join("UTC--2022-12-20T10-30-43.591916000Z--ec554aeafe75601aaab43bd4621a22284db566c2") .to_str() .unwrap(), "--password-file", keystore_dir.join("password-ec554").to_str().unwrap(), ]) .assert_success() .stdout_eq(str![[r#" 0xeC554aeAFE75601AaAb43Bd4621A22284dB566C2 "#]]); }); // tests that `cast wallet remove` can successfully remove a keystore file and validates password casttest!(wallet_remove_keystore_with_unsafe_password, |prj, cmd| { let keystore_path = prj.root().join("keystore"); cmd.set_current_dir(prj.root()); let account_name = "testAccount"; // Default Anvil private key let test_private_key = b256!("0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80"); // import private key cmd.cast_fuse() .args([ "wallet", "import", account_name, "--private-key", &test_private_key.to_string(), "-k", "keystore", "--unsafe-password", "test", ]) .assert_success() .stdout_eq(str![[r#" `testAccount` keystore was saved successfully. [ADDRESS] "#]]); // check that the keystore file was created let keystore_file = keystore_path.join(account_name); assert!(keystore_file.exists()); // Remove the wallet cmd.cast_fuse() .args([ "wallet", "remove", "--name", account_name, "--dir", keystore_path.to_str().unwrap(), "--unsafe-password", "test", ]) .assert_success() .stdout_eq(str![[r#" `testAccount` keystore was removed successfully. "#]]); assert!(!keystore_file.exists()); }); // tests that `cast wallet sign message` outputs the expected signature casttest!(wallet_sign_message_utf8_data, |_prj, cmd| { let pk = "0x0000000000000000000000000000000000000000000000000000000000000001"; let address = "0x7E5F4552091A69125d5DfCb7b8C2659029395Bdf"; let msg = "test"; let expected = "0xfe28833983d6faa0715c7e8c3873c725ddab6fa5bf84d40e780676e463e6bea20fc6aea97dc273a98eb26b0914e224c8dd5c615ceaab69ddddcf9b0ae3de0e371c"; cmd.args(["wallet", "sign", "--private-key", pk, msg]).assert_success().stdout_eq(str![[r#" 0xfe28833983d6faa0715c7e8c3873c725ddab6fa5bf84d40e780676e463e6bea20fc6aea97dc273a98eb26b0914e224c8dd5c615ceaab69ddddcf9b0ae3de0e371c "#]]); // Success. cmd.cast_fuse() .args(["wallet", "verify", "-a", address, msg, expected]) .assert_success() .stdout_eq(str![[r#" Validation succeeded. Address 0x7E5F4552091A69125d5DfCb7b8C2659029395Bdf signed this message. "#]]); // Fail. cmd.cast_fuse() .args(["wallet", "verify", "-a", address, "other msg", expected]) .assert_failure() .stderr_eq(str![[r#" Error: Validation failed. Address 0x7E5F4552091A69125d5DfCb7b8C2659029395Bdf did not sign this message. "#]]); }); // tests that `cast wallet sign message` outputs the expected signature, given a 0x-prefixed data casttest!(wallet_sign_message_hex_data, |_prj, cmd| { cmd.args([ "wallet", "sign", "--private-key", "0x0000000000000000000000000000000000000000000000000000000000000001", "0x0000000000000000000000000000000000000000000000000000000000000000", ]).assert_success().stdout_eq(str![[r#" 0x23a42ca5616ee730ff3735890c32fc7b9491a9f633faca9434797f2c845f5abf4d9ba23bd7edb8577acebaa3644dc5a4995296db420522bb40060f1693c33c9b1c "#]]); }); // // 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([ "wallet", "sign", "--private-key", "0x0000000000000000000000000000000000000000000000000000000000000001", "--data", "{\"types\": {\"EIP712Domain\": [{\"name\": \"name\",\"type\": \"string\"},{\"name\": \"version\",\"type\": \"string\"},{\"name\": \"chainId\",\"type\": \"uint256\"},{\"name\": \"verifyingContract\",\"type\": \"address\"}],\"Message\": [{\"name\": \"data\",\"type\": \"string\"}]},\"primaryType\": \"Message\",\"domain\": {\"name\": \"example.metamask.io\",\"version\": \"1\",\"chainId\": \"1\",\"verifyingContract\": \"0x0000000000000000000000000000000000000000\"},\"message\": {\"data\": \"Hello!\"}}", ]).assert_success().stdout_eq(str![[r#" 0x06c18bdc8163219fddc9afaf5a0550e381326474bb757c86dc32317040cf384e07a2c72ce66c1a0626b6750ca9b6c035bf6f03e7ed67ae2d1134171e9085c0b51b "#]]); }); // tests that `cast wallet sign typed-data` outputs the expected signature, given a JSON file casttest!(wallet_sign_typed_data_file, |_prj, cmd| { cmd.args([ "wallet", "sign", "--private-key", "0x0000000000000000000000000000000000000000000000000000000000000001", "--data", "--from-file", Path::new(env!("CARGO_MANIFEST_DIR")) .join("tests/fixtures/sign_typed_data.json") .into_os_string() .into_string() .unwrap() .as_str(), ]).assert_success().stdout_eq(str![[r#" 0x06c18bdc8163219fddc9afaf5a0550e381326474bb757c86dc32317040cf384e07a2c72ce66c1a0626b6750ca9b6c035bf6f03e7ed67ae2d1134171e9085c0b51b "#]]); }); // tests that `cast wallet sign typed-data` passes with type names containing colons // casttest!(wallet_sign_typed_data_with_colon_succeeds, |_prj, cmd| { let typed_data_with_colon = r#"{ "types": { "EIP712Domain": [ {"name": "name", "type": "string"}, {"name": "version", "type": "string"}, {"name": "chainId", "type": "uint256"}, {"name": "verifyingContract", "type": "address"} ], "Test:Message": [ {"name": "content", "type": "string"} ] }, "primaryType": "Test:Message", "domain": { "name": "TestDomain", "version": "1", "chainId": 1, "verifyingContract": "0x0000000000000000000000000000000000000000" }, "message": { "content": "Hello" } }"#; cmd.args([ "wallet", "sign", "--private-key", "0x0000000000000000000000000000000000000000000000000000000000000001", "--data", typed_data_with_colon, ]).assert_success().stdout_eq(str![[r#" 0xf91c67e845a4d468d1f876f457ffa01e65468641fc121453705242d21de39b266c278592b085814ab1e9adc938cc26b1d64bb61f80b437df077777c4283612291b "#]]); }); // tests that the same data without colon works correctly // casttest!(wallet_sign_typed_data_without_colon_works, |_prj, cmd| { let typed_data_without_colon = r#"{ "types": { "EIP712Domain": [ {"name": "name", "type": "string"}, {"name": "version", "type": "string"}, {"name": "chainId", "type": "uint256"}, {"name": "verifyingContract", "type": "address"} ], "TestMessage": [ {"name": "content", "type": "string"} ] }, "primaryType": "TestMessage", "domain": { "name": "TestDomain", "version": "1", "chainId": 1, "verifyingContract": "0x0000000000000000000000000000000000000000" }, "message": { "content": "Hello" } }"#; cmd.args([ "wallet", "sign", "--private-key", "0x0000000000000000000000000000000000000000000000000000000000000001", "--data", typed_data_without_colon, ]) .assert_success(); }); // tests that `cast wallet sign-auth message` outputs the expected signature casttest!(wallet_sign_auth, |_prj, cmd| { cmd.args([ "wallet", "sign-auth", "--private-key", "0x0000000000000000000000000000000000000000000000000000000000000001", "--nonce", "100", "--chain", "1", "0x7E5F4552091A69125d5DfCb7b8C2659029395Bdf"]).assert_success().stdout_eq(str![[r#" 0xf85a01947e5f4552091a69125d5dfcb7b8c2659029395bdf6401a0ad489ee0314497c3f06567f3080a46a63908edc1c7cdf2ac2d609ca911212086a065a6ba951c8748dd8634740fe498efb61770097d99ff5fdcb9a863b62ea899f6 "#]]); }); // tests that `cast wallet sign-auth --self-broadcast` uses nonce + 1 casttest!(wallet_sign_auth_self_broadcast, async |_prj, cmd| { use alloy_rlp::Decodable; use alloy_signer_local::PrivateKeySigner; let (_, handle) = anvil::spawn(NodeConfig::test().with_hardfork(Some(EthereumHardfork::Prague.into()))).await; let endpoint = handle.http_endpoint(); let private_key = "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80"; let signer: PrivateKeySigner = private_key.parse().unwrap(); let signer_address = signer.address(); let delegate_address = address!("0x70997970C51812dc3A010C7d01b50e0d17dc79C8"); // Get the current nonce from the RPC let provider = ProviderBuilder::new().connect_http(endpoint.parse().unwrap()); let current_nonce = provider.get_transaction_count(signer_address).await.unwrap(); // First, get the auth without --self-broadcast (should use current nonce) let output_normal = cmd .args([ "wallet", "sign-auth", "--private-key", private_key, "--rpc-url", &endpoint, &delegate_address.to_string(), ]) .assert_success() .get_output() .stdout_lossy() .trim() .to_string(); // Then, get the auth with --self-broadcast (should use current nonce + 1) let output_self_broadcast = cmd .cast_fuse() .args([ "wallet", "sign-auth", "--private-key", private_key, "--rpc-url", &endpoint, "--self-broadcast", &delegate_address.to_string(), ]) .assert_success() .get_output() .stdout_lossy() .trim() .to_string(); // The outputs should be different due to different nonces assert_ne!( output_normal, output_self_broadcast, "self-broadcast should produce different signature due to nonce + 1" ); // Decode the RLP to verify the nonces let normal_bytes = hex::decode(output_normal.strip_prefix("0x").unwrap()).unwrap(); let self_broadcast_bytes = hex::decode(output_self_broadcast.strip_prefix("0x").unwrap()).unwrap(); let normal_auth = alloy_eips::eip7702::SignedAuthorization::decode(&mut normal_bytes.as_slice()).unwrap(); let self_broadcast_auth = alloy_eips::eip7702::SignedAuthorization::decode(&mut self_broadcast_bytes.as_slice()) .unwrap(); assert_eq!(normal_auth.nonce(), current_nonce, "normal auth should have current nonce"); assert_eq!( self_broadcast_auth.nonce(), current_nonce + 1, "self-broadcast auth should have current nonce + 1" ); }); // tests that `cast wallet list` outputs the local accounts casttest!(wallet_list_local_accounts, |prj, cmd| { let keystore_path = prj.root().join("keystore"); fs::create_dir_all(keystore_path).unwrap(); cmd.set_current_dir(prj.root()); // empty results cmd.cast_fuse() .args(["wallet", "list", "--dir", "keystore"]) .assert_success() .stdout_eq(str![""]); // create 10 wallets cmd.cast_fuse() .args(["wallet", "new", "keystore", "-n", "10", "--unsafe-password", "test"]) .assert_success() .stdout_eq(str![[r#" Created new encrypted keystore file: [..] [ADDRESS] Created new encrypted keystore file: [..] [ADDRESS] Created new encrypted keystore file: [..] [ADDRESS] Created new encrypted keystore file: [..] [ADDRESS] Created new encrypted keystore file: [..] [ADDRESS] Created new encrypted keystore file: [..] [ADDRESS] Created new encrypted keystore file: [..] [ADDRESS] Created new encrypted keystore file: [..] [ADDRESS] Created new encrypted keystore file: [..] [ADDRESS] Created new encrypted keystore file: [..] [ADDRESS] "#]]); // test list new wallet cmd.cast_fuse().args(["wallet", "list", "--dir", "keystore"]).assert_success().stdout_eq(str![ [r#" [..] (Local) [..] (Local) [..] (Local) [..] (Local) [..] (Local) [..] (Local) [..] (Local) [..] (Local) [..] (Local) [..] (Local) "#] ]); }); // tests that `cast wallet new-mnemonic --entropy` outputs the expected mnemonic casttest!(wallet_mnemonic_from_entropy, |_prj, cmd| { cmd.args([ "wallet", "new-mnemonic", "--accounts", "3", "--entropy", "0xdf9bf37e6fcdf9bf37e6fcdf9bf37e3c", ]) .assert_success() .stdout_eq( str![[r#" Generating mnemonic from provided entropy... Successfully generated a new mnemonic. Phrase: test test test test test test test test test test test junk Accounts: - Account 0: Address: 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 Private key: 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 - Account 1: Address: 0x70997970C51812dc3A010C7d01b50e0d17dc79C8 Private key: 0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d - Account 2: Address: 0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC Private key: 0x5de4111afa1a4b94908f83103eb1f1706367c2e68ca870fc3fb9a804cdab365a "#]] .raw(), ); }); // tests that `cast wallet new-mnemonic --entropy` outputs the expected mnemonic (verbose variant) casttest!(wallet_mnemonic_from_entropy_verbose, |_prj, cmd| { cmd.args([ "wallet", "new-mnemonic", "--accounts", "3", "--entropy", "0xdf9bf37e6fcdf9bf37e6fcdf9bf37e3c", "-v", ]) .assert_success() .stdout_eq( str![[r#" Generating mnemonic from provided entropy... Successfully generated a new mnemonic. Phrase: test test test test test test test test test test test junk Accounts: - Account 0: Address: 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 Public key: 0x8318535b54105d4a7aae60c08fc45f9687181b4fdfc625bd1a753fa7397fed753547f11ca8696646f2f3acb08e31016afac23e630c5d11f59f61fef57b0d2aa5 Private key: 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 - Account 1: Address: 0x70997970C51812dc3A010C7d01b50e0d17dc79C8 Public key: 0xba5734d8f7091719471e7f7ed6b9df170dc70cc661ca05e688601ad984f068b0d67351e5f06073092499336ab0839ef8a521afd334e53807205fa2f08eec74f4 Private key: 0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d - Account 2: Address: 0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC Public key: 0x9d9031e97dd78ff8c15aa86939de9b1e791066a0224e331bc962a2099a7b1f0464b8bbafe1535f2301c72c2cb3535b172da30b02686ab0393d348614f157fbdb Private key: 0x5de4111afa1a4b94908f83103eb1f1706367c2e68ca870fc3fb9a804cdab365a "#]] .raw(), ); }); // tests that `cast wallet new-mnemonic --json` outputs the expected mnemonic casttest!(wallet_mnemonic_from_entropy_json, |_prj, cmd| { cmd.args([ "wallet", "new-mnemonic", "--accounts", "3", "--entropy", "0xdf9bf37e6fcdf9bf37e6fcdf9bf37e3c", "--json", ]) .assert_success() .stdout_eq(str![[r#" { "mnemonic": "test test test test test test test test test test test junk", "accounts": [ { "address": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", "private_key": "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80" }, { "address": "0x70997970C51812dc3A010C7d01b50e0d17dc79C8", "private_key": "0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d" }, { "address": "0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC", "private_key": "0x5de4111afa1a4b94908f83103eb1f1706367c2e68ca870fc3fb9a804cdab365a" } ] } "#]]); }); // tests that `cast wallet new-mnemonic --json` outputs the expected mnemonic (verbose variant) casttest!(wallet_mnemonic_from_entropy_json_verbose, |_prj, cmd| { cmd.args([ "wallet", "new-mnemonic", "--accounts", "3", "--entropy", "0xdf9bf37e6fcdf9bf37e6fcdf9bf37e3c", "--json", "-v", ]) .assert_success() .stdout_eq(str![[r#" { "mnemonic": "test test test test test test test test test test test junk", "accounts": [ { "address": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", "public_key": "0x8318535b54105d4a7aae60c08fc45f9687181b4fdfc625bd1a753fa7397fed753547f11ca8696646f2f3acb08e31016afac23e630c5d11f59f61fef57b0d2aa5", "private_key": "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80" }, { "address": "0x70997970C51812dc3A010C7d01b50e0d17dc79C8", "public_key": "0xba5734d8f7091719471e7f7ed6b9df170dc70cc661ca05e688601ad984f068b0d67351e5f06073092499336ab0839ef8a521afd334e53807205fa2f08eec74f4", "private_key": "0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d" }, { "address": "0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC", "public_key": "0x9d9031e97dd78ff8c15aa86939de9b1e791066a0224e331bc962a2099a7b1f0464b8bbafe1535f2301c72c2cb3535b172da30b02686ab0393d348614f157fbdb", "private_key": "0x5de4111afa1a4b94908f83103eb1f1706367c2e68ca870fc3fb9a804cdab365a" } ] } "#]]); }); // tests that `cast wallet derive` outputs the addresses of the accounts derived from the mnemonic casttest!(wallet_derive_mnemonic, |_prj, cmd| { cmd.args([ "wallet", "derive", "--accounts", "3", "test test test test test test test test test test test junk", ]) .assert_success() .stdout_eq(str![[r#" - Account 0: [ADDRESS] - Account 1: [ADDRESS] - Account 2: [ADDRESS] "#]]); }); // tests that `cast wallet derive` with insecure flag outputs the addresses and private keys of the // accounts derived from the mnemonic casttest!(wallet_derive_mnemonic_insecure, |_prj, cmd| { cmd.args([ "wallet", "derive", "--accounts", "3", "--insecure", "test test test test test test test test test test test junk", ]) .assert_success() .stdout_eq(str![[r#" - Account 0: [ADDRESS] [PRIVATE_KEY] - Account 1: [ADDRESS] [PRIVATE_KEY] - Account 2: [ADDRESS] [PRIVATE_KEY] "#]]); }); // tests that `cast wallet derive` with json flag outputs the addresses of the accounts derived from // the mnemonic in JSON format casttest!(wallet_derive_mnemonic_json, |_prj, cmd| { cmd.args([ "wallet", "derive", "--accounts", "3", "--json", "test test test test test test test test test test test junk", ]) .assert_success() .stdout_eq(str![[r#" [ { "address": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" }, { "address": "0x70997970C51812dc3A010C7d01b50e0d17dc79C8" }, { "address": "0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC" } ] "#]]); }); // tests that `cast wallet derive` with insecure and json flag outputs the addresses and private // keys of the accounts derived from the mnemonic in JSON format casttest!(wallet_derive_mnemonic_insecure_json, |_prj, cmd| { cmd.args([ "wallet", "derive", "--accounts", "3", "--insecure", "--json", "test test test test test test test test test test test junk", ]) .assert_success() .stdout_eq(str![[r#" [ { "address": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", "private_key": "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80" }, { "address": "0x70997970C51812dc3A010C7d01b50e0d17dc79C8", "private_key": "0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d" }, { "address": "0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC", "private_key": "0x5de4111afa1a4b94908f83103eb1f1706367c2e68ca870fc3fb9a804cdab365a" } ] "#]]); }); // tests that `cast wallet private-key` with arguments outputs the private key casttest!(wallet_private_key_from_mnemonic_arg, |_prj, cmd| { cmd.args([ "wallet", "private-key", "test test test test test test test test test test test junk", "1", ]) .assert_success() .stdout_eq(str![[r#" 0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d "#]]); }); // tests that `cast wallet private-key` with options outputs the private key casttest!(wallet_private_key_from_mnemonic_option, |_prj, cmd| { cmd.args([ "wallet", "private-key", "--mnemonic", "test test test test test test test test test test test junk", "--mnemonic-index", "1", ]) .assert_success() .stdout_eq(str![[r#" 0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d "#]]); }); // tests that `cast wallet public-key` correctly derives and outputs the public key casttest!(wallet_public_key_with_private_key, |_prj, cmd| { cmd.args([ "wallet", "public-key", "--raw-private-key", "0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d" ]) .assert_success() .stdout_eq(str![[r#" 0xba5734d8f7091719471e7f7ed6b9df170dc70cc661ca05e688601ad984f068b0d67351e5f06073092499336ab0839ef8a521afd334e53807205fa2f08eec74f4 "#]]); }); // tests that `cast wallet private-key` with derivation path outputs the private key casttest!(wallet_private_key_with_derivation_path, |_prj, cmd| { cmd.args([ "wallet", "private-key", "--mnemonic", "test test test test test test test test test test test junk", "--mnemonic-derivation-path", "m/44'/60'/0'/0/1", ]) .assert_success() .stdout_eq(str![[r#" 0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d "#]]); }); // tests that `cast wallet import` creates a keystore for a private key and that `cast wallet // decrypt-keystore` can access it casttest!(wallet_import_and_decrypt, |prj, cmd| { let keystore_path = prj.root().join("keystore"); cmd.set_current_dir(prj.root()); let account_name = "testAccount"; // Default Anvil private key let test_private_key = b256!("0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80"); // import private key cmd.cast_fuse() .args([ "wallet", "import", account_name, "--private-key", &test_private_key.to_string(), "-k", "keystore", "--unsafe-password", "test", ]) .assert_success() .stdout_eq(str![[r#" `testAccount` keystore was saved successfully. [ADDRESS] "#]]); // check that the keystore file was created let keystore_file = keystore_path.join(account_name); assert!(keystore_file.exists()); // decrypt the keystore file let decrypt_output = cmd.cast_fuse().args([ "wallet", "decrypt-keystore", account_name, "-k", "keystore", "--unsafe-password", "test", ]); // get the PK out of the output (last word in the output) let decrypt_output = decrypt_output.assert_success().get_output().stdout_lossy(); let private_key_string = decrypt_output.split_whitespace().last().unwrap(); // check that the decrypted private key matches the imported private key let decrypted_private_key = B256::from_str(private_key_string).unwrap(); // the form assert_eq!(decrypted_private_key, test_private_key); }); // tests that `cast wallet change-password` can successfully change the password of a keystore file casttest!(wallet_change_password, |prj, cmd| { let keystore_path = prj.root().join("keystore"); cmd.set_current_dir(prj.root()); let account_name = "testAccount"; // Default Anvil private key let test_private_key = b256!("0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80"); // import private key with initial password cmd.cast_fuse() .args([ "wallet", "import", account_name, "--private-key", &test_private_key.to_string(), "-k", "keystore", "--unsafe-password", "old_password", ]) .assert_success() .stdout_eq(str![[r#" `testAccount` keystore was saved successfully. [ADDRESS] "#]]); // check that the keystore file was created let keystore_file = keystore_path.join(account_name); assert!(keystore_file.exists()); // change the password cmd.cast_fuse() .args([ "wallet", "change-password", account_name, "--keystore-dir", "keystore", "--unsafe-password", "old_password", "--unsafe-new-password", "new_password", ]) .assert_success() .stdout_eq(str![[r#" Password for keystore `testAccount` was changed successfully. [ADDRESS] "#]]); // verify the old password no longer works cmd.cast_fuse() .args([ "wallet", "decrypt-keystore", account_name, "-k", "keystore", "--unsafe-password", "old_password", ]) .assert_failure(); // verify the new password works let decrypt_output = cmd.cast_fuse().args([ "wallet", "decrypt-keystore", account_name, "-k", "keystore", "--unsafe-password", "new_password", ]); // get the PK out of the output (last word in the output) let decrypt_output = decrypt_output.assert_success().get_output().stdout_lossy(); let private_key_string = decrypt_output.split_whitespace().last().unwrap(); // check that the decrypted private key matches the imported private key let decrypted_private_key = B256::from_str(private_key_string).unwrap(); assert_eq!(decrypted_private_key, test_private_key); }); // tests that `cast estimate` is working correctly. casttest!(estimate_function_gas, |_prj, cmd| { let eth_rpc_url = next_http_rpc_endpoint(); // ensure we get a positive non-error value for gas estimate let output: u32 = cmd .args([ "estimate", "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045", // vitalik.eth "--value", "100", "deposit()", "--rpc-url", eth_rpc_url.as_str(), ]) .assert_success() .get_output() .stdout_lossy() .trim() .parse() .unwrap(); 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(); // sample contract code bytecode. Wouldn't run but is valid bytecode that the estimate method // accepts and could be deployed. let output = cmd .args([ "estimate", "--rpc-url", eth_rpc_url.as_str(), "--create", "0000", "ERC20(uint256,string,string)", "100", "Test", "TST", ]) .assert_success() .get_output() .stdout_lossy(); // ensure we get a positive non-error value for gas estimate let output: u32 = output.trim().parse().unwrap(); assert!(output > 0); }); // tests that the `cast to-rlp` and `cast from-rlp` commands work correctly casttest!(rlp, |_prj, cmd| { cmd.args(["--to-rlp", "[\"0xaa\", [[\"bb\"]], \"0xcc\"]"]).assert_success().stdout_eq(str![[ r#" 0xc881aac3c281bb81cc "# ]]); cmd.cast_fuse(); cmd.args(["--from-rlp", "0xcbc58455556666c0c0c2c1c0"]).assert_success().stdout_eq(str![[r#" [["0x55556666"],[],[],[[[]]]] "#]]); }); // test that `cast impl` works correctly for both the implementation slot and the beacon slot casttest!(impl_slot, |_prj, cmd| { let eth_rpc_url = next_http_archive_rpc_url(); // Call `cast impl` for the implementation slot (AAVE Proxy) cmd.args([ "impl", "0x4965f6FA20fE9728deCf5165016fc338a5a85aBF", "--rpc-url", eth_rpc_url.as_str(), "--block", "21422087", ]) .assert_success() .stdout_eq(str![[r#" 0xb61306c8eb34a2104d9eb8d84f1bb1001067fa4b "#]]); }); casttest!(impl_slot_beacon, |_prj, cmd| { let eth_rpc_url = next_http_archive_rpc_url(); // Call `cast impl` for the beacon slot cmd.args([ "impl", "0xc63d9f0040d35f328274312fc8771a986fc4ba86", "--beacon", "--rpc-url", eth_rpc_url.as_str(), "--block", "21422087", ]) .assert_success() .stdout_eq(str![[r#" 0xa748ae65ba11606492a9c57effa0d4b7be551ec2 "#]]); }); // test for cast_rpc without arguments casttest!(rpc_no_args, |_prj, cmd| { let eth_rpc_url = next_http_rpc_endpoint(); // Call `cast rpc eth_chainId` cmd.args(["rpc", "--rpc-url", eth_rpc_url.as_str(), "eth_chainId"]).assert_success().stdout_eq( str![[r#" "0x1" "#]], ); }); // test for cast_rpc without arguments using websocket casttest!(ws_rpc_no_args, |_prj, cmd| { let eth_rpc_url = next_ws_rpc_endpoint(); // Call `cast rpc eth_chainId` cmd.args(["rpc", "--rpc-url", eth_rpc_url.as_str(), "eth_chainId"]).assert_success().stdout_eq( str![[r#" "0x1" "#]], ); }); // test for cast_rpc with arguments casttest!(rpc_with_args, |_prj, cmd| { let eth_rpc_url = next_http_rpc_endpoint(); // Call `cast rpc eth_getBlockByNumber 0x123 false` cmd.args(["rpc", "--rpc-url", eth_rpc_url.as_str(), "eth_getBlockByNumber", "0x123", "false"]) .assert_json_stdout(str![[r#" {"number":"0x123","hash":"0xc5dab4e189004a1312e9db43a40abb2de91ad7dd25e75880bf36016d8e9df524","transactions":[],"logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","receiptsRoot":"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421","extraData":"0x476574682f4c5649562f76312e302e302f6c696e75782f676f312e342e32","nonce":"0x29d6547c196e00e0","miner":"0xbb7b8287f3f0a933474a79eae42cbca977791171","difficulty":"0x494433b31","gasLimit":"0x1388","gasUsed":"0x0","uncles":[],"sha3Uncles":"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347","size":"0x220","transactionsRoot":"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421","stateRoot":"0x3fe6bd17aa85376c7d566df97d9f2e536f37f7a87abb3a6f9e2891cf9442f2e4","mixHash":"0x943056aa305aa6d22a3c06110942980342d1f4d4b11c17711961436a0f963ea0","parentHash":"0x7abfd11e862ccde76d6ea8ee20978aac26f4bcb55de1188cc0335be13e817017","timestamp":"0x55ba4564"} "#]]); }); // test for cast_rpc with arguments casttest!(rpc_format_as_json, |_prj, cmd| { let eth_rpc_url = next_http_rpc_endpoint(); // Call `cast rpc eth_getBlockByNumber 0x123 false` cmd.args(["rpc", "--rpc-url", eth_rpc_url.as_str(), "eth_getBlockByNumber", "0x123", "false", "--json"]) .assert_json_stdout(str![[r#" { "hash": "0xc5dab4e189004a1312e9db43a40abb2de91ad7dd25e75880bf36016d8e9df524", "parentHash": "0x7abfd11e862ccde76d6ea8ee20978aac26f4bcb55de1188cc0335be13e817017", "sha3Uncles": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", "miner": "0xbb7b8287f3f0a933474a79eae42cbca977791171", "stateRoot": "0x3fe6bd17aa85376c7d566df97d9f2e536f37f7a87abb3a6f9e2891cf9442f2e4", "transactionsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", "receiptsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "difficulty": "0x494433b31", "number": "0x123", "gasLimit": "0x1388", "gasUsed": "0x0", "timestamp": "0x55ba4564", "extraData": "0x476574682f4c5649562f76312e302e302f6c696e75782f676f312e342e32", "mixHash": "0x943056aa305aa6d22a3c06110942980342d1f4d4b11c17711961436a0f963ea0", "nonce": "0x29d6547c196e00e0", "size": "0x220", "uncles": [], "transactions": [] } "#]]); }); // test for cast_rpc with raw params casttest!(rpc_raw_params, |_prj, cmd| { let eth_rpc_url = next_http_rpc_endpoint(); // Call `cast rpc eth_getBlockByNumber --raw '["0x123", false]'` cmd.args([ "rpc", "--rpc-url", eth_rpc_url.as_str(), "eth_getBlockByNumber", "--raw", r#"["0x123", false]"#, ]) .assert_json_stdout(str![[r#" {"number":"0x123","hash":"0xc5dab4e189004a1312e9db43a40abb2de91ad7dd25e75880bf36016d8e9df524","transactions":[],"logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","receiptsRoot":"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421","extraData":"0x476574682f4c5649562f76312e302e302f6c696e75782f676f312e342e32","nonce":"0x29d6547c196e00e0","miner":"0xbb7b8287f3f0a933474a79eae42cbca977791171","difficulty":"0x494433b31","gasLimit":"0x1388","gasUsed":"0x0","uncles":[],"sha3Uncles":"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347","size":"0x220","transactionsRoot":"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421","stateRoot":"0x3fe6bd17aa85376c7d566df97d9f2e536f37f7a87abb3a6f9e2891cf9442f2e4","mixHash":"0x943056aa305aa6d22a3c06110942980342d1f4d4b11c17711961436a0f963ea0","parentHash":"0x7abfd11e862ccde76d6ea8ee20978aac26f4bcb55de1188cc0335be13e817017","timestamp":"0x55ba4564"} "#]]); }); // test for cast_rpc with direct params casttest!(rpc_raw_params_stdin, |_prj, cmd| { let eth_rpc_url = next_http_rpc_endpoint(); // Call `echo "\n[\n\"0x123\",\nfalse\n]\n" | cast rpc eth_getBlockByNumber --raw cmd.args(["rpc", "--rpc-url", eth_rpc_url.as_str(), "eth_getBlockByNumber", "--raw"]).stdin( b"\n[\n\"0x123\",\nfalse\n]\n" ) .assert_json_stdout(str![[r#" {"number":"0x123","hash":"0xc5dab4e189004a1312e9db43a40abb2de91ad7dd25e75880bf36016d8e9df524","transactions":[],"logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","receiptsRoot":"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421","extraData":"0x476574682f4c5649562f76312e302e302f6c696e75782f676f312e342e32","nonce":"0x29d6547c196e00e0","miner":"0xbb7b8287f3f0a933474a79eae42cbca977791171","difficulty":"0x494433b31","gasLimit":"0x1388","gasUsed":"0x0","uncles":[],"sha3Uncles":"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347","size":"0x220","transactionsRoot":"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421","stateRoot":"0x3fe6bd17aa85376c7d566df97d9f2e536f37f7a87abb3a6f9e2891cf9442f2e4","mixHash":"0x943056aa305aa6d22a3c06110942980342d1f4d4b11c17711961436a0f963ea0","parentHash":"0x7abfd11e862ccde76d6ea8ee20978aac26f4bcb55de1188cc0335be13e817017","timestamp":"0x55ba4564"} "#]]); }); // checks `cast calldata` can handle arrays casttest!(calldata_array, |_prj, cmd| { cmd.args(["calldata", "propose(string[])", "[\"\"]"]).assert_success().stdout_eq(str![[r#" 0xcde2baba0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000 "#]]); }); // casttest!(run_succeeds, |_prj, cmd| { let rpc = next_http_archive_rpc_url(); cmd.args([ "run", "-v", "0x2d951c5c95d374263ca99ad9c20c9797fc714330a8037429a3aa4c83d456f845", "--quick", "--rpc-url", rpc.as_str(), ]) .assert_success() .stdout_eq(str![[r#" ... Transaction successfully executed. [GAS] "#]]); }); // tests that `cast --to-base` commands are working correctly. casttest!(to_base, |_prj, cmd| { let values = [ "1", "100", "100000", "115792089237316195423570985008687907853269984665640564039457584007913129639935", "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "-1", "-100", "-100000", "-57896044618658097711785492504343953926634992332820282019728792003956564819968", ]; for value in values { for subcmd in ["--to-base", "--to-hex", "--to-dec"] { if subcmd == "--to-base" { for base in ["bin", "oct", "dec", "hex"] { cmd.cast_fuse().args([subcmd, value, base]); assert!(!cmd.assert_success().get_output().stdout_lossy().trim().is_empty()); } } else { cmd.cast_fuse().args([subcmd, value]); assert!(!cmd.assert_success().get_output().stdout_lossy().trim().is_empty()); } } } }); // tests that revert reason is only present if transaction has reverted. casttest!(receipt_revert_reason, |_prj, cmd| { let rpc = next_http_archive_rpc_url(); // cmd.args([ "receipt", "0x44f2aaa351460c074f2cb1e5a9e28cbc7d83f33e425101d2de14331c7b7ec31e", "--rpc-url", rpc.as_str(), ]) .assert_success() .stdout_eq(format!(r#" blockHash 0x2cfe65be49863676b6dbc04d58176a14f39b123f1e2f4fea0383a2d82c2c50d0 blockNumber 16239315 contractAddress {} cumulativeGasUsed 10743428 effectiveGasPrice 10539984136 from 0x199D5ED7F45F4eE35960cF22EAde2076e95B253F gasUsed 21000 logs [] logsBloom 0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 root {} status 1 (success) transactionHash 0x44f2aaa351460c074f2cb1e5a9e28cbc7d83f33e425101d2de14331c7b7ec31e transactionIndex 116 type 0 blobGasPrice {} blobGasUsed {} to 0x91da5bf3F8Eb72724E6f50Ec6C3D199C6355c59c "#,"", "", "", "")); let rpc = next_http_archive_rpc_url(); // cmd.cast_fuse() .args([ "receipt", "0x0e07d8b53ed3d91314c80e53cf25bcde02084939395845cbb625b029d568135c", "--rpc-url", rpc.as_str(), ]) .assert_success() .stdout_eq(format!(r#" blockHash 0x883f974b17ca7b28cb970798d1c80f4d4bb427473dc6d39b2a7fe24edc02902d blockNumber 14839405 contractAddress {} cumulativeGasUsed 20273649 effectiveGasPrice 21491736378 from 0x3cF412d970474804623bb4e3a42dE13F9bCa5436 gasUsed 24952 logs [] logsBloom 0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 root {} status 0 (failed) transactionHash 0x0e07d8b53ed3d91314c80e53cf25bcde02084939395845cbb625b029d568135c transactionIndex 173 type 2 blobGasPrice {} blobGasUsed {} to 0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45 revertReason [..]Transaction too old, data: "0x08c379a0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000135472616e73616374696f6e20746f6f206f6c6400000000000000000000000000" "#,"","","","")); }); // tests that the revert reason is loaded using the correct `from` address. // Flaky: Sepolia RPC may not return the revertReason field depending on provider // support for debug/trace APIs. casttest!(flaky_revert_reason_from, |_prj, cmd| { let rpc = next_rpc_endpoint(NamedChain::Sepolia); // https://sepolia.etherscan.io/tx/0x10ee70cf9f5ced5c515e8d53bfab5ea9f5c72cd61b25fba455c8355ee286c4e4 cmd.args([ "receipt", "0x10ee70cf9f5ced5c515e8d53bfab5ea9f5c72cd61b25fba455c8355ee286c4e4", "--rpc-url", rpc.as_str(), ]) .assert_success() .stdout_eq(format!(r#" blockHash 0x32663d7730c9ea8e1de6d99854483e25fcc05bb56c91c0cc82f9f04944fbffc1 blockNumber 7823353 contractAddress {} cumulativeGasUsed 7500797 effectiveGasPrice 14296851013 from 0x3583fF95f96b356d716881C871aF7Eb55ea34a93 gasUsed 25815 logs [] logsBloom 0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 root {} status 0 (failed) transactionHash 0x10ee70cf9f5ced5c515e8d53bfab5ea9f5c72cd61b25fba455c8355ee286c4e4 transactionIndex 96 type 0 blobGasPrice {} blobGasUsed {} to 0x91b5d4111a4C038153b24e31F75ccdC47123595d ... "#, "", "", "", "")); }); // tests that `cast --parse-bytes32-address` command is working correctly. casttest!(parse_bytes32_address, |_prj, cmd| { cmd.args([ "--parse-bytes32-address", "0x000000000000000000000000d8da6bf26964af9d7eed9e03e53415d37aa96045", ]) .assert_success() .stdout_eq(str![[r#" 0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045 "#]]); }); casttest!(access_list, |_prj, cmd| { let rpc = next_http_rpc_endpoint(); cmd.args([ "access-list", "0xbb2b8038a1640196fbe3e38816f3e67cba72d940", "skim(address)", "0xbb2b8038a1640196fbe3e38816f3e67cba72d940", "--rpc-url", rpc.as_str(), "--gas-limit", // need to set this for alchemy.io to avoid "intrinsic gas too low" error "100000", ]) .assert_success() .stdout_eq(str![[r#" [GAS] access list: - address: [..] keys: ... - address: [..] keys: ... - address: [..] keys: ... "#]]); }); casttest!(logs_topics, |_prj, cmd| { let rpc = next_http_archive_rpc_url(); cmd.args([ "logs", "--rpc-url", rpc.as_str(), "--from-block", "12421181", "--to-block", "12421182", "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", "0x000000000000000000000000ab5801a7d398351b8be11c439e05c5b3259aec9b", ]) .assert_success() .stdout_eq(file!["../fixtures/cast_logs.stdout"]); }); casttest!(logs_topic_2, |_prj, cmd| { let rpc = next_http_archive_rpc_url(); cmd.args([ "logs", "--rpc-url", rpc.as_str(), "--from-block", "12421181", "--to-block", "12421182", "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", "", "0x00000000000000000000000068a99f89e475a078645f4bac491360afe255dff1", /* Filter on the * `to` address */ ]) .assert_success() .stdout_eq(file!["../fixtures/cast_logs.stdout"]); }); casttest!(logs_sig, |_prj, cmd| { let rpc = next_http_archive_rpc_url(); cmd.args([ "logs", "--rpc-url", rpc.as_str(), "--from-block", "12421181", "--to-block", "12421182", "Transfer(address indexed from, address indexed to, uint256 value)", "0xAb5801a7D398351b8bE11C439e05C5B3259aeC9B", ]) .assert_success() .stdout_eq(file!["../fixtures/cast_logs.stdout"]); }); casttest!(logs_sig_2, |_prj, cmd| { let rpc = next_http_archive_rpc_url(); cmd.args([ "logs", "--rpc-url", rpc.as_str(), "--from-block", "12421181", "--to-block", "12421182", "Transfer(address indexed from, address indexed to, uint256 value)", "", "0x68A99f89E475a078645f4BAC491360aFe255Dff1", ]) .assert_success() .stdout_eq(file!["../fixtures/cast_logs.stdout"]); }); casttest!(logs_chunked_large_range, |_prj, cmd| { let rpc = next_http_archive_rpc_url(); cmd.args([ "logs", "--rpc-url", rpc.as_str(), "--from-block", "18000000", "--to-block", "18050000", "--query-size", "1000", "Transfer(address indexed from, address indexed to, uint256 value)", "0xA0b86a33E6441d02dd8C6B2b7E5D1E3eD7F73b4b", ]) .assert_success(); }); casttest!(mktx, |_prj, cmd| { cmd.args([ "mktx", "--private-key", "0x0000000000000000000000000000000000000000000000000000000000000001", "--chain", "1", "--nonce", "0", "--value", "100", "--gas-limit", "21000", "--gas-price", "10000000000", "--priority-gas-price", "1000000000", "0x0000000000000000000000000000000000000001", ]).assert_success().stdout_eq(str![[r#" 0x02f86b0180843b9aca008502540be4008252089400000000000000000000000000000000000000016480c001a070d55e79ed3ac9fc8f51e78eb91fd054720d943d66633f2eb1bc960f0126b0eca052eda05a792680de3181e49bab4093541f75b49d1ecbe443077b3660c836016a "#]]); }); // ensure recipient or code is required casttest!(mktx_requires_to, |_prj, cmd| { cmd.args([ "mktx", "--private-key", "0x0000000000000000000000000000000000000000000000000000000000000001", "--chain", "1", ]); cmd.assert_failure().stderr_eq(str![[r#" Error: Must specify a recipient address or contract code to deploy "#]]); }); casttest!(mktx_signer_from_mismatch, |_prj, cmd| { cmd.args([ "mktx", "--private-key", "0x0000000000000000000000000000000000000000000000000000000000000001", "--from", "0x0000000000000000000000000000000000000001", "--chain", "1", "0x0000000000000000000000000000000000000001", ]); cmd.assert_failure().stderr_eq(str![[r#" Error: The specified sender via CLI/env vars does not match the sender configured via the hardware wallet's HD Path. Please use the `--hd-path ` parameter to specify the BIP32 Path which corresponds to the sender, or let foundry automatically detect it by not specifying any sender address. "#]]); }); casttest!(mktx_signer_from_match, |_prj, cmd| { cmd.args([ "mktx", "--private-key", "0x0000000000000000000000000000000000000000000000000000000000000001", "--from", "0x7E5F4552091A69125d5DfCb7b8C2659029395Bdf", "--chain", "1", "--nonce", "0", "--gas-limit", "21000", "--gas-price", "10000000000", "--priority-gas-price", "1000000000", "0x0000000000000000000000000000000000000001", ]).assert_success().stdout_eq(str![[r#" 0x02f86b0180843b9aca008502540be4008252089400000000000000000000000000000000000000018080c001a0cce9a61187b5d18a89ecd27ec675e3b3f10d37f165627ef89a15a7fe76395ce8a07537f5bffb358ffbef22cda84b1c92f7211723f9e09ae037e81686805d3e5505 "#]]); }); casttest!(mktx_raw_unsigned, |_prj, cmd| { cmd.args([ "mktx", "--from", "0x7E5F4552091A69125d5DfCb7b8C2659029395Bdf", "--chain", "1", "--nonce", "0", "--gas-limit", "21000", "--gas-price", "10000000000", "--priority-gas-price", "1000000000", "0x0000000000000000000000000000000000000001", "--raw-unsigned", ]) .assert_success() .stdout_eq(str![[ r#"0x02e80180843b9aca008502540be4008252089400000000000000000000000000000000000000018080c0 "# ]]); }); casttest!(mktx_raw_unsigned_no_from_missing_chain, async |_prj, cmd| { // As chain is not provided, a query is made to the provider to get the chain id, before the // tx is built. Anvil is configured to use chain id 1 so that the produced tx will // be the same as in the `mktx_raw_unsigned` test. let (_, handle) = anvil::spawn(NodeConfig::test().with_chain_id(Some(1u64))).await; cmd.args([ "mktx", "--nonce", "0", "--gas-limit", "21000", "--gas-price", "10000000000", "--priority-gas-price", "1000000000", "0x0000000000000000000000000000000000000001", "--raw-unsigned", "--rpc-url", &handle.http_endpoint(), ]) .assert_success() .stdout_eq(str![[ r#"0x02e80180843b9aca008502540be4008252089400000000000000000000000000000000000000018080c0 "# ]]); }); casttest!(mktx_raw_unsigned_no_from_missing_gas_pricing, async |_prj, cmd| { let (_, handle) = anvil::spawn(NodeConfig::test()).await; cmd.args([ "mktx", "--nonce", "0", "0x0000000000000000000000000000000000000001", "--raw-unsigned", "--rpc-url", &handle.http_endpoint(), ]) .assert_success() .stdout_eq(str![[ r#"0x02e5827a69800184773594018252089400000000000000000000000000000000000000018080c0 "# ]]); }); casttest!(mktx_raw_unsigned_no_from_missing_nonce, |_prj, cmd| { cmd.args([ "mktx", "--chain", "1", "--gas-limit", "21000", "--gas-price", "20000000000", "0x742d35Cc6634C0532925a3b8D6Ac6F67C9c2b7FD", "--raw-unsigned", ]) .assert_failure() .stderr_eq(str![[ r#"Error: Missing required parameters for raw unsigned transaction. When --from is not provided, you must specify: --nonce "# ]]); }); 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(); // cmd.args([ "tx", "0x44f2aaa351460c074f2cb1e5a9e28cbc7d83f33e425101d2de14331c7b7ec31e", "raw", "--rpc-url", rpc.as_str(), ]).assert_success().stdout_eq(str![[r#" 0xf86d824c548502743b65088275309491da5bf3f8eb72724e6f50ec6c3d199c6355c59c87a0a73f33e9e4cc8025a0428518b1748a08bbeb2392ea055b418538944d30adfc2accbbfa8362a401d3a4a07d6093ab2580efd17c11b277de7664fce56e6953cae8e925bec3313399860470 "#]]); // cmd.cast_fuse().args([ "tx", "0x44f2aaa351460c074f2cb1e5a9e28cbc7d83f33e425101d2de14331c7b7ec31e", "--raw", "--rpc-url", rpc.as_str(), ]).assert_success().stdout_eq(str![[r#" 0xf86d824c548502743b65088275309491da5bf3f8eb72724e6f50ec6c3d199c6355c59c87a0a73f33e9e4cc8025a0428518b1748a08bbeb2392ea055b418538944d30adfc2accbbfa8362a401d3a4a07d6093ab2580efd17c11b277de7664fce56e6953cae8e925bec3313399860470 "#]]); }); casttest!(tx_to_request_json, |_prj, cmd| { let rpc = next_http_rpc_endpoint(); // cmd.args([ "tx", "0x44f2aaa351460c074f2cb1e5a9e28cbc7d83f33e425101d2de14331c7b7ec31e", "--to-request", "--rpc-url", rpc.as_str(), ]) .assert_success() .stdout_eq(str![[r#" { "from": "0x199d5ed7f45f4ee35960cf22eade2076e95b253f", "to": "0x91da5bf3f8eb72724e6f50ec6c3d199c6355c59c", "gasPrice": "0x2743b6508", "gas": "0x7530", "value": "0xa0a73f33e9e4cc", "input": "0x", "nonce": "0x4c54", "chainId": "0x1", "type": "0x0" } "#]]); }); casttest!(tx_using_sender_and_nonce, |_prj, cmd| { let rpc = next_http_archive_rpc_url(); // let args = vec![ "tx", "--from", "0x4648451b5F87FF8F0F7D622bD40574bb97E25980", "--nonce", "113642", "--rpc-url", rpc.as_str(), ]; cmd.args(args).assert_success().stdout_eq(str![[r#" blockHash 0x29518c1cea251b1bda5949a9b039722604ec1fb99bf9d8124cfe001c95a50bdc blockNumber 22287055 from 0x4648451b5F87FF8F0F7D622bD40574bb97E25980 transactionIndex 230 effectiveGasPrice 363392048 hash 0x5bcd22734cca2385dc25b2d38a3d33a640c5961bd46d390dff184c894204b594 type 2 chainId 1 nonce 113642 gasLimit 350000 maxFeePerGas 675979146 maxPriorityFeePerGas 1337 to 0xdAC17F958D2ee523a2206206994597C13D831ec7 value 0 accessList [] input 0xa9059cbb000000000000000000000000568766d218d82333dd4dae933ddfcda5da26625000000000000000000000000000000000000000000000000000000000cc3ed109 r 0x1e92d3e1ca69109a1743fc4b3cf9dff58630bc9f429cea3c3fe311506264e36c s 0x793947d4bbdce56a1a5b2b3525c46f01569414a22355f4883b5429668ab0f51a yParity 1 ... "#]]); }); // ensure receipt or code is required casttest!(send_requires_to, |_prj, cmd| { cmd.args([ "send", "--private-key", "0x0000000000000000000000000000000000000000000000000000000000000001", "--chain", "1", ]); cmd.assert_failure().stderr_eq(str![[r#" 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![ [r#" 0x0000000000000000000000000000000000000000000000000000000000000000 "#] ]); let rpc = next_http_archive_rpc_url(); cmd.cast_fuse() .args(["storage", "vitalik.eth", "0x01", "--rpc-url", &rpc]) .assert_success() .stdout_eq(str![[r#" 0x0000000000000000000000000000000000000000000000000000000000000000 "#]]); let rpc = next_http_archive_rpc_url(); let usdt = "0xdac17f958d2ee523a2206206994597c13d831ec7"; let decimals_slot = "0x09"; cmd.cast_fuse() .args(["storage", usdt, decimals_slot, "--rpc-url", &rpc]) .assert_success() .stdout_eq(str![[r#" 0x0000000000000000000000000000000000000000000000000000000000000006 "#]]); let rpc = next_http_archive_rpc_url(); let total_supply_slot = "0x01"; let block_before = "4634747"; let block_after = "4634748"; cmd.cast_fuse() .args(["storage", usdt, total_supply_slot, "--rpc-url", &rpc, "--block", block_before]) .assert_success() .stdout_eq(str![[r#" 0x0000000000000000000000000000000000000000000000000000000000000000 "#]]); let rpc = next_http_archive_rpc_url(); cmd.cast_fuse() .args(["storage", usdt, total_supply_slot, "--rpc-url", &rpc, "--block", block_after]) .assert_success() .stdout_eq(str![[r#" 0x000000000000000000000000000000000000000000000000000000174876e800 "#]]); let decimal_slot_offset_from_total_supply_slot = "0x08"; let decimal_slot_offset_from_total_supply_slot_uint = "8"; let rpc = next_http_archive_rpc_url(); cmd.cast_fuse() .args([ "storage", usdt, total_supply_slot, decimal_slot_offset_from_total_supply_slot, "--rpc-url", &rpc, ]) .assert_success() .stdout_eq(str![[r#" 0x0000000000000000000000000000000000000000000000000000000000000006 "#]]); let rpc = next_http_archive_rpc_url(); cmd.cast_fuse() .args([ "storage", usdt, total_supply_slot, decimal_slot_offset_from_total_supply_slot_uint, "--rpc-url", &rpc, ]) .assert_success() .stdout_eq(str![[r#" 0x0000000000000000000000000000000000000000000000000000000000000006 "#]]); }); casttest!(flaky_storage_with_valid_solc_version_1, |_prj, cmd| { cmd.args([ "storage", "0x13b0D85CcB8bf860b6b79AF3029fCA081AE9beF2", "--solc-version", "0.8.10", "--rpc-url", next_http_archive_rpc_url().as_str(), "--etherscan-api-key", next_etherscan_api_key().as_str(), ]) .assert_success(); }); casttest!(flaky_storage_with_valid_solc_version_2, |_prj, cmd| { cmd.args([ "storage", "0x13b0D85CcB8bf860b6b79AF3029fCA081AE9beF2", "--solc-version", "0.8.23", "--rpc-url", next_http_archive_rpc_url().as_str(), "--etherscan-api-key", next_etherscan_api_key().as_str(), ]) .assert_success(); }); casttest!(flaky_storage_with_invalid_solc_version_1, |_prj, cmd| { let output = cmd .args([ "storage", "0x13b0D85CcB8bf860b6b79AF3029fCA081AE9beF2", "--solc-version", "0.4.0", "--rpc-url", next_http_archive_rpc_url().as_str(), "--etherscan-api-key", next_etherscan_api_key().as_str(), ]) .assert_failure() .get_output() .stderr .clone(); let stderr = String::from_utf8_lossy(&output); assert!( stderr.contains( "Warning: The provided --solc-version is 0.4.0 while the minimum version for storage layouts is 0.6.5" ), "stderr did not contain expected warning. Full stderr:\n{stderr}" ); }); casttest!(flaky_storage_with_invalid_solc_version_2, |_prj, cmd| { cmd.args([ "storage", "0x13b0D85CcB8bf860b6b79AF3029fCA081AE9beF2", "--solc-version", "0.8.2", "--rpc-url", next_http_archive_rpc_url().as_str(), "--etherscan-api-key", next_etherscan_api_key().as_str(), ]) .assert_failure() .stderr_eq(str![[r#" Error: Encountered invalid solc version in contracts/Create2Deployer.sol: No solc version exists that matches the version requirement: ^0.8.9 "#]]); }); // casttest!(flaky_storage_layout_simple, |_prj, cmd| { cmd.args([ "storage", "--rpc-url", next_http_archive_rpc_url().as_str(), "--block", "21034138", "--etherscan-api-key", next_etherscan_api_key().as_str(), "0x13b0D85CcB8bf860b6b79AF3029fCA081AE9beF2", ]) .assert_success() .stdout_eq(str![[r#" ╭---------+---------+------+--------+-------+-------+--------------------------------------------------------------------+-----------------------------------------------╮ | Name | Type | Slot | Offset | Bytes | Value | Hex Value | Contract | +========================================================================================================================================================================+ | _owner | address | 0 | 0 | 20 | 0 | 0x0000000000000000000000000000000000000000000000000000000000000000 | contracts/Create2Deployer.sol:Create2Deployer | |---------+---------+------+--------+-------+-------+--------------------------------------------------------------------+-----------------------------------------------| | _paused | bool | 0 | 20 | 1 | 0 | 0x0000000000000000000000000000000000000000000000000000000000000000 | contracts/Create2Deployer.sol:Create2Deployer | ╰---------+---------+------+--------+-------+-------+--------------------------------------------------------------------+-----------------------------------------------╯ "#]]); }); // casttest!(flaky_storage_layout_simple_json, |_prj, cmd| { cmd.args([ "storage", "--rpc-url", next_http_archive_rpc_url().as_str(), "--block", "21034138", "--etherscan-api-key", next_etherscan_api_key().as_str(), "0x13b0D85CcB8bf860b6b79AF3029fCA081AE9beF2", "--json", ]) .assert_success() .stdout_eq(file!["../fixtures/storage_layout_simple.json": Json]); }); // casttest!(flaky_storage_layout_complex, |_prj, cmd| { cmd.args([ "storage", "--rpc-url", next_http_archive_rpc_url().as_str(), "--block", "21034138", "--etherscan-api-key", next_etherscan_api_key().as_str(), "0xBA12222222228d8Ba445958a75a0704d566BF2C8", ]) .assert_success() .stdout_eq(str![[r#" ╭-------------------------------+--------------------------------------------------------------------+------+--------+-------+--------------------------------------------------+--------------------------------------------------------------------+---------------------------------╮ | Name | Type | Slot | Offset | Bytes | Value | Hex Value | Contract | +======================================================================================================================================================================================================================================================================================+ | _status | uint256 | 0 | 0 | 32 | 1 | 0x0000000000000000000000000000000000000000000000000000000000000001 | contracts/vault/Vault.sol:Vault | |-------------------------------+--------------------------------------------------------------------+------+--------+-------+--------------------------------------------------+--------------------------------------------------------------------+---------------------------------| | _generalPoolsBalances | mapping(bytes32 => struct EnumerableMap.IERC20ToBytes32Map) | 1 | 0 | 32 | 0 | 0x0000000000000000000000000000000000000000000000000000000000000000 | contracts/vault/Vault.sol:Vault | |-------------------------------+--------------------------------------------------------------------+------+--------+-------+--------------------------------------------------+--------------------------------------------------------------------+---------------------------------| | _nextNonce | mapping(address => uint256) | 2 | 0 | 32 | 0 | 0x0000000000000000000000000000000000000000000000000000000000000000 | contracts/vault/Vault.sol:Vault | |-------------------------------+--------------------------------------------------------------------+------+--------+-------+--------------------------------------------------+--------------------------------------------------------------------+---------------------------------| | _paused | bool | 3 | 0 | 1 | 0 | 0x0000000000000000000000000000000000000000000000000000000000000000 | contracts/vault/Vault.sol:Vault | |-------------------------------+--------------------------------------------------------------------+------+--------+-------+--------------------------------------------------+--------------------------------------------------------------------+---------------------------------| | _authorizer | contract IAuthorizer | 3 | 1 | 20 | 549683469959765988649777481110995959958745616871 | 0x0000000000000000000000006048a8c631fb7e77eca533cf9c29784e482391e7 | contracts/vault/Vault.sol:Vault | |-------------------------------+--------------------------------------------------------------------+------+--------+-------+--------------------------------------------------+--------------------------------------------------------------------+---------------------------------| | _approvedRelayers | mapping(address => mapping(address => bool)) | 4 | 0 | 32 | 0 | 0x0000000000000000000000000000000000000000000000000000000000000000 | contracts/vault/Vault.sol:Vault | |-------------------------------+--------------------------------------------------------------------+------+--------+-------+--------------------------------------------------+--------------------------------------------------------------------+---------------------------------| | _isPoolRegistered | mapping(bytes32 => bool) | 5 | 0 | 32 | 0 | 0x0000000000000000000000000000000000000000000000000000000000000000 | contracts/vault/Vault.sol:Vault | |-------------------------------+--------------------------------------------------------------------+------+--------+-------+--------------------------------------------------+--------------------------------------------------------------------+---------------------------------| | _nextPoolNonce | uint256 | 6 | 0 | 32 | 1760 | 0x00000000000000000000000000000000000000000000000000000000000006e0 | contracts/vault/Vault.sol:Vault | |-------------------------------+--------------------------------------------------------------------+------+--------+-------+--------------------------------------------------+--------------------------------------------------------------------+---------------------------------| | _minimalSwapInfoPoolsBalances | mapping(bytes32 => mapping(contract IERC20 => bytes32)) | 7 | 0 | 32 | 0 | 0x0000000000000000000000000000000000000000000000000000000000000000 | contracts/vault/Vault.sol:Vault | |-------------------------------+--------------------------------------------------------------------+------+--------+-------+--------------------------------------------------+--------------------------------------------------------------------+---------------------------------| | _minimalSwapInfoPoolsTokens | mapping(bytes32 => struct EnumerableSet.AddressSet) | 8 | 0 | 32 | 0 | 0x0000000000000000000000000000000000000000000000000000000000000000 | contracts/vault/Vault.sol:Vault | |-------------------------------+--------------------------------------------------------------------+------+--------+-------+--------------------------------------------------+--------------------------------------------------------------------+---------------------------------| | _twoTokenPoolTokens | mapping(bytes32 => struct TwoTokenPoolsBalance.TwoTokenPoolTokens) | 9 | 0 | 32 | 0 | 0x0000000000000000000000000000000000000000000000000000000000000000 | contracts/vault/Vault.sol:Vault | |-------------------------------+--------------------------------------------------------------------+------+--------+-------+--------------------------------------------------+--------------------------------------------------------------------+---------------------------------| | _poolAssetManagers | mapping(bytes32 => mapping(contract IERC20 => address)) | 10 | 0 | 32 | 0 | 0x0000000000000000000000000000000000000000000000000000000000000000 | contracts/vault/Vault.sol:Vault | |-------------------------------+--------------------------------------------------------------------+------+--------+-------+--------------------------------------------------+--------------------------------------------------------------------+---------------------------------| | _internalTokenBalance | mapping(address => mapping(contract IERC20 => uint256)) | 11 | 0 | 32 | 0 | 0x0000000000000000000000000000000000000000000000000000000000000000 | contracts/vault/Vault.sol:Vault | ╰-------------------------------+--------------------------------------------------------------------+------+--------+-------+--------------------------------------------------+--------------------------------------------------------------------+---------------------------------╯ "#]]); }); casttest!(flaky_storage_layout_complex_md, |_prj, cmd| { cmd.args([ "storage", "--rpc-url", next_http_archive_rpc_url().as_str(), "--block", "21034138", "--etherscan-api-key", next_etherscan_api_key().as_str(), "0xBA12222222228d8Ba445958a75a0704d566BF2C8", "--md", ]) .assert_success() .stdout_eq(str![[r#" | Name | Type | Slot | Offset | Bytes | Value | Hex Value | Contract | |-------------------------------|--------------------------------------------------------------------|------|--------|-------|--------------------------------------------------|--------------------------------------------------------------------|---------------------------------| | _status | uint256 | 0 | 0 | 32 | 1 | 0x0000000000000000000000000000000000000000000000000000000000000001 | contracts/vault/Vault.sol:Vault | | _generalPoolsBalances | mapping(bytes32 => struct EnumerableMap.IERC20ToBytes32Map) | 1 | 0 | 32 | 0 | 0x0000000000000000000000000000000000000000000000000000000000000000 | contracts/vault/Vault.sol:Vault | | _nextNonce | mapping(address => uint256) | 2 | 0 | 32 | 0 | 0x0000000000000000000000000000000000000000000000000000000000000000 | contracts/vault/Vault.sol:Vault | | _paused | bool | 3 | 0 | 1 | 0 | 0x0000000000000000000000000000000000000000000000000000000000000000 | contracts/vault/Vault.sol:Vault | | _authorizer | contract IAuthorizer | 3 | 1 | 20 | 549683469959765988649777481110995959958745616871 | 0x0000000000000000000000006048a8c631fb7e77eca533cf9c29784e482391e7 | contracts/vault/Vault.sol:Vault | | _approvedRelayers | mapping(address => mapping(address => bool)) | 4 | 0 | 32 | 0 | 0x0000000000000000000000000000000000000000000000000000000000000000 | contracts/vault/Vault.sol:Vault | | _isPoolRegistered | mapping(bytes32 => bool) | 5 | 0 | 32 | 0 | 0x0000000000000000000000000000000000000000000000000000000000000000 | contracts/vault/Vault.sol:Vault | | _nextPoolNonce | uint256 | 6 | 0 | 32 | 1760 | 0x00000000000000000000000000000000000000000000000000000000000006e0 | contracts/vault/Vault.sol:Vault | | _minimalSwapInfoPoolsBalances | mapping(bytes32 => mapping(contract IERC20 => bytes32)) | 7 | 0 | 32 | 0 | 0x0000000000000000000000000000000000000000000000000000000000000000 | contracts/vault/Vault.sol:Vault | | _minimalSwapInfoPoolsTokens | mapping(bytes32 => struct EnumerableSet.AddressSet) | 8 | 0 | 32 | 0 | 0x0000000000000000000000000000000000000000000000000000000000000000 | contracts/vault/Vault.sol:Vault | | _twoTokenPoolTokens | mapping(bytes32 => struct TwoTokenPoolsBalance.TwoTokenPoolTokens) | 9 | 0 | 32 | 0 | 0x0000000000000000000000000000000000000000000000000000000000000000 | contracts/vault/Vault.sol:Vault | | _poolAssetManagers | mapping(bytes32 => mapping(contract IERC20 => address)) | 10 | 0 | 32 | 0 | 0x0000000000000000000000000000000000000000000000000000000000000000 | contracts/vault/Vault.sol:Vault | | _internalTokenBalance | mapping(address => mapping(contract IERC20 => uint256)) | 11 | 0 | 32 | 0 | 0x0000000000000000000000000000000000000000000000000000000000000000 | contracts/vault/Vault.sol:Vault | "#]]); }); casttest!(flaky_storage_layout_complex_proxy, |_prj, cmd| { cmd.args([ "storage", "--rpc-url", next_rpc_endpoint(NamedChain::Sepolia).as_str(), "--block", "7857852", "--etherscan-api-key", next_etherscan_api_key().as_str(), "0xE2588A9CAb7Ea877206E35f615a39f84a64A7A3b", "--proxy", "0x29fcb43b46531bca003ddc8fcb67ffe91900c762" ]) .assert_success() .stdout_eq(str![[r#" ╭----------------------------+-------------------------------------------------+------+--------+-------+--------------------------------------------------+--------------------------------------------------------------------+-----------------------------╮ | Name | Type | Slot | Offset | Bytes | Value | Hex Value | Contract | +============================================================================================================================================================================================================================================================+ | singleton | address | 0 | 0 | 20 | 239704109775411986678417050956533140837380441954 | 0x00000000000000000000000029fcb43b46531bca003ddc8fcb67ffe91900c762 | contracts/SafeL2.sol:SafeL2 | |----------------------------+-------------------------------------------------+------+--------+-------+--------------------------------------------------+--------------------------------------------------------------------+-----------------------------| | modules | mapping(address => address) | 1 | 0 | 32 | 0 | 0x0000000000000000000000000000000000000000000000000000000000000000 | contracts/SafeL2.sol:SafeL2 | |----------------------------+-------------------------------------------------+------+--------+-------+--------------------------------------------------+--------------------------------------------------------------------+-----------------------------| | owners | mapping(address => address) | 2 | 0 | 32 | 0 | 0x0000000000000000000000000000000000000000000000000000000000000000 | contracts/SafeL2.sol:SafeL2 | |----------------------------+-------------------------------------------------+------+--------+-------+--------------------------------------------------+--------------------------------------------------------------------+-----------------------------| | ownerCount | uint256 | 3 | 0 | 32 | 1 | 0x0000000000000000000000000000000000000000000000000000000000000001 | contracts/SafeL2.sol:SafeL2 | |----------------------------+-------------------------------------------------+------+--------+-------+--------------------------------------------------+--------------------------------------------------------------------+-----------------------------| | threshold | uint256 | 4 | 0 | 32 | 1 | 0x0000000000000000000000000000000000000000000000000000000000000001 | contracts/SafeL2.sol:SafeL2 | |----------------------------+-------------------------------------------------+------+--------+-------+--------------------------------------------------+--------------------------------------------------------------------+-----------------------------| | nonce | uint256 | 5 | 0 | 32 | 0 | 0x0000000000000000000000000000000000000000000000000000000000000000 | contracts/SafeL2.sol:SafeL2 | |----------------------------+-------------------------------------------------+------+--------+-------+--------------------------------------------------+--------------------------------------------------------------------+-----------------------------| | _deprecatedDomainSeparator | bytes32 | 6 | 0 | 32 | 0 | 0x0000000000000000000000000000000000000000000000000000000000000000 | contracts/SafeL2.sol:SafeL2 | |----------------------------+-------------------------------------------------+------+--------+-------+--------------------------------------------------+--------------------------------------------------------------------+-----------------------------| | signedMessages | mapping(bytes32 => uint256) | 7 | 0 | 32 | 0 | 0x0000000000000000000000000000000000000000000000000000000000000000 | contracts/SafeL2.sol:SafeL2 | |----------------------------+-------------------------------------------------+------+--------+-------+--------------------------------------------------+--------------------------------------------------------------------+-----------------------------| | approvedHashes | mapping(address => mapping(bytes32 => uint256)) | 8 | 0 | 32 | 0 | 0x0000000000000000000000000000000000000000000000000000000000000000 | contracts/SafeL2.sol:SafeL2 | ╰----------------------------+-------------------------------------------------+------+--------+-------+--------------------------------------------------+--------------------------------------------------------------------+-----------------------------╯ "#]]); }); casttest!(flaky_storage_layout_complex_json, |_prj, cmd| { cmd.args([ "storage", "--rpc-url", next_http_archive_rpc_url().as_str(), "--block", "21034138", "--etherscan-api-key", next_etherscan_api_key().as_str(), "0xBA12222222228d8Ba445958a75a0704d566BF2C8", "--json", ]) .assert_success() .stdout_eq(file!["../fixtures/storage_layout_complex.json": Json]); }); casttest!(balance, |_prj, cmd| { let rpc = next_http_rpc_endpoint(); let dai = "0x6B175474E89094C44Da98b954EedeAC495271d0F"; let dai_result = cmd .args([ "balance", "0x0000000000000000000000000000000000000000", "--erc20", dai, "--rpc-url", &rpc, ]) .assert_success() .get_output() .stdout_lossy() .trim() .to_string(); let alias_result = cmd .cast_fuse() .args([ "balance", "0x0000000000000000000000000000000000000000", "--erc721", dai, "--rpc-url", &rpc, ]) .assert_success() .get_output() .stdout_lossy() .trim() .to_string(); assert_ne!(dai_result, "0"); assert_eq!(alias_result, dai_result); }); // tests that `cast interface` excludes the constructor // casttest!(interface_no_constructor, |prj, cmd| { let interface = include_str!("../fixtures/interface.json"); let path = prj.root().join("interface.json"); fs::write(&path, interface).unwrap(); // Call `cast find-block` cmd.arg("interface").arg(&path).assert_success().stdout_eq(str![[ r#"// SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.4; library IIntegrationManager { type SpendAssetsHandleType is uint8; } interface Interface { function getIntegrationManager() external view returns (address integrationManager_); function lend(address _vaultProxy, bytes memory, bytes memory _assetData) external; function parseAssetsForAction(address, bytes4 _selector, bytes memory _actionData) external view returns ( IIntegrationManager.SpendAssetsHandleType spendAssetsHandleType_, address[] memory spendAssets_, uint256[] memory spendAssetAmounts_, address[] memory incomingAssets_, uint256[] memory minIncomingAssetAmounts_ ); function redeem(address _vaultProxy, bytes memory, bytes memory _assetData) external; } "# ]]); }); // tests that `cast interface --flatten` inlines inherited struct types into the interface // casttest!(interface_flatten, |prj, cmd| { let interface = include_str!("../fixtures/interface_inherited_struct.json"); let path = prj.root().join("interface_inherited_struct.json"); fs::write(&path, interface).unwrap(); // Without --flatten, a separate library is generated for the struct cmd.arg("interface").arg(&path).assert_success().stdout_eq(str![[ r#"// SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.4; library IBase { struct TestStruct { address asset; } } interface Interface { function test(IBase.TestStruct memory param) external; } "# ]]); // With --flatten, the struct is inlined into the interface cmd.cast_fuse().arg("interface").arg("--flatten").arg(&path).assert_success().stdout_eq(str![ [r#"// SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.4; interface Interface { // Types from `IBase` struct TestStruct { address asset; } function test(TestStruct memory param) external; } "#] ]); }); // tests that fetches WETH interface from etherscan // casttest!(flaky_fetch_weth_interface_from_etherscan, |_prj, cmd| { cmd.args([ "interface", "--etherscan-api-key", &next_etherscan_api_key(), "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", ]) .assert_success() .stdout_eq(str![[r#" // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.4; interface WETH9 { event Approval(address indexed src, address indexed guy, uint256 wad); event Deposit(address indexed dst, uint256 wad); event Transfer(address indexed src, address indexed dst, uint256 wad); event Withdrawal(address indexed src, uint256 wad); fallback() external payable; function allowance(address, address) external view returns (uint256); function approve(address guy, uint256 wad) external returns (bool); function balanceOf(address) external view returns (uint256); function decimals() external view returns (uint8); function deposit() external payable; function name() external view returns (string memory); function symbol() external view returns (string memory); function totalSupply() external view returns (uint256); function transfer(address dst, uint256 wad) external returns (bool); function transferFrom(address src, address dst, uint256 wad) external returns (bool); function withdraw(uint256 wad) external; } "#]]); }); casttest!(ens_namehash, |_prj, cmd| { cmd.args(["namehash", "emo.eth"]).assert_success().stdout_eq(str![[r#" 0x0a21aaf2f6414aa664deb341d1114351fdb023cad07bf53b28e57c26db681910 "#]]); }); casttest!(ens_lookup, |_prj, cmd| { let eth_rpc_url = next_http_rpc_endpoint(); cmd.args([ "lookup-address", "0x28679A1a632125fbBf7A68d850E50623194A709E", "--rpc-url", ð_rpc_url, "--verify", ]) .assert_success() .stdout_eq(str![[r#" emo.eth "#]]); }); casttest!(ens_resolve, |_prj, cmd| { let eth_rpc_url = next_http_rpc_endpoint(); cmd.args(["resolve-name", "emo.eth", "--rpc-url", ð_rpc_url, "--verify"]) .assert_success() .stdout_eq(str![[r#" 0x28679A1a632125fbBf7A68d850E50623194A709E "#]]); }); casttest!(ens_resolve_no_dot_eth, |_prj, cmd| { let eth_rpc_url = next_http_rpc_endpoint(); cmd.args(["resolve-name", "emo", "--rpc-url", ð_rpc_url, "--verify"]) .assert_failure() .stderr_eq(str![[r#" Error: ENS resolver not found for name "emo" "#]]); }); casttest!(index7201, |_prj, cmd| { cmd.args(["index-erc7201", "example.main"]).assert_success().stdout_eq(str![[r#" 0x183a6125c38840424c4a85fa12bab2ab606c4b6d0e7cc73c0c06ba5300eab500 "#]]); }); casttest!(index7201_unknown_formula_id, |_prj, cmd| { cmd.args(["index-erc7201", "test", "--formula-id", "unknown"]).assert_failure().stderr_eq( str![[r#" Error: unsupported formula ID: unknown "#]], ); }); casttest!(block_number, |_prj, cmd| { let eth_rpc_url = next_http_rpc_endpoint(); let s = cmd .args(["block-number", "--rpc-url", eth_rpc_url.as_str()]) .assert_success() .get_output() .stdout_lossy(); assert!(s.trim().parse::().unwrap() > 0, "{s}") }); casttest!(block_number_latest, |_prj, cmd| { let eth_rpc_url = next_http_rpc_endpoint(); let s = cmd .args(["block-number", "--rpc-url", eth_rpc_url.as_str(), "latest"]) .assert_success() .get_output() .stdout_lossy(); assert!(s.trim().parse::().unwrap() > 0, "{s}") }); casttest!(block_number_hash, |_prj, cmd| { let eth_rpc_url = next_http_rpc_endpoint(); let s = cmd .args([ "block-number", "--rpc-url", eth_rpc_url.as_str(), "0x88e96d4537bea4d9c05d12549907b32561d3bf31f45aae734cdc119f13406cb6", ]) .assert_success() .get_output() .stdout_lossy(); assert_eq!(s.trim().parse::().unwrap(), 1, "{s}") }); // Tests that `cast --disable-block-gas-limit` commands are working correctly for BSC // // Equivalent transaction on Binance Smart Chain Testnet: // casttest!(run_disable_block_gas_limit_check, |_prj, cmd| { let bsc_testnet_rpc_url = next_rpc_endpoint(NamedChain::BinanceSmartChainTestnet); let latest_block_json: serde_json::Value = serde_json::from_str( &cmd.args(["block", "--rpc-url", bsc_testnet_rpc_url.as_str(), "--json"]) .assert_success() .get_output() .stdout_lossy(), ) .expect("Failed to parse latest block"); let latest_excessive_gas_limit_tx = latest_block_json["transactions"].as_array().and_then(|txs| { txs.iter() .find(|tx| tx.get("gas").and_then(|gas| gas.as_str()) == Some("0x7fffffffffffffff")) }); match latest_excessive_gas_limit_tx { Some(tx) => { let tx_hash = tx.get("hash").and_then(|h| h.as_str()).expect("Transaction missing hash"); // If --disable-block-gas-limit is not provided, the transaction should fail as the gas // limit exceeds the block gas limit. cmd.cast_fuse() .args(["run", "-v", tx_hash, "--quick", "--rpc-url", bsc_testnet_rpc_url.as_str()]) .assert_failure() .stderr_eq(str![[r#" Error: EVM error; transaction validation error: caller gas limit exceeds the block gas limit "#]]); // If --disable-block-gas-limit is provided, the transaction should succeed // despite the gas limit exceeding the block gas limit. cmd.cast_fuse() .args([ "run", "-v", tx_hash, "--quick", "--rpc-url", bsc_testnet_rpc_url.as_str(), "--disable-block-gas-limit", ]) .assert_success() .stdout_eq(str![[r#" ... Transaction successfully executed. [GAS] "#]]); } None => { eprintln!( "Skipping test: No transaction with gas = 0x7fffffffffffffff found in the latest block." ); } } }); casttest!(send_eip7702, async |_prj, cmd| { let (_api, handle) = anvil::spawn(NodeConfig::test().with_hardfork(Some(EthereumHardfork::Prague.into()))).await; let endpoint = handle.http_endpoint(); cmd.args([ "send", "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", "--auth", "0x70997970C51812dc3A010C7d01b50e0d17dc79C8", "--private-key", "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80", "--rpc-url", &endpoint, ]) .assert_success(); cmd.cast_fuse() .args(["code", "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", "--rpc-url", &endpoint]) .assert_success() .stdout_eq(str![[r#" 0xef010070997970c51812dc3a010c7d01b50e0d17dc79c8 "#]]); }); casttest!(send_eip7702_multiple_auth, async |_prj, cmd| { let (_api, handle) = anvil::spawn(NodeConfig::test().with_hardfork(Some(EthereumHardfork::Prague.into()))).await; let endpoint = handle.http_endpoint(); // Create a pre-signed authorization using a different signer (account index 1) let signer: PrivateKeySigner = "0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d".parse().unwrap(); // Anvil default chain_id is 31337 let auth = Authorization { chain_id: U256::from(31337), // Delegate to account index 2 address: address!("0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC"), nonce: 0, }; let signature = signer.sign_hash(&auth.signature_hash()).await.unwrap(); let signed_auth = auth.into_signed(signature); let encoded_auth = hex::encode_prefixed(alloy_rlp::encode(&signed_auth)); // Send transaction with multiple --auth flags: one address and one pre-signed authorization let output = cmd .args([ "send", "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", "--auth", "0x70997970C51812dc3A010C7d01b50e0d17dc79C8", "--auth", &encoded_auth, "--private-key", "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80", "--rpc-url", &endpoint, "--gas-limit", "100000", "--json", ]) .assert_success() .get_output() .stdout_lossy(); // Extract transaction hash from JSON output let json: serde_json::Value = serde_json::from_str(&output).unwrap(); let tx_hash = json["transactionHash"].as_str().unwrap(); // Use cast tx to verify multiple authorizations were included let tx_output = cmd .cast_fuse() .args(["tx", tx_hash, "--rpc-url", &endpoint, "--json"]) .assert_success() .get_output() .stdout_lossy(); let tx_json: serde_json::Value = serde_json::from_str(&tx_output).unwrap(); let auth_list = tx_json["authorizationList"].as_array().unwrap(); // Verify we have 2 authorizations assert_eq!(auth_list.len(), 2, "Expected 2 authorizations in the transaction"); }); // Test that multiple address-based authorizations are rejected casttest!(send_eip7702_multiple_address_auth_rejected, async |_prj, cmd| { let (_api, handle) = anvil::spawn(NodeConfig::test().with_hardfork(Some(EthereumHardfork::Prague.into()))).await; let endpoint = handle.http_endpoint(); cmd.args([ "send", "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", "--auth", "0x70997970C51812dc3A010C7d01b50e0d17dc79C8", "--auth", "0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC", "--private-key", "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80", "--rpc-url", &endpoint, ]); cmd.assert_failure().stderr_eq(str![[r#" Error: Multiple address-based authorizations provided. Only one address can be specified; use pre-signed authorizations (hex-encoded) for multiple authorizations. "#]]); }); casttest!(send_sync, async |_prj, cmd| { let (_api, handle) = anvil::spawn(NodeConfig::test()).await; let endpoint = handle.http_endpoint(); let output = cmd .args([ "send", "0x70997970C51812dc3A010C7d01b50e0d17dc79C8", "--value", "1", "--private-key", "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80", "--rpc-url", &endpoint, "--sync", ]) .assert_success() .get_output() .stdout_lossy(); assert!(output.contains("transactionHash")); assert!(output.contains("blockNumber")); assert!(output.contains("gasUsed")); }); casttest!(hash_message, |_prj, cmd| { cmd.args(["hash-message", "hello"]).assert_success().stdout_eq(str![[r#" 0x50b2c43fd39106bafbba0da34fc430e1f91e3c96ea2acee2bc34119f92b37750 "#]]); cmd.cast_fuse().args(["hash-message", "0x68656c6c6f"]).assert_success().stdout_eq(str![[r#" 0x83a0870b6c63a71efdd3b2749ef700653d97454152c4b53fa9b102dc430c7c32 "#]]); }); casttest!(parse_units, |_prj, cmd| { cmd.args(["parse-units", "1.5", "6"]).assert_success().stdout_eq(str![[r#" 1500000 "#]]); cmd.cast_fuse().args(["pun", "1.23", "18"]).assert_success().stdout_eq(str![[r#" 1230000000000000000 "#]]); cmd.cast_fuse().args(["--parse-units", "1.23", "3"]).assert_success().stdout_eq(str![[r#" 1230 "#]]); }); casttest!(string_decode, |_prj, cmd| { cmd.args(["string-decode", "0x88c379a0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000054753303235000000000000000000000000000000000000000000000000000000"]).assert_success().stdout_eq(str![[r#" "GS025" "#]]); }); casttest!(format_units, |_prj, cmd| { cmd.args(["format-units", "1000000", "6"]).assert_success().stdout_eq(str![[r#" 1 "#]]); cmd.cast_fuse().args(["--format-units", "2500000", "6"]).assert_success().stdout_eq(str![[ r#" 2.500000 "# ]]); cmd.cast_fuse().args(["fun", "1230", "3"]).assert_success().stdout_eq(str![[r#" 1.230 "#]]); }); // tests that fetches a sample contract creation code // casttest!(flaky_fetch_creation_code_from_etherscan, |_prj, cmd| { let eth_rpc_url = next_http_rpc_endpoint(); cmd.args([ "creation-code", "--etherscan-api-key", &next_etherscan_api_key(), "0x0923cad07f06b2d0e5e49e63b8b35738d4156b95", "--rpc-url", eth_rpc_url.as_str(), ]) .assert_success() .stdout_eq(str![[r#" 0x60566050600b82828239805160001a6073146043577f4e487b7100000000000000000000000000000000000000000000000000000000600052600060045260246000fd5b30600052607381538281f3fe73000000000000000000000000000000000000000030146080604052600080fdfea264697066735822122074c61e8e4eefd410ca92eec26e8112ec6e831d0a4bf35718fdd78b45d68220d064736f6c63430008070033 "#]]); }); // tests that fetches a sample contract creation args bytes // casttest!(flaky_fetch_creation_code_only_args_from_etherscan, |_prj, cmd| { let eth_rpc_url = next_http_rpc_endpoint(); cmd.args([ "creation-code", "--etherscan-api-key", &next_etherscan_api_key(), "0x6982508145454ce325ddbe47a25d4ec3d2311933", "--rpc-url", eth_rpc_url.as_str(), "--only-args", ]) .assert_success() .stdout_eq(str![[r#" 0x00000000000000000000000000000000000014bddab3e51a57cff87a50000000 "#]]); }); // tests that displays a sample contract creation args // casttest!(flaky_fetch_constructor_args_from_etherscan, |_prj, cmd| { let eth_rpc_url = next_http_rpc_endpoint(); cmd.args([ "constructor-args", "--etherscan-api-key", &next_etherscan_api_key(), "0x6982508145454ce325ddbe47a25d4ec3d2311933", "--rpc-url", eth_rpc_url.as_str(), ]) .assert_success() .stdout_eq(str![[r#" 0x00000000000000000000000000000000000014bddab3e51a57cff87a50000000 → Uint(420690000000000000000000000000000, 256) "#]]); }); // casttest!(flaky_test_non_mainnet_traces, |prj, cmd| { prj.clear(); cmd.args([ "run", "0xa003e419e2d7502269eb5eda56947b580120e00abfd5b5460d08f8af44a0c24f", "--rpc-url", next_rpc_endpoint(NamedChain::Optimism).as_str(), "--etherscan-api-key", next_etherscan_api_key().as_str(), ]) .assert_success() .stdout_eq(str![[r#" Executing previous transactions from the block. Traces: [33841] FiatTokenProxy::fallback(0x111111125421cA6dc452d289314280a0f8842A65, 164054805 [1.64e8]) ├─ [26673] FiatTokenV2_2::approve(0x111111125421cA6dc452d289314280a0f8842A65, 164054805 [1.64e8]) [delegatecall] │ ├─ emit Approval(owner: 0x9a95Af47C51562acfb2107F44d7967DF253197df, spender: 0x111111125421cA6dc452d289314280a0f8842A65, value: 164054805 [1.64e8]) │ └─ ← [Return] true └─ ← [Return] true ... "#]]); }); // tests that displays a sample contract artifact // casttest!(flaky_fetch_artifact_from_etherscan, |_prj, cmd| { let eth_rpc_url = next_http_rpc_endpoint(); cmd.args([ "artifact", "--etherscan-api-key", &next_etherscan_api_key(), "0x0923cad07f06b2d0e5e49e63b8b35738d4156b95", "--rpc-url", eth_rpc_url.as_str(), ]) .assert_success() .stdout_eq(str![[r#"{ "abi": [], "bytecode": { "object": "0x60566050600b82828239805160001a6073146043577f4e487b7100000000000000000000000000000000000000000000000000000000600052600060045260246000fd5b30600052607381538281f3fe73000000000000000000000000000000000000000030146080604052600080fdfea264697066735822122074c61e8e4eefd410ca92eec26e8112ec6e831d0a4bf35718fdd78b45d68220d064736f6c63430008070033" } } "#]]); }); // tests cast can decode traces when using project artifacts forgetest_async!(decode_traces_with_project_artifacts, |prj, cmd| { let (api, handle) = anvil::spawn(NodeConfig::test().with_disable_default_create2_deployer(true)).await; foundry_test_utils::util::initialize(prj.root()); prj.add_source( "LocalProjectContract", r#" contract LocalProjectContract { event LocalProjectContractCreated(address owner); constructor() { emit LocalProjectContractCreated(msg.sender); } } "#, ); prj.add_script( "LocalProjectScript", r#" import "forge-std/Script.sol"; import {LocalProjectContract} from "../src/LocalProjectContract.sol"; contract LocalProjectScript is Script { function run() public { vm.startBroadcast(); new LocalProjectContract(); vm.stopBroadcast(); } } "#, ); cmd.args([ "script", "--private-key", "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80", "--rpc-url", &handle.http_endpoint(), "--broadcast", "LocalProjectScript", ]); cmd.assert_success(); let tx_hash = api .transaction_by_block_number_and_index(BlockNumberOrTag::Latest, Index::from(0)) .await .unwrap() .unwrap() .tx_hash(); // Assert cast with local artifacts from outside the project. cmd.cast_fuse() .args(["run", "--la", format!("{tx_hash}").as_str(), "--rpc-url", &handle.http_endpoint()]) .assert_success() .stdout_eq(str![[r#" Executing previous transactions from the block. Compiling project to generate artifacts Nothing to compile "#]]); // Run cast from project dir. cmd.cast_fuse().set_current_dir(prj.root()); // Assert cast without local artifacts cannot decode traces. cmd.cast_fuse() .args(["run", format!("{tx_hash}").as_str(), "--rpc-url", &handle.http_endpoint()]) .assert_success() .stdout_eq(str![[r#" Executing previous transactions from the block. Traces: [..] → new @0x5FbDB2315678afecb367f032d93F642f64180aa3 ├─ emit topic 0: 0xa7263295d3a687d750d1fd377b5df47de69d7db8decc745aaa4bbee44dc1688d │ data: 0x000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb92266 └─ ← [Return] 62 bytes of code Transaction successfully executed. [GAS] "#]]); // Assert cast with local artifacts can decode traces. cmd.cast_fuse() .args(["run", "--la", format!("{tx_hash}").as_str(), "--rpc-url", &handle.http_endpoint()]) .assert_success() .stdout_eq(str![[r#" Executing previous transactions from the block. Compiling project to generate artifacts No files changed, compilation skipped Traces: [..] → new LocalProjectContract@0x5FbDB2315678afecb367f032d93F642f64180aa3 ├─ emit LocalProjectContractCreated(owner: 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266) └─ ← [Return] 62 bytes of code Transaction successfully executed. [GAS] "#]]); }); // tests cast can decode traces when running with verbosity level > 4 forgetest_async!(show_state_changes_in_traces, |prj, cmd| { let (api, handle) = anvil::spawn(NodeConfig::test()).await; foundry_test_utils::util::initialize(prj.root()); prj.initialize_default_contracts(); // Deploy counter contract. cmd.args([ "script", "--private-key", "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80", "--rpc-url", &handle.http_endpoint(), "--broadcast", "CounterScript", ]) .assert_success(); // Send tx to change counter storage value. cmd.cast_fuse() .args([ "send", "0x5FbDB2315678afecb367f032d93F642f64180aa3", "setNumber(uint256)", "111", "--private-key", "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80", "--rpc-url", &handle.http_endpoint(), ]) .assert_success(); let tx_hash = api .transaction_by_block_number_and_index(BlockNumberOrTag::Latest, Index::from(0)) .await .unwrap() .unwrap() .tx_hash(); // Assert cast with verbosity displays storage changes. cmd.cast_fuse() .args([ "run", format!("{tx_hash}").as_str(), "-vvvvv", "--rpc-url", &handle.http_endpoint(), ]) .assert_success() .stdout_eq(str![[r#" Executing previous transactions from the block. Traces: [..] 0x5FbDB2315678afecb367f032d93F642f64180aa3::setNumber(111) ├─ storage changes: │ @ 0: 0 → 111 └─ ← [Stop] Transaction successfully executed. [GAS] "#]]); }); // tests cast can decode external libraries traces with project cached selectors forgetest_async!(flaky_decode_external_libraries_with_cached_selectors, |prj, cmd| { let (api, handle) = anvil::spawn(NodeConfig::test()).await; foundry_test_utils::util::initialize(prj.root()); prj.add_source( "ExternalLib", r#" import "./CounterInExternalLib.sol"; library ExternalLib { function updateCounterInExternalLib(CounterInExternalLib.Info storage counterInfo, uint256 counter) public { counterInfo.counter = counter + 1; } } "#, ); prj.add_source( "CounterInExternalLib", r#" import "./ExternalLib.sol"; contract CounterInExternalLib { struct Info { uint256 counter; } Info info; constructor() { ExternalLib.updateCounterInExternalLib(info, 100); } } "#, ); prj.add_script( "CounterInExternalLibScript", r#" import "forge-std/Script.sol"; import {CounterInExternalLib} from "../src/CounterInExternalLib.sol"; contract CounterInExternalLibScript is Script { function run() public { vm.startBroadcast(); new CounterInExternalLib(); vm.stopBroadcast(); } } "#, ); cmd.args([ "script", "--private-key", "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80", "--rpc-url", &handle.http_endpoint(), "--broadcast", "CounterInExternalLibScript", ]) .assert_success(); let tx_hash = api .transaction_by_block_number_and_index(BlockNumberOrTag::Latest, Index::from(0)) .await .unwrap() .unwrap() .tx_hash(); // Build and cache project selectors. cmd.forge_fuse().args(["build"]).assert_success(); cmd.forge_fuse().args(["selectors", "cache"]).assert_success(); // Assert cast with local artifacts can decode external lib signature. cmd.cast_fuse() .args(["run", format!("{tx_hash}").as_str(), "--rpc-url", &handle.http_endpoint()]) .assert_success() .stdout_eq(str![[r#" ... Traces: [..] → new @0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512 ├─ [..] [..]::updateCounterInExternalLib(0, 100) [delegatecall] │ └─ ← [Stop] └─ ← [Return] [..] bytes of code Transaction successfully executed. [GAS] "#]]); }); // https://github.com/foundry-rs/foundry/issues/9476 forgetest_async!(cast_call_custom_chain_id, |_prj, cmd| { let chain_id = 55555u64; let (_api, handle) = anvil::spawn(NodeConfig::test().with_chain_id(Some(chain_id))).await; let http_endpoint = handle.http_endpoint(); cmd.cast_fuse() .args([ "call", "5FbDB2315678afecb367f032d93F642f64180aa3", "--rpc-url", &http_endpoint, "--chain", &chain_id.to_string(), ]) .assert_success(); }); // https://github.com/foundry-rs/foundry/issues/10848 forgetest_async!(cast_call_disable_labels, |prj, cmd| { let (_, handle) = anvil::spawn(NodeConfig::test()).await; foundry_test_utils::util::initialize(prj.root()); prj.initialize_default_contracts(); prj.add_source( "Counter", r#" contract Counter { uint256 public number; function getBalance(address target) public returns (uint256) { return target.balance; } } "#, ); // Deploy counter contract. cmd.args([ "script", "--private-key", "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80", "--rpc-url", &handle.http_endpoint(), "--broadcast", "CounterScript", ]) .assert_success(); // Override state, `number()` should return overridden value. cmd.cast_fuse() .args([ "call", "0x5FbDB2315678afecb367f032d93F642f64180aa3", "--rpc-url", &handle.http_endpoint(), "--override-state", "0x5FbDB2315678afecb367f032d93F642f64180aa3:0x0:0x1234", "number()(uint256)", ]) .assert_success() .stdout_eq(str![[r#" 4660 "#]]); // Override state, `number()` should return overridden value. cmd.cast_fuse() .args([ "call", "0x5FbDB2315678afecb367f032d93F642f64180aa3", "--labels", "0x5FbDB2315678afecb367f032d93F642f64180aa3:WETH", "--rpc-url", &handle.http_endpoint(), "--override-state", "0x5FbDB2315678afecb367f032d93F642f64180aa3:0x0:0x1234", "number()(uint256)", "--trace", ]) .assert_success() .stdout_eq(str![[r#" Traces: [2402] WETH::number() └─ ← [Return] 0x0000000000000000000000000000000000000000000000000000000000001234 Transaction successfully executed. [GAS] "#]]); // Override state, `number()` with `disable_labels`. cmd.cast_fuse() .args([ "call", "0x5FbDB2315678afecb367f032d93F642f64180aa3", "--labels", "0x5FbDB2315678afecb367f032d93F642f64180aa3:WETH", "--rpc-url", &handle.http_endpoint(), "--override-state", "0x5FbDB2315678afecb367f032d93F642f64180aa3:0x0:0x1234", "number()(uint256)", "--trace", "--disable-labels", ]) .assert_success() .stdout_eq(str![[r#" Traces: [2402] 0x5FbDB2315678afecb367f032d93F642f64180aa3::number() └─ ← [Return] 0x0000000000000000000000000000000000000000000000000000000000001234 Transaction successfully executed. [GAS] "#]]); }); // https://github.com/foundry-rs/foundry/issues/10189 forgetest_async!(cast_call_custom_override, |prj, cmd| { let (_, handle) = anvil::spawn(NodeConfig::test()).await; foundry_test_utils::util::initialize(prj.root()); prj.initialize_default_contracts(); prj.add_source( "Counter", r#" contract Counter { uint256 public number; function getBalance(address target) public returns (uint256) { return target.balance; } } "#, ); // Deploy counter contract. cmd.args([ "script", "--private-key", "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80", "--rpc-url", &handle.http_endpoint(), "--broadcast", "CounterScript", ]) .assert_success(); // Override state, `number()` should return overridden value. cmd.cast_fuse() .args([ "call", "0x5FbDB2315678afecb367f032d93F642f64180aa3", "--rpc-url", &handle.http_endpoint(), "--override-state", "0x5FbDB2315678afecb367f032d93F642f64180aa3:0x0:0x1234", "number()(uint256)", ]) .assert_success() .stdout_eq(str![[r#" 4660 "#]]); // Override state, `number()` should return overridden value. cmd.cast_fuse() .args([ "call", "0x5FbDB2315678afecb367f032d93F642f64180aa3", "--rpc-url", &handle.http_endpoint(), "--override-state", "0x5FbDB2315678afecb367f032d93F642f64180aa3:0x0:0x1234", "number()(uint256)", "--trace", ]) .assert_success() .stdout_eq(str![[r#" Traces: [2402] 0x5FbDB2315678afecb367f032d93F642f64180aa3::number() └─ ← [Return] 0x0000000000000000000000000000000000000000000000000000000000001234 Transaction successfully executed. [GAS] "#]]); // Override balance, `getBalance()` should return overridden value. cmd.cast_fuse() .args([ "call", "0x5FbDB2315678afecb367f032d93F642f64180aa3", "--rpc-url", &handle.http_endpoint(), "--override-balance", "0x5FbDB2315678afecb367f032d93F642f64180aa3:0x1111", "getBalance(address)(uint256)", "0x5FbDB2315678afecb367f032d93F642f64180aa3", ]) .assert_success() .stdout_eq(str![[r#" 4369 "#]]); // Override balance, `getBalance()` should return overridden value. cmd.cast_fuse() .args([ "call", "0x5FbDB2315678afecb367f032d93F642f64180aa3", "--rpc-url", &handle.http_endpoint(), "--override-balance", "0x5FbDB2315678afecb367f032d93F642f64180aa3:0x1111", "getBalance(address)(uint256)", "0x5FbDB2315678afecb367f032d93F642f64180aa3", "--trace", ]) .assert_success() .stdout_eq(str![[r#" Traces: [747] 0x5FbDB2315678afecb367f032d93F642f64180aa3::getBalance(0x5FbDB2315678afecb367f032d93F642f64180aa3) └─ ← [Return] 0x0000000000000000000000000000000000000000000000000000000000001111 Transaction successfully executed. [GAS] "#]]); // Override code with // contract Counter { // uint256 public number1; // } // Calling `number()` should fail. cmd.cast_fuse() .args([ "call", "0x5FbDB2315678afecb367f032d93F642f64180aa3", "--rpc-url", &handle.http_endpoint(), "--override-code", "0x5FbDB2315678afecb367f032d93F642f64180aa3:0x6080604052348015600e575f5ffd5b50600436106026575f3560e01c8063c223a39e14602a575b5f5ffd5b60306044565b604051603b9190605f565b60405180910390f35b5f5481565b5f819050919050565b6059816049565b82525050565b5f60208201905060705f8301846052565b9291505056fea26469706673582212202a0acfb9083efed3e0e9f27177b090731d4392cf196d58e27e05088f59008d0964736f6c634300081d0033", "number()(uint256)", ]) .assert_failure() .stderr_eq(str![[r#" Error: server returned an error response: error code 3: execution reverted, data: "0x" "#]]); // Override code with // contract Counter { // uint256 public number1; // } // Calling `number()` should revert. cmd.cast_fuse() .args([ "call", "0x5FbDB2315678afecb367f032d93F642f64180aa3", "--rpc-url", &handle.http_endpoint(), "--override-code", "0x5FbDB2315678afecb367f032d93F642f64180aa3:0x6080604052348015600e575f5ffd5b50600436106026575f3560e01c8063c223a39e14602a575b5f5ffd5b60306044565b604051603b9190605f565b60405180910390f35b5f5481565b5f819050919050565b6059816049565b82525050565b5f60208201905060705f8301846052565b9291505056fea26469706673582212202a0acfb9083efed3e0e9f27177b090731d4392cf196d58e27e05088f59008d0964736f6c634300081d0033", "number()(uint256)", "--trace" ]) .assert_success() .stderr_eq(str![[r#" Error: Transaction failed. "#]]); // Calling `number1()` with overridden state should return new value. cmd.cast_fuse() .args([ "call", "0x5FbDB2315678afecb367f032d93F642f64180aa3", "--rpc-url", &handle.http_endpoint(), "--override-code", "0x5FbDB2315678afecb367f032d93F642f64180aa3:0x6080604052348015600e575f5ffd5b50600436106026575f3560e01c8063c223a39e14602a575b5f5ffd5b60306044565b604051603b9190605f565b60405180910390f35b5f5481565b5f819050919050565b6059816049565b82525050565b5f60208201905060705f8301846052565b9291505056fea26469706673582212202a0acfb9083efed3e0e9f27177b090731d4392cf196d58e27e05088f59008d0964736f6c634300081d0033", "--override-state", "0x5FbDB2315678afecb367f032d93F642f64180aa3:0x0:0x2222", "number1()(uint256)", ]) .assert_success() .stdout_eq(str![[r#" 8738 "#]]); // Calling `number1()` with overridden state should return new value. cmd.cast_fuse() .args([ "call", "0x5FbDB2315678afecb367f032d93F642f64180aa3", "--rpc-url", &handle.http_endpoint(), "--override-code", "0x5FbDB2315678afecb367f032d93F642f64180aa3:0x6080604052348015600e575f5ffd5b50600436106026575f3560e01c8063c223a39e14602a575b5f5ffd5b60306044565b604051603b9190605f565b60405180910390f35b5f5481565b5f819050919050565b6059816049565b82525050565b5f60208201905060705f8301846052565b9291505056fea26469706673582212202a0acfb9083efed3e0e9f27177b090731d4392cf196d58e27e05088f59008d0964736f6c634300081d0033", "--override-state", "0x5FbDB2315678afecb367f032d93F642f64180aa3:0x0:0x2222", "number1()(uint256)", "--trace" ]) .assert_success() .stdout_eq(str![[r#" Traces: [2402] 0x5FbDB2315678afecb367f032d93F642f64180aa3::number1() └─ ← [Return] 0x0000000000000000000000000000000000000000000000000000000000002222 Transaction successfully executed. [GAS] "#]]); // Calling `number1()` with overridden state should return new value. cmd.cast_fuse() .args([ "call", "0x5FbDB2315678afecb367f032d93F642f64180aa3", "--rpc-url", &handle.http_endpoint(), "--override-code", "0x5FbDB2315678afecb367f032d93F642f64180aa3:0x6080604052348015600e575f5ffd5b50600436106026575f3560e01c8063c223a39e14602a575b5f5ffd5b60306044565b604051603b9190605f565b60405180910390f35b5f5481565b5f819050919050565b6059816049565b82525050565b5f60208201905060705f8301846052565b9291505056fea26469706673582212202a0acfb9083efed3e0e9f27177b090731d4392cf196d58e27e05088f59008d0964736f6c634300081d0033", "--override-state-diff", "0x5FbDB2315678afecb367f032d93F642f64180aa3:0x0:0x2222", "number1()(uint256)", ]) .assert_success() .stdout_eq(str![[r#" 8738 "#]]); // Calling `number1()` with overridden state should return new value. cmd.cast_fuse() .args([ "call", "0x5FbDB2315678afecb367f032d93F642f64180aa3", "--rpc-url", &handle.http_endpoint(), "--override-code", "0x5FbDB2315678afecb367f032d93F642f64180aa3:0x6080604052348015600e575f5ffd5b50600436106026575f3560e01c8063c223a39e14602a575b5f5ffd5b60306044565b604051603b9190605f565b60405180910390f35b5f5481565b5f819050919050565b6059816049565b82525050565b5f60208201905060705f8301846052565b9291505056fea26469706673582212202a0acfb9083efed3e0e9f27177b090731d4392cf196d58e27e05088f59008d0964736f6c634300081d0033", "--override-state-diff", "0x5FbDB2315678afecb367f032d93F642f64180aa3:0x0:0x2222", "number1()(uint256)", "--trace", ]) .assert_success() .stdout_eq(str![[r#" Traces: [2402] 0x5FbDB2315678afecb367f032d93F642f64180aa3::number1() └─ ← [Return] 0x0000000000000000000000000000000000000000000000000000000000002222 Transaction successfully executed. [GAS] "#]]); }); // https://github.com/foundry-rs/foundry/issues/9541 forgetest_async!(flaky_cast_run_impersonated_tx, |_prj, cmd| { let (_api, handle) = anvil::spawn( NodeConfig::test() .with_auto_impersonate(true) .with_eth_rpc_url(Some("https://sepolia.base.org")), ) .await; let http_endpoint = handle.http_endpoint(); let provider = ProviderBuilder::new().connect_http(http_endpoint.parse().unwrap()); // send impersonated tx let tx = TransactionRequest::default() .with_from(address!("0x041563c07028Fc89106788185763Fc73028e8511")) .with_to(address!("0xF38aA5909D89F5d98fCeA857e708F6a6033f6CF8")) .with_input( Bytes::from_str( "0x60fe47b1000000000000000000000000000000000000000000000000000000000000000c", ) .unwrap(), ); let receipt = provider.send_transaction(tx).await.unwrap().get_receipt().await.unwrap(); assert!(receipt.status()); // run impersonated tx cmd.cast_fuse() .args(["run", &receipt.transaction_hash.to_string(), "--rpc-url", &http_endpoint]) .assert_success(); }); // casttest!(flaky_fetch_src_blockscout, |_prj, cmd| { let url = "https://eth.blockscout.com/api"; let weth = address!("0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"); cmd.args([ "source", &weth.to_string(), "--chain-id", "1", "--explorer-api-url", url, "--flatten", ]) .assert_success() .stdout_eq(str![[r#" ... contract WETH9 { string public name = "Wrapped Ether"; string public symbol = "WETH"; uint8 public decimals = 18; ..."#]]); }); casttest!(flaky_fetch_src_default, |_prj, cmd| { let weth = address!("0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"); let etherscan_api_key = next_etherscan_api_key(); cmd.args(["source", &weth.to_string(), "--flatten", "--etherscan-api-key", ðerscan_api_key]) .assert_success() .stdout_eq(str![[r#" ... contract WETH9 { string public name = "Wrapped Ether"; string public symbol = "WETH"; uint8 public decimals = 18; ..."#]]); }); // // casttest!(flaky_osaka_can_run_p256_precompile, |_prj, cmd| { cmd.args([ "run", "0x17b2de59ebd7dfd2452a3638a16737b6b65ae816c1c5571631dc0d80b63c41de", "--rpc-url", next_rpc_endpoint(NamedChain::Base).as_str(), "--quick", "--evm-version", "osaka", ]) .assert_success() .stdout_eq(str![[r#" Traces: [91537] 0xc2FF493F28e894742b968A7DB5D3F21F0aD80C6c::execute(0x0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000a12384c5e52fd646e7bc7f6b3b33a605651f566e000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000170000000000000000000000000000000000000000000000000000000000000000000000000000000000000000833589fcd6edb6e08f4c7c32d4f71b54bda02913000000000000000000000000000000000000000000000000000000000000060f000000000000000000000000000000000000000000000000000000000000060f0000000000000000000000000000000000000000000000000000000000036cd000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000320000000000000000000000000000000000000000000000000000000000000060f000000000000000000000000000000000000000000000000000000000000060f000000000000000000000000327a25ad5cfe5c4d4339c1a4267d4a83e8c93312000000000000000000000000000000000000000000000000000000000000034000000000000000000000000000000000000000000000000000000000000005a00000000000000000000000000b55b053230e4effb6609de652fca73fd1c2980400000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000221000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000006cdd519280ec730727f07aa36550bde31a1d5f3097818f3425c2f083ed33a91f080fa2afac0071f6e1af9a0e9c09b851bf01e68bc8a1c1f89f686c48205762f92500000000000000000000000000000000000000000000000000000000000000244242424242424242424242424242424242424242424242424242424242424242010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000827b226368616c6c656e6765223a224b51704d51446e7841757a726f68522d483878472d5a536b625249702d76515f5f5f4a714259357a655038222c2263726f73734f726967696e223a66616c73652c226f726967696e223a2268747470732f2f6974686163612e78797a222c2274797065223a22776562617574686e2e676574227d0000000000000000000000000000000000000000000000000000000000001bde17b8de18819c9eb86cefc3920ddb5d3d4254de276e3d6e18dd2b399f732b00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000) ├─ [2241] 0xA12384c5E52fD646E7BC7F6B3b33A605651F566E::fallback(00) [staticcall] │ └─ ← [Return] 0x0000000000000000000000000b55b053230e4effb6609de652fca73fd1c29804 ├─ [9750] 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913::balanceOf(0xA12384c5E52fD646E7BC7F6B3b33A605651F566E) [staticcall] │ ├─ [2553] 0x2Ce6311ddAE708829bc0784C967b7d77D19FD779::balanceOf(0xA12384c5E52fD646E7BC7F6B3b33A605651F566E) [delegatecall] │ │ └─ ← [Return] 0x000000000000000000000000000000000000000000000000000000000000f3b9 │ └─ ← [Return] 0x000000000000000000000000000000000000000000000000000000000000f3b9 ├─ [65442] 0xc2FF493F28e894742b968A7DB5D3F21F0aD80C6c::fulfillBasicOrder_efficient_6GL6yc() │ ├─ [25070] 0xA12384c5E52fD646E7BC7F6B3b33A605651F566E::unwrapAndValidateSignature(0x290a4c4039f102eceba2147e1fcc46f994a46d1229faf43ffff26a058e7378ff, 0x000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000006cdd519280ec730727f07aa36550bde31a1d5f3097818f3425c2f083ed33a91f080fa2afac0071f6e1af9a0e9c09b851bf01e68bc8a1c1f89f686c48205762f92500000000000000000000000000000000000000000000000000000000000000244242424242424242424242424242424242424242424242424242424242424242010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000827b226368616c6c656e6765223a224b51704d51446e7841757a726f68522d483878472d5a536b625249702d76515f5f5f4a714259357a655038222c2263726f73734f726967696e223a66616c73652c226f726967696e223a2268747470732f2f6974686163612e78797a222c2274797065223a22776562617574686e2e676574227d0000000000000000000000000000000000000000000000000000000000001bde17b8de18819c9eb86cefc3920ddb5d3d4254de276e3d6e18dd2b399f732b00) [staticcall] │ │ ├─ [22067] 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 │ │ │ ├─ [6900] P256VERIFY::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::multicallN2M_001Taw5z() │ │ ├─ [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| { let (_, handle) = anvil::spawn(NodeConfig::test()).await; foundry_test_utils::util::initialize(prj.root()); prj.add_source( "SimpleStorage", r#" contract SimpleStorage { uint256 private storedValue; error AddressInsufficientBalance(address account, uint256 newValue); function setValue(uint256 _newValue) public { if (_newValue > 100) { revert AddressInsufficientBalance(msg.sender, _newValue); } storedValue = _newValue; } } "#, ); prj.add_script( "SimpleStorageScript", r#" import "forge-std/Script.sol"; import {SimpleStorage} from "../src/SimpleStorage.sol"; contract SimpleStorageScript is Script { function run() public { vm.startBroadcast(); new SimpleStorage(); vm.stopBroadcast(); } } "#, ); cmd.args([ "script", "--private-key", "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80", "--rpc-url", &handle.http_endpoint(), "--broadcast", "SimpleStorageScript", ]) .assert_success(); // Cache project selectors. cmd.forge_fuse().set_current_dir(prj.root()); cmd.forge_fuse().args(["selectors", "cache"]).assert_success(); // Assert cast send can decode custom error on estimate gas execution failure. cmd.cast_fuse() .args([ "send", "0x5FbDB2315678afecb367f032d93F642f64180aa3", "setValue(uint256)", "1000", "--private-key", "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80", "--rpc-url", &handle.http_endpoint(), ]) .assert_failure().stderr_eq(str![[r#" Error: Failed to estimate gas: server returned an error response: error code 3: execution reverted: custom error 0x6786ad34: 000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb9226600000000000000000000000000000000000000000000000000000000000003e8, data: "0x6786ad34000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb9226600000000000000000000000000000000000000000000000000000000000003e8"[..] "#]]); }); // 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#" [[..]] "#]]); }); // casttest!(cast_call_on_contract_with_no_code_prints_warning, |_prj, cmd| { let eth_rpc_url = next_http_rpc_endpoint(); cmd.args([ "call", "0x0000000000000000000000000000000000000000", "--rpc-url", eth_rpc_url.as_str(), ]) .assert_success() .stderr_eq(str![[r#" Warning: Contract code is empty "#]]) .stdout_eq(str![[r#" 0x "#]]); }); // casttest!(tx_raw_opstack_deposit, |_prj, cmd| { cmd.args([ "tx", "0xf403cba612d1c01c027455c0d97427ccd5f7f99aac30017e065f81d1e30244ea", "--raw", "-n", "optimism", "--rpc-url", "https://sepolia.base.org", ]).assert_success() .stdout_eq(str![[r#" 0x7ef90207a0cbde10ec697aff886f95d2514bab434e455620627b9bb8ba33baaaa4d537d62794d45955f4de64f1840e5686e64278da901e263031944200000000000000000000000000000000000007872386f26fc10000872386f26fc1000083096c4980b901a4d764ad0b0001000000000000000000000000000000000000000000000000000000065132000000000000000000000000fd0bf71f60660e2f608ed56e1659c450eb1131200000000000000000000000004200000000000000000000000000000000000010000000000000000000000000000000000000000000000000002386f26fc1000000000000000000000000000000000000000000000000000000000000000493e000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000a41635f5fd000000000000000000000000ca11bde05977b3631167028862be2a173976ca110000000000000000000000005703b26fe5a7be820db1bf34c901a79da1a46ba4000000000000000000000000000000000000000000000000002386f26fc100000000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 "#]]); }); casttest!(tx_raw_tempo, |_prj, cmd| { cmd.args([ "tx", "0xa24c6bbeea629a80be79e970a9749d0cbc6ee31625a0b75f585c173ab15a18ec", "--raw", "-n", "tempo", "--rpc-url", "https://rpc.moderato.tempo.xyz", ]).assert_success() .stdout_eq(str![[r#" 0x76f8cf82a5bf1485059682f018830494e5f85ef85c9420c0000000000000000000007d9cc57068833ea780b84440c10f190000000000000000000000008a871f4189067637cfc4cc1500abd6244bf1df740000000000000000000000000000000000000000000000000000000005f5e100c08082057e80809420c000000000000000000000000000000000000080c0b841eb100c4cbd96903bf9e97968c0982670bb90fc191ee4544c7ff32d44e901dbea3f6fbdd58255051135c2fe1aa81583a270d96009cbe375f4605ef15971273a4f1b "#]]); }); // Test that cast send --create works correctly with constructor arguments // forgetest_async!(cast_send_create_with_constructor_args, |prj, cmd| { let (_api, handle) = anvil::spawn(NodeConfig::test()).await; let endpoint = handle.http_endpoint(); // Deploy a simple contract with constructor arguments // Contract source that takes constructor args prj.add_source( "ConstructorContract", r#" contract ConstructorContract { uint256 public value; string public name; constructor(uint256 _value, string memory _name) { value = _value; name = _name; } function getValue() public view returns (uint256) { return value; } } "#, ); // Compile to get bytecode cmd.forge_fuse().args(["build"]).assert_success(); // Get the compiled bytecode let bytecode_path = prj.root().join("out/ConstructorContract.sol/ConstructorContract.json"); let contract_json = std::fs::read_to_string(bytecode_path).unwrap(); let contract_data: serde_json::Value = serde_json::from_str(&contract_json).unwrap(); let bytecode = contract_data["bytecode"]["object"].as_str().unwrap(); // Use cast send --create with constructor arguments let output = cmd .cast_fuse() .args([ "send", "--private-key", "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80", "--rpc-url", &endpoint, "--create", bytecode, "constructor(uint256,string)", "42", "TestContract", ]) .assert_success() .get_output() .stdout_lossy(); // Extract the deployed contract address from output let lines: Vec<&str> = output.lines().collect(); let mut address = None; for line in lines { if line.contains("contractAddress") { let parts: Vec<&str> = line.split_whitespace().collect(); address = Some(parts[1]); break; } } let address = address.expect("Contract address not found in output"); // Verify the contract was deployed correctly by calling getValue() let value_output = cmd .cast_fuse() .args(["call", address, "getValue()", "--rpc-url", &endpoint]) .assert_success() .get_output() .stdout_lossy(); // The value should be 42 (0x2a in hex) assert!( value_output.contains("0x000000000000000000000000000000000000000000000000000000000000002a") ); }); // Test that cast estimate --create works correctly with constructor arguments // casttest!(cast_estimate_create_with_constructor_args, |prj, cmd| { let eth_rpc_url = next_http_rpc_endpoint(); // Add a simple contract with constructor arguments prj.add_source( "EstimateContract", r#" contract EstimateContract { uint256 public value; string public name; constructor(uint256 _value, string memory _name) { value = _value; name = _name; } } "#, ); // Compile to get bytecode cmd.forge_fuse().args(["build"]).assert_success(); // Get the compiled bytecode let bytecode_path = prj.root().join("out/EstimateContract.sol/EstimateContract.json"); let contract_json = std::fs::read_to_string(bytecode_path).unwrap(); let contract_data: serde_json::Value = serde_json::from_str(&contract_json).unwrap(); let bytecode = contract_data["bytecode"]["object"].as_str().unwrap(); let output = cmd .cast_fuse() .args([ "estimate", "--rpc-url", eth_rpc_url.as_str(), "--create", bytecode, "constructor(uint256,string)", "100", "TestContract", ]) .assert_success() .get_output() .stdout_lossy(); // Parse the gas estimate let gas_estimate = output.trim().parse::().expect("Failed to parse gas estimate"); // Gas estimate should be positive and reasonable for contract deployment assert!(gas_estimate > 50000, "Gas estimate too low for contract deployment"); assert!(gas_estimate < 5000000, "Gas estimate unreasonably high"); }); // Test edge case: empty constructor arguments // forgetest_async!(cast_send_create_empty_constructor, |prj, cmd| { let (_api, handle) = anvil::spawn(NodeConfig::test()).await; let endpoint = handle.http_endpoint(); // Simple contract with no constructor arguments prj.add_source( "SimpleContract", r#" contract SimpleContract { uint256 public constant VALUE = 42; } "#, ); // Compile cmd.forge_fuse().args(["build"]).assert_success(); // Get bytecode let bytecode_path = prj.root().join("out/SimpleContract.sol/SimpleContract.json"); let contract_json = std::fs::read_to_string(bytecode_path).unwrap(); let contract_data: serde_json::Value = serde_json::from_str(&contract_json).unwrap(); let bytecode = contract_data["bytecode"]["object"].as_str().unwrap(); // Deploy with empty constructor let output = cmd .cast_fuse() .args([ "send", "--private-key", "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80", "--rpc-url", &endpoint, "--create", bytecode, "constructor()", ]) .assert_success() .get_output() .stdout_lossy(); // Verify deployment succeeded assert!(output.contains("contractAddress")); }); // Test complex constructor arguments (multiple types) // forgetest_async!(cast_send_create_complex_constructor, |prj, cmd| { let (_api, handle) = anvil::spawn(NodeConfig::test()).await; let endpoint = handle.http_endpoint(); // Contract with complex constructor prj.add_source( "ComplexContract", r#" contract ComplexContract { address public owner; uint256[] public values; bool public active; constructor(address _owner, uint256[] memory _values, bool _active) { owner = _owner; values = _values; active = _active; } function getValuesLength() public view returns (uint256) { return values.length; } } "#, ); // Compile cmd.forge_fuse().args(["build"]).assert_success(); // Get bytecode let bytecode_path = prj.root().join("out/ComplexContract.sol/ComplexContract.json"); let contract_json = std::fs::read_to_string(bytecode_path).unwrap(); let contract_data: serde_json::Value = serde_json::from_str(&contract_json).unwrap(); let bytecode = contract_data["bytecode"]["object"].as_str().unwrap(); // Deploy with complex arguments let output = cmd .cast_fuse() .args([ "send", "--private-key", "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80", "--rpc-url", &endpoint, "--create", bytecode, "constructor(address,uint256[],bool)", "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", "[1,2,3,4,5]", "true", ]) .assert_success() .get_output() .stdout_lossy(); // Extract deployed address let lines: Vec<&str> = output.lines().collect(); let mut address = None; for line in lines { if line.contains("contractAddress") { let parts: Vec<&str> = line.split_whitespace().collect(); if parts.len() >= 2 { address = Some(parts[1]); break; } } } let address = address.expect("Contract address not found in output"); // Verify the array length was set correctly let length_output = cmd .cast_fuse() .args(["call", address, "getValuesLength()", "--rpc-url", &endpoint]) .assert_success() .get_output() .stdout_lossy(); // Should return 5 (0x5 in hex) assert!( length_output .contains("0x0000000000000000000000000000000000000000000000000000000000000005") ); }); casttest!(recover_authority, |_prj, cmd| { let auth = r#"{ "chainId": "0x1", "address": "0xb684710e6d5914ad6e64493de2a3c424cc43e970", "nonce": "0x3dc1", "yParity": "0x1", "r": "0x2f15ba55009fcd3682cd0f9c9645dd94e616f9a969ba3f1a5a2d871f9fe0f2b4", "s": "0x53c332a83312d0b17dd4c16eeb15b1ff5223398b14e0a55c70762e8f3972b7a5" }"#; cmd.args(["recover-authority", auth]).assert_success().stdout_eq(str![[r#" 0x17816E9A858b161c3E37016D139cf618056CaCD4 "#]]); }); // // tests `cast code --disassemble` casttest!(can_disassemble_contract_code, |_prj, cmd| { let rpc = next_rpc_endpoint(NamedChain::Mainnet); cmd.args([ "code", "--disassemble", "--rpc-url", rpc.as_str(), "0x1F573D6Fb3F13d689FF844B4cE37794d79a7FF1C", ]) .assert_success() .stdout_eq(str![[r#" 00000000: PUSH1 0x60 00000002: PUSH1 0x40 00000004: MSTORE 00000005: CALLDATASIZE 00000006: ISZERO 00000007: PUSH2 0x010f 0000000a: JUMPI 0000000b: PUSH4 0xffffffff 00000010: PUSH29 0x0100000000000000000000000000000000000000000000000000000000 0000002e: PUSH1 0x00 ... "#]]); }); // tests that cast call properly applies state diff override // casttest!(cast_call_can_override_state_diff, |_prj, cmd| { let rpc = next_rpc_endpoint(NamedChain::Sepolia); cmd.args([ "call", "--rpc-url", rpc.as_str(), "--data", "0x", "0x1EA77b250eF79e917A5A637D5BB82D0980653F1B", "--override-state-diff", "0x1EA77b250eF79e917A5A637D5BB82D0980653F1B:1:1", ]) .assert_success() .stdout_eq(str![[r#" 0x1337 "#]]); cmd.args(["--trace"]).assert_success().stdout_eq(str![[r#" Traces: [7281] 0x1EA77b250eF79e917A5A637D5BB82D0980653F1B::fallback() ├─ [2275] 0xe537cb8a46Bd179c0C36aB7E3Fdecd759C8B80fc::fallback() [delegatecall] │ └─ ← [Return] 0x1337 └─ ← [Return] 0x1337 Transaction successfully executed. [GAS] "#]]); }); // Tests for negative number argument parsing // Ensures that negative numbers in function arguments are properly parsed // instead of being treated as command flags // Test that cast call accepts negative numbers as function arguments casttest!(cast_call_negative_numbers, |_prj, cmd| { let rpc = next_rpc_endpoint(NamedChain::Sepolia); // Test with negative int parameter - should not treat -456789 as a flag cmd.args([ "call", "0xAbCdEf1234567890aBcDeF1234567890aBcDeF12", "processValue(int128)", "-456789", "--rpc-url", rpc.as_str(), ]) .assert_success(); }); // Test negative numbers with multiple parameters casttest!(cast_call_multiple_negative_numbers, |_prj, cmd| { let rpc = next_rpc_endpoint(NamedChain::Sepolia); cmd.args([ "call", "--rpc-url", rpc.as_str(), "0xDeaDBeeFcAfEbAbEfAcEfEeDcBaDbEeFcAfEbAbE", "calculateDelta(int64,int32,uint16)", "-987654321", "-42", "65535", ]) .assert_success(); }); // Test negative numbers mixed with flags casttest!(cast_call_negative_with_flags, |_prj, cmd| { let rpc = next_rpc_endpoint(NamedChain::Sepolia); cmd.args([ "call", "--trace", // flag before "0x9876543210FeDcBa9876543210FeDcBa98765432", "updateBalance(int256)", "-777888", "--rpc-url", rpc.as_str(), // flag after ]) .assert_success(); }); // Test that actual invalid flags are still caught casttest!(cast_call_invalid_flag_still_caught, |_prj, cmd| { cmd.args([ "call", "--invalid-flag", // This should be caught as invalid "0x5555555555555555555555555555555555555555", ]) .assert_failure() .stderr_eq(str![[r#" error: unexpected argument '--invalid-flag' found tip: to pass '--invalid-flag' as a value, use '-- --invalid-flag' Usage: cast[..] call [OPTIONS] [TO] [SIG] [ARGS]... [COMMAND] For more information, try '--help'. "#]]); }); // Test cast estimate with negative numbers casttest!(cast_estimate_negative_numbers, |_prj, cmd| { let rpc = next_rpc_endpoint(NamedChain::Sepolia); cmd.args([ "estimate", "0xBbBbBbBbBbBbBbBbBbBbBbBbBbBbBbBbBbBbBbBb", "rebalance(int64)", "-8888", "--rpc-url", rpc.as_str(), ]) .assert_success(); }); // Test cast mktx with negative numbers casttest!(cast_mktx_negative_numbers, |_prj, cmd| { let rpc = next_rpc_endpoint(NamedChain::Sepolia); cmd.args([ "mktx", "0x1111111111111111111111111111111111111111", "settleDebt(int256)", "-15000", "--private-key", "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80", // anvil wallet #0 "--rpc-url", rpc.as_str(), "--gas-limit", "100000", ]) .assert_success(); }); // Test cast mktx with EIP-4844 blob transaction (legacy format) casttest!(cast_mktx_eip4844_blob, |prj, cmd| { // Create a temporary blob data file let blob_data = b"dummy blob data for testing"; let blob_path = prj.root().join("blob_data.bin"); fs::write(&blob_path, blob_data).unwrap(); cmd.args([ "mktx", "--private-key", "0x0000000000000000000000000000000000000000000000000000000000000001", "--chain", "1", "--nonce", "0", "--gas-limit", "100000", "--gas-price", "10000000000", "--priority-gas-price", "1000000000", "--blob", "--eip4844", "--blob-gas-price", "1000000", "--path", blob_path.to_str().unwrap(), "0x0000000000000000000000000000000000000001", ]) .assert_success(); }); // Test cast mktx with EIP-7594 blob transaction (default format) casttest!(cast_mktx_eip7594_blob, |prj, cmd| { // Create a temporary blob data file let blob_data = b"dummy peerdas blob data for testing"; let blob_path = prj.root().join("peerdas_blob_data.bin"); fs::write(&blob_path, blob_data).unwrap(); cmd.args([ "mktx", "--private-key", "0x0000000000000000000000000000000000000000000000000000000000000001", "--chain", "1", "--nonce", "0", "--gas-limit", "100000", "--gas-price", "10000000000", "--priority-gas-price", "1000000000", "--blob", "--blob-gas-price", "1000000", "--path", blob_path.to_str().unwrap(), "0x0000000000000000000000000000000000000001", ]) .assert_success(); }); // Test cast access-list with negative numbers casttest!(cast_access_list_negative_numbers, |_prj, cmd| { let rpc = next_rpc_endpoint(NamedChain::Sepolia); cmd.args([ "access-list", "0x9999999999999999999999999999999999999999", "adjustPosition(int128)", "-33333", "--gas-limit", "1000000", "--rpc-url", rpc.as_str(), ]) .assert_success(); }); // tests that cast call properly applies multiple state diff overrides // casttest!(cast_call_can_override_several_state_diff, |_prj, cmd| { let rpc = next_http_archive_rpc_url(); cmd.args([ "call", "--trace", "--from", "0xf6F444fD3B0088c1375671c05A7513661beFa4e6", "0x5EA1d9A6dDC3A0329378a327746D71A2019eC332", "--rpc-url", rpc.as_str(), "--block", "23290753", "--data", "0xe75235b8", "--override-state-diff", "0x5EA1d9A6dDC3A0329378a327746D71A2019eC332:0xf0af0268363540b847b4c07f2f9a0401c607c1b11ebca511724a71755dfd4137:1,0x5EA1d9A6dDC3A0329378a327746D71A2019eC332:4:1,0x5EA1d9A6dDC3A0329378a327746D71A2019eC332:0x4a204f620c8c5ccdca3fd54d003badd85ba500436a431f0cbda4f558c93c34c8:0,0x5EA1d9A6dDC3A0329378a327746D71A2019eC332:0xb104e0b93118902c651344349b610029d694cfdec91c589c91ebafbcd0289947:0", ]) .assert_success() .stdout_eq(str![[r#" ... [..] 0x5EA1d9A6dDC3A0329378a327746D71A2019eC332::getThreshold() ... "#]]); cmd.cast_fuse().args([ "call", "--trace", "--from", "0x2066901073a33ba2500274704aB04763875cF210", "0x5EA1d9A6dDC3A0329378a327746D71A2019eC332", "--rpc-url", rpc.as_str(), "--block", "23290753", "--data", "0x2f54bf6e0000000000000000000000002066901073a33ba2500274704ab04763875cf210", "--override-state-diff", "0x5EA1d9A6dDC3A0329378a327746D71A2019eC332:0xf0af0268363540b847b4c07f2f9a0401c607c1b11ebca511724a71755dfd4137:1,0x5EA1d9A6dDC3A0329378a327746D71A2019eC332:4:1,0x5EA1d9A6dDC3A0329378a327746D71A2019eC332:0x4a204f620c8c5ccdca3fd54d003badd85ba500436a431f0cbda4f558c93c34c8:0,0x5EA1d9A6dDC3A0329378a327746D71A2019eC332:0xb104e0b93118902c651344349b610029d694cfdec91c589c91ebafbcd0289947:0", ]) .assert_success() .stdout_eq(str![[r#" ... [..] 0x5EA1d9A6dDC3A0329378a327746D71A2019eC332::isOwner(0x2066901073a33ba2500274704aB04763875cF210) ... "#]]); }); casttest!(correct_json_serialization, |_prj, cmd| { let rpc = next_http_archive_rpc_url(); // cast calldata "decimals()" let calldata = "0x313ce567"; let tokens = [ "0xdac17f958d2ee523a2206206994597c13d831ec7", // USDT "0x6b175474e89094c44da98b954eedeac495271d0f", // DAI "0x6b175474e89094c44da98b954eedeac495271d0f", // WETH ]; let calldata_args = format!( "[{}]", tokens .iter() .map(|token| format!("({token},false,{calldata})")) .collect::>() .join(",") ); let args = vec![ "call", "--json", "--rpc-url", rpc.as_str(), "0xcA11bde05977b3631167028862bE2a173976CA11", "aggregate3((address,bool,bytes)[])((bool,bytes)[])", &calldata_args, ]; let expected_output = json!([[ [true, "0x0000000000000000000000000000000000000000000000000000000000000006"], [true, "0x0000000000000000000000000000000000000000000000000000000000000012"], [true, "0x0000000000000000000000000000000000000000000000000000000000000012"] ]]); let decoded: serde_json::Value = serde_json::from_slice(&cmd.args(args).assert_success().get_output().stdout) .expect("not valid json"); assert_eq!(decoded, expected_output); }); // Test cast abi-encode-event with indexed parameters casttest!(abi_encode_event_indexed, |_prj, cmd| { cmd.args([ "abi-encode-event", "Transfer(address indexed from, address indexed to, uint256 value)", "0x1234567890123456789012345678901234567890", "0xabcdefabcdefabcdefabcdefabcdefabcdefabcd", "1000", ]) .assert_success() .stdout_eq(str![[r#" [topic0]: 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef [topic1]: 0x0000000000000000000000001234567890123456789012345678901234567890 [topic2]: 0x000000000000000000000000abcdefabcdefabcdefabcdefabcdefabcdefabcd [data]: 0x00000000000000000000000000000000000000000000000000000000000003e8 "#]]); }); // Test cast abi-encode-event with no indexed parameters casttest!(abi_encode_event_no_indexed, |_prj, cmd| { cmd.args([ "abi-encode-event", "Approval(address owner, address spender, uint256 value)", "0x1234567890123456789012345678901234567890", "0xabcdefabcdefabcdefabcdefabcdefabcdefabcd", "2000" ]) .assert_success() .stdout_eq(str![[r#" [topic0]: 0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925 [data]: 0x0000000000000000000000001234567890123456789012345678901234567890000000000000000000000000abcdefabcdefabcdefabcdefabcdefabcdefabcd00000000000000000000000000000000000000000000000000000000000007d0 "#]]); }); // Test cast abi-encode-event with dynamic indexed parameter (string) casttest!(abi_encode_event_dynamic_indexed, |_prj, cmd| { cmd.args(["abi-encode-event", "Log(string indexed message, uint256 data)", "hello", "42"]) .assert_success() .stdout_eq(str![[r#" [topic0]: 0xdd970dd9b5bfe707922155b058a407655cb18288b807e2216442bca8ad83d6b5 [topic1]: 0x984002fcc0ca639f96622add24c2edd2fe72c65e71ca3faa243e091e0bc7cdab [data]: 0x000000000000000000000000000000000000000000000000000000000000002a "#]]); }); // Test cast run Celo transfer with precompiles. casttest!( #[ignore = "requires debug_traceTransaction, which most free Celo RPC endpoints no longer support"] flaky_run_celo_with_precompiles, |_prj, cmd| { let rpc = next_rpc_endpoint(NamedChain::Celo); cmd.args([ "run", "0xa652b9f41bb1a617ea6b2835b3316e79f0f21b8264e7bcd20e57c4092a70a0f6", "--quick", "--rpc-url", rpc.as_str(), ]) .assert_success() .stdout_eq(str![[r#" Traces: [17776] 0x471EcE3750Da237f93B8E339c536989b8978a438::transfer(0xD2eB2d37d238Caeff39CFA36A013299C6DbAC56A, 138000000000000000 [1.38e17]) ├─ [12370] 0xFeA1B35f1D5f2A58532a70e7A32e6F2D3Bc4F7B1::transfer(0xD2eB2d37d238Caeff39CFA36A013299C6DbAC56A, 138000000000000000 [1.38e17]) [delegatecall] │ ├─ [9000] CELO_TRANSFER_PRECOMPILE::00000000(00000000000000008106680ba7095cfd8f4351a8b7041da3060afb83000000000000000000000000d2eb2d37d238caeff39cfa36a013299c6dbac56a00000000000000000000000000000000000000000000000001ea4644d3010000) │ │ └─ ← [Return] │ ├─ emit Transfer(param0: 0x8106680Ba7095CfD8F4351a8B7041da3060Afb83, param1: 0xD2eB2d37d238Caeff39CFA36A013299C6DbAC56A, param2: 138000000000000000 [1.38e17]) │ └─ ← [Return] 0x0000000000000000000000000000000000000000000000000000000000000001 └─ ← [Return] 0x0000000000000000000000000000000000000000000000000000000000000001 Transaction successfully executed. [GAS] "#]]); } ); casttest!(keccak_stdin_bytes, |_prj, cmd| { cmd.args(["keccak"]).stdin("0x12").assert_success().stdout_eq(str![[r#" 0x5fa2358263196dbbf23d1ca7a509451f7a2f64c15837bfbb81298b1e3e24e4fa "#]]); }); casttest!(keccak_stdin_bytes_with_newline, |_prj, cmd| { cmd.args(["keccak"]).stdin("0x12\n").assert_success().stdout_eq(str![[r#" 0x5fa2358263196dbbf23d1ca7a509451f7a2f64c15837bfbb81298b1e3e24e4fa "#]]); }); // Test cast send with raw --data flag using encoded calldata forgetest_async!(cast_send_with_data, |prj, cmd| { let (api, handle) = anvil::spawn(NodeConfig::test()).await; foundry_test_utils::util::initialize(prj.root()); prj.initialize_default_contracts(); // Deploy counter contract cmd.args([ "script", "--private-key", "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80", "--rpc-url", &handle.http_endpoint(), "--broadcast", "CounterScript", ]) .assert_success(); // setNumber(111) encoded: selector 0x3fb5c1cb + uint256(111) let calldata = "0x3fb5c1cb000000000000000000000000000000000000000000000000000000000000006f"; // Send tx using --data instead of sig+args cmd.cast_fuse() .args([ "send", "0x5FbDB2315678afecb367f032d93F642f64180aa3", "--data", calldata, "--private-key", "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80", "--rpc-url", &handle.http_endpoint(), ]) .assert_success(); // Verify via trace that setNumber(111) was called let tx_hash = api .transaction_by_block_number_and_index(BlockNumberOrTag::Latest, Index::from(0)) .await .unwrap() .unwrap() .tx_hash(); cmd.cast_fuse() .args([ "run", format!("{tx_hash}").as_str(), "-vvvvv", "--rpc-url", &handle.http_endpoint(), ]) .assert_success() .stdout_eq(str![[r#" Executing previous transactions from the block. Traces: [..] 0x5FbDB2315678afecb367f032d93F642f64180aa3::setNumber(111) ├─ storage changes: │ @ 0: 0 → 111 └─ ← [Stop] Transaction successfully executed. [GAS] "#]]); }); // tests that the --curl flag outputs a valid curl command for cast rpc casttest!(curl_rpc, |_prj, cmd| { let rpc = "https://eth.example.com"; let output = cmd .args(["rpc", "eth_blockNumber", "--rpc-url", rpc, "--curl"]) .assert_success() .get_output() .stdout_lossy(); // Verify curl command structure assert!(output.contains("curl -X POST")); assert!(output.contains("-H 'Content-Type: application/json'")); assert!(output.contains("eth_blockNumber")); assert!(output.contains("jsonrpc")); assert!(output.contains(rpc)); }); // tests that the --curl flag outputs a valid curl command for cast block-number casttest!(curl_block_number, |_prj, cmd| { let rpc = "https://eth.example.com"; let output = cmd .args(["block-number", "--rpc-url", rpc, "--curl"]) .assert_success() .get_output() .stdout_lossy(); // Verify curl command structure assert!(output.contains("curl -X POST")); assert!(output.contains("eth_blockNumber")); assert!(output.contains(rpc)); }); // tests that the --curl flag outputs a valid curl command for cast chain-id casttest!(curl_chain_id, |_prj, cmd| { let rpc = "https://eth.example.com"; let output = cmd .args(["chain-id", "--rpc-url", rpc, "--curl"]) .assert_success() .get_output() .stdout_lossy(); // Verify curl command structure assert!(output.contains("curl -X POST")); assert!(output.contains("eth_chainId")); assert!(output.contains(rpc)); }); // tests that the --curl flag outputs a valid curl command for cast gas-price casttest!(curl_gas_price, |_prj, cmd| { let rpc = "https://eth.example.com"; let output = cmd .args(["gas-price", "--rpc-url", rpc, "--curl"]) .assert_success() .get_output() .stdout_lossy(); // Verify curl command structure assert!(output.contains("curl -X POST")); assert!(output.contains("eth_gasPrice")); assert!(output.contains(rpc)); }); // tests that the --curl flag outputs a valid curl command for cast call casttest!(curl_call, |_prj, cmd| { let rpc = "https://eth.example.com"; let to = "0xdead000000000000000000000000000000000000"; let output = cmd .args(["call", to, "balanceOf(address)(uint256)", to, "--rpc-url", rpc, "--curl"]) .assert_success() .get_output() .stdout_lossy(); // Verify curl command structure assert!(output.contains("curl -X POST")); assert!(output.contains("eth_call")); assert!(output.contains(rpc)); }); // https://github.com/foundry-rs/foundry/issues/11584 // Tests that invalid hex calldata (odd length) produces a clear error message casttest!(cast_call_invalid_hex_calldata_error, |_prj, cmd| { let rpc = next_rpc_endpoint(NamedChain::Mainnet); cmd.args([ "call", "0xdead000000000000000000000000000000000000", "--data", "0x0", // Invalid: odd length hex "--rpc-url", rpc.as_str(), ]) .assert_failure() .stderr_eq(str![[r#" Error: Invalid hex calldata '0x0': odd number of digits "#]]); }); // https://github.com/foundry-rs/foundry/issues/11584 // Tests that valid hex calldata works correctly casttest!(cast_call_valid_hex_calldata, |_prj, cmd| { let rpc = next_rpc_endpoint(NamedChain::Mainnet); cmd.args([ "call", "0xdead000000000000000000000000000000000000", "--data", "0x00", // Valid: even length hex "--rpc-url", rpc.as_str(), ]) .assert_success(); }); // https://github.com/foundry-rs/foundry/issues/11584 // Tests that invalid hex with uppercase 0X prefix also produces clear error casttest!(cast_call_invalid_hex_uppercase_prefix, |_prj, cmd| { let rpc = next_rpc_endpoint(NamedChain::Mainnet); cmd.args([ "call", "0xdead000000000000000000000000000000000000", "--data", "0X1", // Invalid: odd length hex with uppercase prefix "--rpc-url", rpc.as_str(), ]) .assert_failure() .stderr_eq(str![[r#" Error: Invalid hex calldata '0X1': odd number of digits "#]]); }); ================================================ FILE: crates/cast/tests/cli/selectors.rs ================================================ use foundry_test_utils::util::OutputExt; use std::path::Path; casttest!(flaky_error_decode_with_openchain, |prj, cmd| { prj.clear_cache(); cmd.args(["decode-error", "0x7a0e198500000000000000000000000000000000000000000000000000000000000000650000000000000000000000000000000000000000000000000000000000000064"]).assert_success().stdout_eq(str![[r#" ValueTooHigh(uint256,uint256) 101 100 "#]]); }); casttest!(flaky_fourbyte, |_prj, cmd| { cmd.args(["4byte", "0xa9059cbb"]).assert_success().stdout_eq(str![[r#" transfer(address,uint256) "#]]); }); casttest!(fourbyte_invalid, |_prj, cmd| { cmd.args(["4byte", "0xa9059c"]).assert_failure().stderr_eq(str![[r#" error: invalid value '0xa9059c' for '[SELECTOR]': invalid string length For more information, try '--help'. "#]]); }); casttest!(flaky_fourbyte_calldata, |_prj, cmd| { cmd.args(["4byte-calldata", "0xa9059cbb0000000000000000000000000a2ac0c368dc8ec680a0c98c907656bd970675950000000000000000000000000000000000000000000000000000000767954a79"]).assert_success().stdout_eq(str![[r#" 1) "transfer(address,uint256)" 0x0A2AC0c368Dc8eC680a0c98C907656BD97067595 31802608249 [3.18e10] "#]]); }); casttest!(flaky_fourbyte_calldata_only_selector, |_prj, cmd| { cmd.args(["4byte-calldata", "0xa9059cbb"]).assert_success().stdout_eq(str![[r#" transfer(address,uint256) "#]]); }); casttest!(flaky_fourbyte_calldata_alias, |_prj, cmd| { cmd.args(["4byte-decode", "0xa9059cbb0000000000000000000000000a2ac0c368dc8ec680a0c98c907656bd970675950000000000000000000000000000000000000000000000000000000767954a79"]).assert_success().stdout_eq(str![[r#" 1) "transfer(address,uint256)" 0x0A2AC0c368Dc8eC680a0c98C907656BD97067595 31802608249 [3.18e10] "#]]); }); casttest!(flaky_fourbyte_event, |_prj, cmd| { cmd.args(["4byte-event", "0x7e1db2a1cd12f0506ecd806dba508035b290666b84b096a87af2fd2a1516ede6"]) .assert_success() .stdout_eq(str![[r#" updateAuthority(address,uint8) "#]]); }); casttest!(flaky_fourbyte_event_2, |_prj, cmd| { cmd.args(["4byte-event", "0xb7009613e63fb13fd59a2fa4c206a992c1f090a44e5d530be255aa17fed0b3dd"]) .assert_success() .stdout_eq(str![[r#" canCall(address,address,bytes4) "#]]); }); casttest!(flaky_upload_signatures, |_prj, cmd| { // test no prefix is accepted as function let output = cmd .args(["upload-signature", "transfer(address,uint256)"]) .assert_success() .get_output() .stdout_lossy(); assert!(output.contains("Function transfer(address,uint256): 0xa9059cbb"), "{}", output); // test event prefix cmd.args(["upload-signature", "event Transfer(address,uint256)"]); let output = cmd.assert_success().get_output().stdout_lossy(); assert!(output.contains("Event Transfer(address,uint256): 0x69ca02dd4edd7bf0a4abb9ed3b7af3f14778db5d61921c7dc7cd545266326de2"), "{}", output); // test error prefix cmd.args(["upload-signature", "error ERC20InsufficientBalance(address,uint256,uint256)"]); let output = cmd.assert_success().get_output().stdout_lossy(); assert!( output.contains("Function ERC20InsufficientBalance(address,uint256,uint256): 0xe450d38c"), "{}", output ); // Custom error is interpreted as function // test multiple sigs cmd.args([ "upload-signature", "event Transfer(address,uint256)", "transfer(address,uint256)", "approve(address,uint256)", ]); let output = cmd.assert_success().get_output().stdout_lossy(); assert!(output.contains("Event Transfer(address,uint256): 0x69ca02dd4edd7bf0a4abb9ed3b7af3f14778db5d61921c7dc7cd545266326de2"), "{}", output); assert!(output.contains("Function transfer(address,uint256): 0xa9059cbb"), "{}", output); assert!(output.contains("Function approve(address,uint256): 0x095ea7b3"), "{}", output); // test abi cmd.args([ "upload-signature", "event Transfer(address,uint256)", "transfer(address,uint256)", "error ERC20InsufficientBalance(address,uint256,uint256)", Path::new(env!("CARGO_MANIFEST_DIR")) .join("tests/fixtures/ERC20Artifact.json") .as_os_str() .to_str() .unwrap(), ]); let output = cmd.assert_success().get_output().stdout_lossy(); assert!(output.contains("Event Transfer(address,uint256): 0x69ca02dd4edd7bf0a4abb9ed3b7af3f14778db5d61921c7dc7cd545266326de2"), "{}", output); assert!(output.contains("Function transfer(address,uint256): 0xa9059cbb"), "{}", output); assert!(output.contains("Function approve(address,uint256): 0x095ea7b3"), "{}", output); assert!(output.contains("Function decimals(): 0x313ce567"), "{}", output); assert!(output.contains("Function allowance(address,address): 0xdd62ed3e"), "{}", output); assert!( output.contains("Function ERC20InsufficientBalance(address,uint256,uint256): 0xe450d38c"), "{}", output ); }); // tests cast can decode event with provided signature casttest!(event_decode_with_sig, |_prj, cmd| { cmd.args(["decode-event", "--sig", "MyEvent(uint256,address)", "0x000000000000000000000000000000000000000000000000000000000000004e0000000000000000000000000000000000000000000000000000000000d0004f"]).assert_success().stdout_eq(str![[r#" 78 0x0000000000000000000000000000000000D0004F "#]]); cmd.args(["--json"]).assert_success().stdout_eq(str![[r#" [ 78, "0x0000000000000000000000000000000000D0004F" ] "#]]); }); // tests cast can decode event with Openchain API casttest!(flaky_event_decode_with_openchain, |prj, cmd| { prj.clear_cache(); cmd.args(["decode-event", "0xe27c4c1372396a3d15a9922f74f9dfc7c72b1ad6d63868470787249c356454c1000000000000000000000000000000000000000000000000000000000000004e00000000000000000000000000000000000000000000000000000dd00000004e"]).assert_success().stdout_eq(str![[r#" BaseCurrencySet(address,uint256) 0x000000000000000000000000000000000000004e 15187004358734 [1.518e13] "#]]); }); // tests cast can decode error with provided signature casttest!(error_decode_with_sig, |_prj, cmd| { cmd.args(["decode-error", "--sig", "AnotherValueTooHigh(uint256,address)", "0x7191bc6200000000000000000000000000000000000000000000000000000000000000650000000000000000000000000000000000000000000000000000000000D0004F"]).assert_success().stdout_eq(str![[r#" 101 0x0000000000000000000000000000000000D0004F "#]]); cmd.args(["--json"]).assert_success().stdout_eq(str![[r#" [ 101, "0x0000000000000000000000000000000000D0004F" ] "#]]); }); // tests cast can decode error and event when using local sig identifiers cache forgetest_init!(flaky_error_event_decode_with_cache, |prj, cmd| { prj.add_source( "LocalProjectContract", r#" contract ContractWithCustomError { error AnotherValueTooHigh(uint256, address); event MyUniqueEventWithinLocalProject(uint256 a, address b); } "#, ); // Build and cache project selectors. cmd.forge_fuse().args(["build"]).assert_success(); cmd.forge_fuse().args(["selectors", "cache"]).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 "#]]); }); forgetest!(flaky_cache_selectors_from_extra_abis, |prj, cmd| { // Create folder with ABI JSON files containing a unique error let abis_dir = prj.root().join("external_abis"); std::fs::create_dir(&abis_dir).unwrap(); std::fs::write( abis_dir.join("test.json"), r#"[{ "type": "error", "name": "MyUniqueExtraAbiError", "inputs": [ {"name": "value", "type": "uint256"}, {"name": "flag", "type": "bool"} ] }]"#, ) .unwrap(); cmd.forge_fuse() .args(["selectors", "cache", "--extra-abis-path", abis_dir.to_str().unwrap()]) .assert_success(); // Verify with cast decode-error (uses local cache via SignaturesIdentifier) // Selector for MyUniqueExtraAbiError(uint256,bool) is 0x7819b107 // Encoded: selector + uint256(42) + bool(true) cmd.cast_fuse() .args(["decode-error", "0x7819b107000000000000000000000000000000000000000000000000000000000000002a0000000000000000000000000000000000000000000000000000000000000001"]) .assert_success() .stdout_eq(str![[r#" MyUniqueExtraAbiError(uint256,bool) 42 true "#]]); }); ================================================ FILE: crates/cast/tests/fixtures/ERC20Artifact.json ================================================ {"abi":[{"inputs":[{"internalType":"string","name":"name","type":"string"},{"internalType":"string","name":"symbol","type":"string"},{"internalType":"uint8","name":"decimals","type":"uint8"}],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"spender","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"Transfer","type":"event"},{"inputs":[],"name":"DOMAIN_SEPARATOR","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"allowance","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"approve","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"decimals","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"mint","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"name","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"nonces","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"},{"internalType":"uint256","name":"deadline","type":"uint256"},{"internalType":"uint8","name":"v","type":"uint8"},{"internalType":"bytes32","name":"r","type":"bytes32"},{"internalType":"bytes32","name":"s","type":"bytes32"}],"name":"permit","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"symbol","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalSupply","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"transfer","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"transferFrom","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"}],"bytecode":{"object":"0x60e06040523480156200001157600080fd5b5060405162000f7738038062000f7783398101604081905262000034916200029a565b82828282600090805190602001906200004f92919062000127565b5081516200006590600190602085019062000127565b5060ff81166080524660a0526200007b6200008b565b60c0525062000400945050505050565b60007f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f6000604051620000bf91906200035c565b6040805191829003822060208301939093528101919091527fc89efdaa54c0f20c7adf612882df0950f5a951637e0307cdcb4c672f298b8bc660608201524660808201523060a082015260c00160405160208183030381529060405280519060200120905090565b82805462000135906200031f565b90600052602060002090601f016020900481019282620001595760008555620001a4565b82601f106200017457805160ff1916838001178555620001a4565b82800160010185558215620001a4579182015b82811115620001a457825182559160200191906001019062000187565b50620001b2929150620001b6565b5090565b5b80821115620001b25760008155600101620001b7565b634e487b7160e01b600052604160045260246000fd5b600082601f830112620001f557600080fd5b81516001600160401b0380821115620002125762000212620001cd565b604051601f8301601f19908116603f011681019082821181831017156200023d576200023d620001cd565b816040528381526020925086838588010111156200025a57600080fd5b600091505b838210156200027e57858201830151818301840152908201906200025f565b83821115620002905760008385830101525b9695505050505050565b600080600060608486031215620002b057600080fd5b83516001600160401b0380821115620002c857600080fd5b620002d687838801620001e3565b94506020860151915080821115620002ed57600080fd5b50620002fc86828701620001e3565b925050604084015160ff811681146200031457600080fd5b809150509250925092565b600181811c908216806200033457607f821691505b602082108114156200035657634e487b7160e01b600052602260045260246000fd5b50919050565b600080835481600182811c9150808316806200037957607f831692505b60208084108214156200039a57634e487b7160e01b86526022600452602486fd5b818015620003b15760018114620003c357620003f2565b60ff19861689528489019650620003f2565b60008a81526020902060005b86811015620003ea5781548b820152908501908301620003cf565b505084890196505b509498975050505050505050565b60805160a05160c051610b476200043060003960006104530152600061041e015260006101440152610b476000f3fe608060405234801561001057600080fd5b50600436106100cf5760003560e01c806340c10f191161008c57806395d89b411161006657806395d89b41146101d5578063a9059cbb146101dd578063d505accf146101f0578063dd62ed3e1461020357600080fd5b806340c10f191461018057806370a08231146101955780637ecebe00146101b557600080fd5b806306fdde03146100d4578063095ea7b3146100f257806318160ddd1461011557806323b872dd1461012c578063313ce5671461013f5780633644e51514610178575b600080fd5b6100dc61022e565b6040516100e99190610856565b60405180910390f35b6101056101003660046108c7565b6102bc565b60405190151581526020016100e9565b61011e60025481565b6040519081526020016100e9565b61010561013a3660046108f1565b610328565b6101667f000000000000000000000000000000000000000000000000000000000000000081565b60405160ff90911681526020016100e9565b61011e61041a565b61019361018e3660046108c7565b610475565b005b61011e6101a336600461092d565b60036020526000908152604090205481565b61011e6101c336600461092d565b60056020526000908152604090205481565b6100dc610483565b6101056101eb3660046108c7565b610490565b6101936101fe36600461094f565b610508565b61011e6102113660046109c2565b600460209081526000928352604080842090915290825290205481565b6000805461023b906109f5565b80601f0160208091040260200160405190810160405280929190818152602001828054610267906109f5565b80156102b45780601f10610289576101008083540402835291602001916102b4565b820191906000526020600020905b81548152906001019060200180831161029757829003601f168201915b505050505081565b3360008181526004602090815260408083206001600160a01b038716808552925280832085905551919290917f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925906103179086815260200190565b60405180910390a350600192915050565b6001600160a01b038316600090815260046020908152604080832033845290915281205460001981146103845761035f8382610a46565b6001600160a01b03861660009081526004602090815260408083203384529091529020555b6001600160a01b038516600090815260036020526040812080548592906103ac908490610a46565b90915550506001600160a01b03808516600081815260036020526040908190208054870190555190918716907fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef906104079087815260200190565b60405180910390a3506001949350505050565b60007f000000000000000000000000000000000000000000000000000000000000000046146104505761044b610751565b905090565b507f000000000000000000000000000000000000000000000000000000000000000090565b61047f82826107eb565b5050565b6001805461023b906109f5565b336000908152600360205260408120805483919083906104b1908490610a46565b90915550506001600160a01b038316600081815260036020526040908190208054850190555133907fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef906103179086815260200190565b4284101561055d5760405162461bcd60e51b815260206004820152601760248201527f5045524d49545f444541444c494e455f4558504952454400000000000000000060448201526064015b60405180910390fd5b6000600161056961041a565b6001600160a01b038a811660008181526005602090815260409182902080546001810190915582517f6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c98184015280840194909452938d166060840152608083018c905260a083019390935260c08083018b90528151808403909101815260e08301909152805192019190912061190160f01b6101008301526101028201929092526101228101919091526101420160408051601f198184030181528282528051602091820120600084529083018083525260ff871690820152606081018590526080810184905260a0016020604051602081039080840390855afa158015610675573d6000803e3d6000fd5b5050604051601f1901519150506001600160a01b038116158015906106ab5750876001600160a01b0316816001600160a01b0316145b6106e85760405162461bcd60e51b815260206004820152600e60248201526d24a72b20a624a22fa9a4a3a722a960911b6044820152606401610554565b6001600160a01b0390811660009081526004602090815260408083208a8516808552908352928190208990555188815291928a16917f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925910160405180910390a350505050505050565b60007f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f60006040516107839190610a5d565b6040805191829003822060208301939093528101919091527fc89efdaa54c0f20c7adf612882df0950f5a951637e0307cdcb4c672f298b8bc660608201524660808201523060a082015260c00160405160208183030381529060405280519060200120905090565b80600260008282546107fd9190610af9565b90915550506001600160a01b0382166000818152600360209081526040808320805486019055518481527fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef910160405180910390a35050565b600060208083528351808285015260005b8181101561088357858101830151858201604001528201610867565b81811115610895576000604083870101525b50601f01601f1916929092016040019392505050565b80356001600160a01b03811681146108c257600080fd5b919050565b600080604083850312156108da57600080fd5b6108e3836108ab565b946020939093013593505050565b60008060006060848603121561090657600080fd5b61090f846108ab565b925061091d602085016108ab565b9150604084013590509250925092565b60006020828403121561093f57600080fd5b610948826108ab565b9392505050565b600080600080600080600060e0888a03121561096a57600080fd5b610973886108ab565b9650610981602089016108ab565b95506040880135945060608801359350608088013560ff811681146109a557600080fd5b9699959850939692959460a0840135945060c09093013592915050565b600080604083850312156109d557600080fd5b6109de836108ab565b91506109ec602084016108ab565b90509250929050565b600181811c90821680610a0957607f821691505b60208210811415610a2a57634e487b7160e01b600052602260045260246000fd5b50919050565b634e487b7160e01b600052601160045260246000fd5b600082821015610a5857610a58610a30565b500390565b600080835481600182811c915080831680610a7957607f831692505b6020808410821415610a9957634e487b7160e01b86526022600452602486fd5b818015610aad5760018114610abe57610aeb565b60ff19861689528489019650610aeb565b60008a81526020902060005b86811015610ae35781548b820152908501908301610aca565b505084890196505b509498975050505050505050565b60008219821115610b0c57610b0c610a30565b50019056fea26469706673582212209ca55739676d094f8ef4b3b5cfcc08f600b805843c4c3027beb2b4159a02f63664736f6c634300080a0033","sourceMap":"113:230:22:-:0;;;148:102;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;224:4;230:6;238:8;2098:5:10;2091:4;:12;;;;;;;;;;;;:::i;:::-;-1:-1:-1;2113:16:10;;;;:6;;:16;;;;;:::i;:::-;-1:-1:-1;2139:20:10;;;;;2189:13;2170:32;;2239:24;:22;:24::i;:::-;2212:51;;-1:-1:-1;113:230:22;;-1:-1:-1;;;;;113:230:22;5507:446:10;5572:7;5669:95;5802:4;5786:22;;;;;;:::i;:::-;;;;;;;;;;5637:295;;;3635:25:23;;;;3676:18;;3669:34;;;;5830:14:10;3719:18:23;;;3712:34;5866:13:10;3762:18:23;;;3755:34;5909:4:10;3805:19:23;;;3798:61;3607:19;;5637:295:10;;;;;;;;;;;;5610:336;;;;;;5591:355;;5507:446;:::o;113:230:22:-;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;113:230:22;;;-1:-1:-1;113:230:22;:::i;:::-;;;:::o;:::-;;;;;;;;;;;;;;;14:127:23;75:10;70:3;66:20;63:1;56:31;106:4;103:1;96:15;130:4;127:1;120:15;146:885;200:5;253:3;246:4;238:6;234:17;230:27;220:55;;271:1;268;261:12;220:55;294:13;;-1:-1:-1;;;;;356:10:23;;;353:36;;;369:18;;:::i;:::-;444:2;438:9;412:2;498:13;;-1:-1:-1;;494:22:23;;;518:2;490:31;486:40;474:53;;;542:18;;;562:22;;;539:46;536:72;;;588:18;;:::i;:::-;628:10;624:2;617:22;663:2;655:6;648:18;685:4;675:14;;730:3;725:2;720;712:6;708:15;704:24;701:33;698:53;;;747:1;744;737:12;698:53;769:1;760:10;;779:133;793:2;790:1;787:9;779:133;;;881:14;;;877:23;;871:30;850:14;;;846:23;;839:63;804:10;;;;779:133;;;930:2;927:1;924:9;921:80;;;989:1;984:2;979;971:6;967:15;963:24;956:35;921:80;1019:6;146:885;-1:-1:-1;;;;;;146:885:23:o;1036:712::-;1142:6;1150;1158;1211:2;1199:9;1190:7;1186:23;1182:32;1179:52;;;1227:1;1224;1217:12;1179:52;1254:16;;-1:-1:-1;;;;;1319:14:23;;;1316:34;;;1346:1;1343;1336:12;1316:34;1369:61;1422:7;1413:6;1402:9;1398:22;1369:61;:::i;:::-;1359:71;;1476:2;1465:9;1461:18;1455:25;1439:41;;1505:2;1495:8;1492:16;1489:36;;;1521:1;1518;1511:12;1489:36;;1544:63;1599:7;1588:8;1577:9;1573:24;1544:63;:::i;:::-;1534:73;;;1650:2;1639:9;1635:18;1629:25;1694:4;1687:5;1683:16;1676:5;1673:27;1663:55;;1714:1;1711;1704:12;1663:55;1737:5;1727:15;;;1036:712;;;;;:::o;1753:380::-;1832:1;1828:12;;;;1875;;;1896:61;;1950:4;1942:6;1938:17;1928:27;;1896:61;2003:2;1995:6;1992:14;1972:18;1969:38;1966:161;;;2049:10;2044:3;2040:20;2037:1;2030:31;2084:4;2081:1;2074:15;2112:4;2109:1;2102:15;1966:161;;1753:380;;;:::o;2267:1104::-;2397:3;2426:1;2459:6;2453:13;2489:3;2511:1;2539:9;2535:2;2531:18;2521:28;;2599:2;2588:9;2584:18;2621;2611:61;;2665:4;2657:6;2653:17;2643:27;;2611:61;2691:2;2739;2731:6;2728:14;2708:18;2705:38;2702:165;;;-1:-1:-1;;;2766:33:23;;2822:4;2819:1;2812:15;2852:4;2773:3;2840:17;2702:165;2883:18;2910:104;;;;3028:1;3023:323;;;;2876:470;;2910:104;-1:-1:-1;;2943:24:23;;2931:37;;2988:16;;;;-1:-1:-1;2910:104:23;;3023:323;2214:1;2207:14;;;2251:4;2238:18;;3121:1;3135:165;3149:6;3146:1;3143:13;3135:165;;;3227:14;;3214:11;;;3207:35;3270:16;;;;3164:10;;3135:165;;;3139:3;;3329:6;3324:3;3320:16;3313:23;;2876:470;-1:-1:-1;3362:3:23;;2267:1104;-1:-1:-1;;;;;;;;2267:1104:23:o;3376:489::-;113:230:22;;;;;;;;;;;;;;;;;;;;;;;;","linkReferences":{}},"deployedBytecode":{"object":"0x608060405234801561001057600080fd5b50600436106100cf5760003560e01c806340c10f191161008c57806395d89b411161006657806395d89b41146101d5578063a9059cbb146101dd578063d505accf146101f0578063dd62ed3e1461020357600080fd5b806340c10f191461018057806370a08231146101955780637ecebe00146101b557600080fd5b806306fdde03146100d4578063095ea7b3146100f257806318160ddd1461011557806323b872dd1461012c578063313ce5671461013f5780633644e51514610178575b600080fd5b6100dc61022e565b6040516100e99190610856565b60405180910390f35b6101056101003660046108c7565b6102bc565b60405190151581526020016100e9565b61011e60025481565b6040519081526020016100e9565b61010561013a3660046108f1565b610328565b6101667f000000000000000000000000000000000000000000000000000000000000000081565b60405160ff90911681526020016100e9565b61011e61041a565b61019361018e3660046108c7565b610475565b005b61011e6101a336600461092d565b60036020526000908152604090205481565b61011e6101c336600461092d565b60056020526000908152604090205481565b6100dc610483565b6101056101eb3660046108c7565b610490565b6101936101fe36600461094f565b610508565b61011e6102113660046109c2565b600460209081526000928352604080842090915290825290205481565b6000805461023b906109f5565b80601f0160208091040260200160405190810160405280929190818152602001828054610267906109f5565b80156102b45780601f10610289576101008083540402835291602001916102b4565b820191906000526020600020905b81548152906001019060200180831161029757829003601f168201915b505050505081565b3360008181526004602090815260408083206001600160a01b038716808552925280832085905551919290917f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925906103179086815260200190565b60405180910390a350600192915050565b6001600160a01b038316600090815260046020908152604080832033845290915281205460001981146103845761035f8382610a46565b6001600160a01b03861660009081526004602090815260408083203384529091529020555b6001600160a01b038516600090815260036020526040812080548592906103ac908490610a46565b90915550506001600160a01b03808516600081815260036020526040908190208054870190555190918716907fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef906104079087815260200190565b60405180910390a3506001949350505050565b60007f000000000000000000000000000000000000000000000000000000000000000046146104505761044b610751565b905090565b507f000000000000000000000000000000000000000000000000000000000000000090565b61047f82826107eb565b5050565b6001805461023b906109f5565b336000908152600360205260408120805483919083906104b1908490610a46565b90915550506001600160a01b038316600081815260036020526040908190208054850190555133907fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef906103179086815260200190565b4284101561055d5760405162461bcd60e51b815260206004820152601760248201527f5045524d49545f444541444c494e455f4558504952454400000000000000000060448201526064015b60405180910390fd5b6000600161056961041a565b6001600160a01b038a811660008181526005602090815260409182902080546001810190915582517f6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c98184015280840194909452938d166060840152608083018c905260a083019390935260c08083018b90528151808403909101815260e08301909152805192019190912061190160f01b6101008301526101028201929092526101228101919091526101420160408051601f198184030181528282528051602091820120600084529083018083525260ff871690820152606081018590526080810184905260a0016020604051602081039080840390855afa158015610675573d6000803e3d6000fd5b5050604051601f1901519150506001600160a01b038116158015906106ab5750876001600160a01b0316816001600160a01b0316145b6106e85760405162461bcd60e51b815260206004820152600e60248201526d24a72b20a624a22fa9a4a3a722a960911b6044820152606401610554565b6001600160a01b0390811660009081526004602090815260408083208a8516808552908352928190208990555188815291928a16917f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925910160405180910390a350505050505050565b60007f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f60006040516107839190610a5d565b6040805191829003822060208301939093528101919091527fc89efdaa54c0f20c7adf612882df0950f5a951637e0307cdcb4c672f298b8bc660608201524660808201523060a082015260c00160405160208183030381529060405280519060200120905090565b80600260008282546107fd9190610af9565b90915550506001600160a01b0382166000818152600360209081526040808320805486019055518481527fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef910160405180910390a35050565b600060208083528351808285015260005b8181101561088357858101830151858201604001528201610867565b81811115610895576000604083870101525b50601f01601f1916929092016040019392505050565b80356001600160a01b03811681146108c257600080fd5b919050565b600080604083850312156108da57600080fd5b6108e3836108ab565b946020939093013593505050565b60008060006060848603121561090657600080fd5b61090f846108ab565b925061091d602085016108ab565b9150604084013590509250925092565b60006020828403121561093f57600080fd5b610948826108ab565b9392505050565b600080600080600080600060e0888a03121561096a57600080fd5b610973886108ab565b9650610981602089016108ab565b95506040880135945060608801359350608088013560ff811681146109a557600080fd5b9699959850939692959460a0840135945060c09093013592915050565b600080604083850312156109d557600080fd5b6109de836108ab565b91506109ec602084016108ab565b90509250929050565b600181811c90821680610a0957607f821691505b60208210811415610a2a57634e487b7160e01b600052602260045260246000fd5b50919050565b634e487b7160e01b600052601160045260246000fd5b600082821015610a5857610a58610a30565b500390565b600080835481600182811c915080831680610a7957607f831692505b6020808410821415610a9957634e487b7160e01b86526022600452602486fd5b818015610aad5760018114610abe57610aeb565b60ff19861689528489019650610aeb565b60008a81526020902060005b86811015610ae35781548b820152908501908301610aca565b505084890196505b509498975050505050505050565b60008219821115610b0c57610b0c610a30565b50019056fea26469706673582212209ca55739676d094f8ef4b3b5cfcc08f600b805843c4c3027beb2b4159a02f63664736f6c634300080a0033","sourceMap":"113:230:22:-:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;1028:18:10;;;:::i;:::-;;;;;;;:::i;:::-;;;;;;;;2458:211;;;;;;:::i;:::-;;:::i;:::-;;;1218:14:23;;1211:22;1193:41;;1181:2;1166:18;2458:211:10;1053:187:23;1301:26:10;;;;;;;;;1391:25:23;;;1379:2;1364:18;1301:26:10;1245:177:23;3054:592:10;;;;;;:::i;:::-;;:::i;1080:31::-;;;;;;;;1932:4:23;1920:17;;;1902:36;;1890:2;1875:18;1080:31:10;1760:184:23;5324:177:10;;;:::i;256:85:22:-;;;;;;:::i;:::-;;:::i;:::-;;1334:44:10;;;;;;:::i;:::-;;;;;;;;;;;;;;1748:41;;;;;;:::i;:::-;;;;;;;;;;;;;;1053:20;;;:::i;2675:373::-;;;;;;:::i;:::-;;:::i;3835:1483::-;;;;;;:::i;:::-;;:::i;1385:64::-;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;1028:18;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::o;2458:211::-;2558:10;2532:4;2548:21;;;:9;:21;;;;;;;;-1:-1:-1;;;;;2548:30:10;;;;;;;;;;:39;;;2603:37;2532:4;;2548:30;;2603:37;;;;2581:6;1391:25:23;;1379:2;1364:18;;1245:177;2603:37:10;;;;;;;;-1:-1:-1;2658:4:10;2458:211;;;;:::o;3054:592::-;-1:-1:-1;;;;;3206:15:10;;3172:4;3206:15;;;:9;:15;;;;;;;;3222:10;3206:27;;;;;;;;-1:-1:-1;;3284:28:10;;3280:80;;3344:16;3354:6;3344:7;:16;:::i;:::-;-1:-1:-1;;;;;3314:15:10;;;;;;:9;:15;;;;;;;;3330:10;3314:27;;;;;;;:46;3280:80;-1:-1:-1;;;;;3371:15:10;;;;;;:9;:15;;;;;:25;;3390:6;;3371:15;:25;;3390:6;;3371:25;:::i;:::-;;;;-1:-1:-1;;;;;;;3542:13:10;;;;;;;:9;:13;;;;;;;:23;;;;;;3591:26;3542:13;;3591:26;;;;;;;3559:6;1391:25:23;;1379:2;1364:18;;1245:177;3591:26:10;;;;;;;;-1:-1:-1;3635:4:10;;3054:592;-1:-1:-1;;;;3054:592:10:o;5324:177::-;5381:7;5424:16;5407:13;:33;:87;;5470:24;:22;:24::i;:::-;5400:94;;5324:177;:::o;5407:87::-;-1:-1:-1;5443:24:10;;5324:177::o;256:85:22:-;317:17;323:2;327:6;317:5;:17::i;:::-;256:85;;:::o;1053:20:10:-;;;;;;;:::i;2675:373::-;2771:10;2745:4;2761:21;;;:9;:21;;;;;:31;;2786:6;;2761:21;2745:4;;2761:31;;2786:6;;2761:31;:::i;:::-;;;;-1:-1:-1;;;;;;;2938:13:10;;;;;;:9;:13;;;;;;;:23;;;;;;2987:32;2996:10;;2987:32;;;;2955:6;1391:25:23;;1379:2;1364:18;;1245:177;3835:1483:10;4054:15;4042:8;:27;;4034:63;;;;-1:-1:-1;;;4034:63:10;;4134:2:23;4034:63:10;;;4116:21:23;4173:2;4153:18;;;4146:30;4212:25;4192:18;;;4185:53;4255:18;;4034:63:10;;;;;;;;;4262:24;4289:805;4425:18;:16;:18::i;:::-;-1:-1:-1;;;;;4870:13:10;;;;;;;:6;:13;;;;;;;;;:15;;;;;;;;4508:449;;4552:165;4508:449;;;4571:25:23;4650:18;;;4643:43;;;;4722:15;;;4702:18;;;4695:43;4754:18;;;4747:34;;;4797:19;;;4790:35;;;;4841:19;;;;4834:35;;;4508:449:10;;;;;;;;;;4543:19:23;;;4508:449:10;;;4469:514;;;;;;;;-1:-1:-1;;;4347:658:10;;;5138:27:23;5181:11;;;5174:27;;;;5217:12;;;5210:28;;;;5254:12;;4347:658:10;;;-1:-1:-1;;4347:658:10;;;;;;;;;4316:707;;4347:658;4316:707;;;;4289:805;;;;;;;;;5504:25:23;5577:4;5565:17;;5545:18;;;5538:45;5599:18;;;5592:34;;;5642:18;;;5635:34;;;5476:19;;4289:805:10;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;;4289:805:10;;-1:-1:-1;;4289:805:10;;;-1:-1:-1;;;;;;;5117:30:10;;;;;;:59;;;5171:5;-1:-1:-1;;;;;5151:25:10;:16;-1:-1:-1;;;;;5151:25:10;;5117:59;5109:86;;;;-1:-1:-1;;;5109:86:10;;5882:2:23;5109:86:10;;;5864:21:23;5921:2;5901:18;;;5894:30;-1:-1:-1;;;5940:18:23;;;5933:44;5994:18;;5109:86:10;5680:338:23;5109:86:10;-1:-1:-1;;;;;5210:27:10;;;;;;;:9;:27;;;;;;;;:36;;;;;;;;;;;;;:44;;;5280:31;1391:25:23;;;5210:36:10;;5280:31;;;;;1364:18:23;5280:31:10;;;;;;;3835:1483;;;;;;;:::o;5507:446::-;5572:7;5669:95;5802:4;5786:22;;;;;;:::i;:::-;;;;;;;;;;5637:295;;;7520:25:23;;;;7561:18;;7554:34;;;;5830:14:10;7604:18:23;;;7597:34;5866:13:10;7647:18:23;;;7640:34;5909:4:10;7690:19:23;;;7683:61;7492:19;;5637:295:10;;;;;;;;;;;;5610:336;;;;;;5591:355;;5507:446;:::o;6147:325::-;6232:6;6217:11;;:21;;;;;;;:::i;:::-;;;;-1:-1:-1;;;;;;;6384:13:10;;;;;;:9;:13;;;;;;;;:23;;;;;;6433:32;1391:25:23;;;6433:32:10;;1364:18:23;6433:32:10;;;;;;;6147:325;;:::o;14:597:23:-;126:4;155:2;184;173:9;166:21;216:6;210:13;259:6;254:2;243:9;239:18;232:34;284:1;294:140;308:6;305:1;302:13;294:140;;;403:14;;;399:23;;393:30;369:17;;;388:2;365:26;358:66;323:10;;294:140;;;452:6;449:1;446:13;443:91;;;522:1;517:2;508:6;497:9;493:22;489:31;482:42;443:91;-1:-1:-1;595:2:23;574:15;-1:-1:-1;;570:29:23;555:45;;;;602:2;551:54;;14:597;-1:-1:-1;;;14:597:23:o;616:173::-;684:20;;-1:-1:-1;;;;;733:31:23;;723:42;;713:70;;779:1;776;769:12;713:70;616:173;;;:::o;794:254::-;862:6;870;923:2;911:9;902:7;898:23;894:32;891:52;;;939:1;936;929:12;891:52;962:29;981:9;962:29;:::i;:::-;952:39;1038:2;1023:18;;;;1010:32;;-1:-1:-1;;;794:254:23:o;1427:328::-;1504:6;1512;1520;1573:2;1561:9;1552:7;1548:23;1544:32;1541:52;;;1589:1;1586;1579:12;1541:52;1612:29;1631:9;1612:29;:::i;:::-;1602:39;;1660:38;1694:2;1683:9;1679:18;1660:38;:::i;:::-;1650:48;;1745:2;1734:9;1730:18;1717:32;1707:42;;1427:328;;;;;:::o;2131:186::-;2190:6;2243:2;2231:9;2222:7;2218:23;2214:32;2211:52;;;2259:1;2256;2249:12;2211:52;2282:29;2301:9;2282:29;:::i;:::-;2272:39;2131:186;-1:-1:-1;;;2131:186:23:o;2322:693::-;2433:6;2441;2449;2457;2465;2473;2481;2534:3;2522:9;2513:7;2509:23;2505:33;2502:53;;;2551:1;2548;2541:12;2502:53;2574:29;2593:9;2574:29;:::i;:::-;2564:39;;2622:38;2656:2;2645:9;2641:18;2622:38;:::i;:::-;2612:48;;2707:2;2696:9;2692:18;2679:32;2669:42;;2758:2;2747:9;2743:18;2730:32;2720:42;;2812:3;2801:9;2797:19;2784:33;2857:4;2850:5;2846:16;2839:5;2836:27;2826:55;;2877:1;2874;2867:12;2826:55;2322:693;;;;-1:-1:-1;2322:693:23;;;;2900:5;2952:3;2937:19;;2924:33;;-1:-1:-1;3004:3:23;2989:19;;;2976:33;;2322:693;-1:-1:-1;;2322:693:23:o;3020:260::-;3088:6;3096;3149:2;3137:9;3128:7;3124:23;3120:32;3117:52;;;3165:1;3162;3155:12;3117:52;3188:29;3207:9;3188:29;:::i;:::-;3178:39;;3236:38;3270:2;3259:9;3255:18;3236:38;:::i;:::-;3226:48;;3020:260;;;;;:::o;3285:380::-;3364:1;3360:12;;;;3407;;;3428:61;;3482:4;3474:6;3470:17;3460:27;;3428:61;3535:2;3527:6;3524:14;3504:18;3501:38;3498:161;;;3581:10;3576:3;3572:20;3569:1;3562:31;3616:4;3613:1;3606:15;3644:4;3641:1;3634:15;3498:161;;3285:380;;;:::o;3670:127::-;3731:10;3726:3;3722:20;3719:1;3712:31;3762:4;3759:1;3752:15;3786:4;3783:1;3776:15;3802:125;3842:4;3870:1;3867;3864:8;3861:34;;;3875:18;;:::i;:::-;-1:-1:-1;3912:9:23;;3802:125::o;6152:1104::-;6282:3;6311:1;6344:6;6338:13;6374:3;6396:1;6424:9;6420:2;6416:18;6406:28;;6484:2;6473:9;6469:18;6506;6496:61;;6550:4;6542:6;6538:17;6528:27;;6496:61;6576:2;6624;6616:6;6613:14;6593:18;6590:38;6587:165;;;-1:-1:-1;;;6651:33:23;;6707:4;6704:1;6697:15;6737:4;6658:3;6725:17;6587:165;6768:18;6795:104;;;;6913:1;6908:323;;;;6761:470;;6795:104;-1:-1:-1;;6828:24:23;;6816:37;;6873:16;;;;-1:-1:-1;6795:104:23;;6908:323;6099:1;6092:14;;;6136:4;6123:18;;7006:1;7020:165;7034:6;7031:1;7028:13;7020:165;;;7112:14;;7099:11;;;7092:35;7155:16;;;;7049:10;;7020:165;;;7024:3;;7214:6;7209:3;7205:16;7198:23;;6761:470;-1:-1:-1;7247:3:23;;6152:1104;-1:-1:-1;;;;;;;;6152:1104:23:o;7755:128::-;7795:3;7826:1;7822:6;7819:1;7816:13;7813:39;;;7832:18;;:::i;:::-;-1:-1:-1;7868:9:23;;7755:128::o","linkReferences":{},"immutableReferences":{"3499":[{"start":324,"length":32}],"3513":[{"start":1054,"length":32}],"3515":[{"start":1107,"length":32}]}},"methodIdentifiers":{"DOMAIN_SEPARATOR()":"3644e515","allowance(address,address)":"dd62ed3e","approve(address,uint256)":"095ea7b3","balanceOf(address)":"70a08231","decimals()":"313ce567","mint(address,uint256)":"40c10f19","name()":"06fdde03","nonces(address)":"7ecebe00","permit(address,address,uint256,uint256,uint8,bytes32,bytes32)":"d505accf","symbol()":"95d89b41","totalSupply()":"18160ddd","transfer(address,uint256)":"a9059cbb","transferFrom(address,address,uint256)":"23b872dd"}} ================================================ FILE: crates/cast/tests/fixtures/TestToken.sol ================================================ // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; contract TestToken { string public name = "Test Token"; string public symbol = "TEST"; uint8 public decimals = 18; uint256 public totalSupply; mapping(address => uint256) public balanceOf; mapping(address => mapping(address => uint256)) public allowance; event Transfer(address indexed from, address indexed to, uint256 value); event Approval(address indexed owner, address indexed spender, uint256 value); constructor() { totalSupply = 1000 * 10**decimals; balanceOf[msg.sender] = totalSupply; emit Transfer(address(0), msg.sender, totalSupply); } function transfer(address to, uint256 amount) external returns (bool) { require(balanceOf[msg.sender] >= amount, "Insufficient balance"); balanceOf[msg.sender] -= amount; balanceOf[to] += amount; emit Transfer(msg.sender, to, amount); return true; } function approve(address spender, uint256 amount) external returns (bool) { allowance[msg.sender][spender] = amount; emit Approval(msg.sender, spender, amount); return true; } function transferFrom(address from, address to, uint256 amount) external returns (bool) { require(balanceOf[from] >= amount, "Insufficient balance"); require(allowance[from][msg.sender] >= amount, "Insufficient allowance"); balanceOf[from] -= amount; balanceOf[to] += amount; allowance[from][msg.sender] -= amount; emit Transfer(from, to, amount); return true; } function mint(address to, uint256 amount) external { totalSupply += amount; balanceOf[to] += amount; emit Transfer(address(0), to, amount); } function burn(uint256 amount) external { uint256 balance = balanceOf[msg.sender]; amount = (amount > balance) ? balance : amount; totalSupply -= amount; balanceOf[msg.sender] -= amount; emit Transfer(msg.sender, address(0), amount); } } ================================================ FILE: crates/cast/tests/fixtures/cast_logs.stdout ================================================ - address: 0x95aD61b0a150d79219dCF64E1E6Cc01f0B64C4cE blockHash: 0x439b61565dacbc09a6d54378dff60d9b0400496d7a5a060cfdfdd899262f466c blockNumber: 12421182 data: 0x000000000000000000000000000000000000027fd7b375dda5ef932dac18d302 logIndex: 15 removed: false topics: [ 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef 0x000000000000000000000000ab5801a7d398351b8be11c439e05c5b3259aec9b 0x00000000000000000000000068a99f89e475a078645f4bac491360afe255dff1 ] transactionHash: 0xb65bcbb85c1633b0ab4e4886c3cd8eeaeb63edbb39cacdb9223fdcf4454fd2c7 transactionIndex: 8 ================================================ FILE: crates/cast/tests/fixtures/interface.json ================================================ [{"type":"constructor","inputs":[{"name":"_integrationManager","type":"address","internalType":"address"},{"name":"_addressListRegistry","type":"address","internalType":"address"},{"name":"_aTokenListId","type":"uint256","internalType":"uint256"},{"name":"_pool","type":"address","internalType":"address"},{"name":"_referralCode","type":"uint16","internalType":"uint16"}],"stateMutability":"nonpayable"},{"type":"function","name":"getIntegrationManager","inputs":[],"outputs":[{"name":"integrationManager_","type":"address","internalType":"address"}],"stateMutability":"view"},{"type":"function","name":"lend","inputs":[{"name":"_vaultProxy","type":"address","internalType":"address"},{"name":"","type":"bytes","internalType":"bytes"},{"name":"_assetData","type":"bytes","internalType":"bytes"}],"outputs":[],"stateMutability":"nonpayable"},{"type":"function","name":"parseAssetsForAction","inputs":[{"name":"","type":"address","internalType":"address"},{"name":"_selector","type":"bytes4","internalType":"bytes4"},{"name":"_actionData","type":"bytes","internalType":"bytes"}],"outputs":[{"name":"spendAssetsHandleType_","type":"uint8","internalType":"enum IIntegrationManager.SpendAssetsHandleType"},{"name":"spendAssets_","type":"address[]","internalType":"address[]"},{"name":"spendAssetAmounts_","type":"uint256[]","internalType":"uint256[]"},{"name":"incomingAssets_","type":"address[]","internalType":"address[]"},{"name":"minIncomingAssetAmounts_","type":"uint256[]","internalType":"uint256[]"}],"stateMutability":"view"},{"type":"function","name":"redeem","inputs":[{"name":"_vaultProxy","type":"address","internalType":"address"},{"name":"","type":"bytes","internalType":"bytes"},{"name":"_assetData","type":"bytes","internalType":"bytes"}],"outputs":[],"stateMutability":"nonpayable"}] ================================================ FILE: crates/cast/tests/fixtures/interface_inherited_struct.json ================================================ [{"type":"function","name":"test","inputs":[{"name":"param","type":"tuple","internalType":"struct IBase.TestStruct","components":[{"name":"asset","type":"address","internalType":"address"}]}],"outputs":[],"stateMutability":"nonpayable"}] ================================================ FILE: crates/cast/tests/fixtures/keystore/UTC--2022-10-30T06-51-20.130356000Z--560d246fcddc9ea98a8b032c9a2f474efb493c28 ================================================ {"address":"560d246fcddc9ea98a8b032c9a2f474efb493c28","crypto":{"cipher":"aes-128-ctr","ciphertext":"0b0012edfc7a1b22c7c616a78562807c363482490359ae23858c49d6a369b2ff","cipherparams":{"iv":"9d72960fe04dd987300e91c101c890b8"},"kdf":"scrypt","kdfparams":{"dklen":32,"n":262144,"p":1,"r":8,"salt":"f7d24eae5e746700a1fdc0e0886801b0e7aff7e298f9409f74355a5387827181"},"mac":"981cae42a43ce975b9ff13d19a99ae755ad3f6125c94a4da517c50067ca87f07"},"id":"852dbcfc-726a-416e-9321-29f9b5d7e2de","version":3} ================================================ FILE: crates/cast/tests/fixtures/keystore/UTC--2022-12-20T10-30-43.591916000Z--ec554aeafe75601aaab43bd4621a22284db566c2 ================================================ {"address":"ec554aeafe75601aaab43bd4621a22284db566c2","crypto":{"cipher":"aes-128-ctr","ciphertext":"f4e11a2667d97f42ad820f2aa735cd557ff608f47e2738d763a834b889611e18","cipherparams":{"iv":"99c3e2f1c98ccac50cb19f0a148e02ee"},"kdf":"scrypt","kdfparams":{"dklen":32,"n":262144,"p":1,"r":8,"salt":"81f3033ffcc9fec9939eaec246a4037d9bc0c47cfb33247c98132b7d477b7e64"},"mac":"13bb4eac9a8d8f72905bca0c6f947c440d990baa649b0697b2083f3ab57d2cc5"},"id":"279ce8c3-dd94-43a8-aa46-15c3a45adbd1","version":3} ================================================ FILE: crates/cast/tests/fixtures/keystore/password ================================================ this is keystore password ================================================ FILE: crates/cast/tests/fixtures/keystore/password-ec554 ================================================ keystorepassword ================================================ FILE: crates/cast/tests/fixtures/sign_typed_data.json ================================================ {"types":{"EIP712Domain":[{"name":"name","type":"string"},{"name":"version","type":"string"},{"name":"chainId","type":"uint256"},{"name":"verifyingContract","type":"address"}],"Message":[{"name":"data","type":"string"}]},"primaryType":"Message","domain":{"name":"example.metamask.io","version":"1","chainId":"1","verifyingContract":"0x0000000000000000000000000000000000000000"},"message":{"data":"Hello!"}} ================================================ FILE: crates/cast/tests/fixtures/storage_layout_complex.json ================================================ { "storage": [ { "astId": 3805, "contract": "contracts/vault/Vault.sol:Vault", "label": "_status", "offset": 0, "slot": "0", "type": "t_uint256" }, { "astId": 9499, "contract": "contracts/vault/Vault.sol:Vault", "label": "_generalPoolsBalances", "offset": 0, "slot": "1", "type": "t_mapping(t_bytes32,t_struct(IERC20ToBytes32Map)3177_storage)" }, { "astId": 716, "contract": "contracts/vault/Vault.sol:Vault", "label": "_nextNonce", "offset": 0, "slot": "2", "type": "t_mapping(t_address,t_uint256)" }, { "astId": 967, "contract": "contracts/vault/Vault.sol:Vault", "label": "_paused", "offset": 0, "slot": "3", "type": "t_bool" }, { "astId": 8639, "contract": "contracts/vault/Vault.sol:Vault", "label": "_authorizer", "offset": 1, "slot": "3", "type": "t_contract(IAuthorizer)11086" }, { "astId": 8645, "contract": "contracts/vault/Vault.sol:Vault", "label": "_approvedRelayers", "offset": 0, "slot": "4", "type": "t_mapping(t_address,t_mapping(t_address,t_bool))" }, { "astId": 5769, "contract": "contracts/vault/Vault.sol:Vault", "label": "_isPoolRegistered", "offset": 0, "slot": "5", "type": "t_mapping(t_bytes32,t_bool)" }, { "astId": 5771, "contract": "contracts/vault/Vault.sol:Vault", "label": "_nextPoolNonce", "offset": 0, "slot": "6", "type": "t_uint256" }, { "astId": 9915, "contract": "contracts/vault/Vault.sol:Vault", "label": "_minimalSwapInfoPoolsBalances", "offset": 0, "slot": "7", "type": "t_mapping(t_bytes32,t_mapping(t_contract(IERC20)3793,t_bytes32))" }, { "astId": 9919, "contract": "contracts/vault/Vault.sol:Vault", "label": "_minimalSwapInfoPoolsTokens", "offset": 0, "slot": "8", "type": "t_mapping(t_bytes32,t_struct(AddressSet)3520_storage)" }, { "astId": 10373, "contract": "contracts/vault/Vault.sol:Vault", "label": "_twoTokenPoolTokens", "offset": 0, "slot": "9", "type": "t_mapping(t_bytes32,t_struct(TwoTokenPoolTokens)10369_storage)" }, { "astId": 4007, "contract": "contracts/vault/Vault.sol:Vault", "label": "_poolAssetManagers", "offset": 0, "slot": "10", "type": "t_mapping(t_bytes32,t_mapping(t_contract(IERC20)3793,t_address))" }, { "astId": 8019, "contract": "contracts/vault/Vault.sol:Vault", "label": "_internalTokenBalance", "offset": 0, "slot": "11", "type": "t_mapping(t_address,t_mapping(t_contract(IERC20)3793,t_uint256))" } ], "types": { "t_address": { "encoding": "inplace", "label": "address", "numberOfBytes": "20" }, "t_array(t_address)dyn_storage": { "encoding": "dynamic_array", "label": "address[]", "numberOfBytes": "32", "base": "t_address" }, "t_bool": { "encoding": "inplace", "label": "bool", "numberOfBytes": "1" }, "t_bytes32": { "encoding": "inplace", "label": "bytes32", "numberOfBytes": "32" }, "t_contract(IAuthorizer)11086": { "encoding": "inplace", "label": "contract IAuthorizer", "numberOfBytes": "20" }, "t_contract(IERC20)3793": { "encoding": "inplace", "label": "contract IERC20", "numberOfBytes": "20" }, "t_mapping(t_address,t_bool)": { "encoding": "mapping", "key": "t_address", "label": "mapping(address => bool)", "numberOfBytes": "32", "value": "t_bool" }, "t_mapping(t_address,t_mapping(t_address,t_bool))": { "encoding": "mapping", "key": "t_address", "label": "mapping(address => mapping(address => bool))", "numberOfBytes": "32", "value": "t_mapping(t_address,t_bool)" }, "t_mapping(t_address,t_mapping(t_contract(IERC20)3793,t_uint256))": { "encoding": "mapping", "key": "t_address", "label": "mapping(address => mapping(contract IERC20 => uint256))", "numberOfBytes": "32", "value": "t_mapping(t_contract(IERC20)3793,t_uint256)" }, "t_mapping(t_address,t_uint256)": { "encoding": "mapping", "key": "t_address", "label": "mapping(address => uint256)", "numberOfBytes": "32", "value": "t_uint256" }, "t_mapping(t_bytes32,t_bool)": { "encoding": "mapping", "key": "t_bytes32", "label": "mapping(bytes32 => bool)", "numberOfBytes": "32", "value": "t_bool" }, "t_mapping(t_bytes32,t_mapping(t_contract(IERC20)3793,t_address))": { "encoding": "mapping", "key": "t_bytes32", "label": "mapping(bytes32 => mapping(contract IERC20 => address))", "numberOfBytes": "32", "value": "t_mapping(t_contract(IERC20)3793,t_address)" }, "t_mapping(t_bytes32,t_mapping(t_contract(IERC20)3793,t_bytes32))": { "encoding": "mapping", "key": "t_bytes32", "label": "mapping(bytes32 => mapping(contract IERC20 => bytes32))", "numberOfBytes": "32", "value": "t_mapping(t_contract(IERC20)3793,t_bytes32)" }, "t_mapping(t_bytes32,t_struct(AddressSet)3520_storage)": { "encoding": "mapping", "key": "t_bytes32", "label": "mapping(bytes32 => struct EnumerableSet.AddressSet)", "numberOfBytes": "32", "value": "t_struct(AddressSet)3520_storage" }, "t_mapping(t_bytes32,t_struct(IERC20ToBytes32Map)3177_storage)": { "encoding": "mapping", "key": "t_bytes32", "label": "mapping(bytes32 => struct EnumerableMap.IERC20ToBytes32Map)", "numberOfBytes": "32", "value": "t_struct(IERC20ToBytes32Map)3177_storage" }, "t_mapping(t_bytes32,t_struct(TwoTokenPoolBalances)10360_storage)": { "encoding": "mapping", "key": "t_bytes32", "label": "mapping(bytes32 => struct TwoTokenPoolsBalance.TwoTokenPoolBalances)", "numberOfBytes": "32", "value": "t_struct(TwoTokenPoolBalances)10360_storage" }, "t_mapping(t_bytes32,t_struct(TwoTokenPoolTokens)10369_storage)": { "encoding": "mapping", "key": "t_bytes32", "label": "mapping(bytes32 => struct TwoTokenPoolsBalance.TwoTokenPoolTokens)", "numberOfBytes": "32", "value": "t_struct(TwoTokenPoolTokens)10369_storage" }, "t_mapping(t_contract(IERC20)3793,t_address)": { "encoding": "mapping", "key": "t_contract(IERC20)3793", "label": "mapping(contract IERC20 => address)", "numberOfBytes": "32", "value": "t_address" }, "t_mapping(t_contract(IERC20)3793,t_bytes32)": { "encoding": "mapping", "key": "t_contract(IERC20)3793", "label": "mapping(contract IERC20 => bytes32)", "numberOfBytes": "32", "value": "t_bytes32" }, "t_mapping(t_contract(IERC20)3793,t_uint256)": { "encoding": "mapping", "key": "t_contract(IERC20)3793", "label": "mapping(contract IERC20 => uint256)", "numberOfBytes": "32", "value": "t_uint256" }, "t_mapping(t_uint256,t_struct(IERC20ToBytes32MapEntry)3166_storage)": { "encoding": "mapping", "key": "t_uint256", "label": "mapping(uint256 => struct EnumerableMap.IERC20ToBytes32MapEntry)", "numberOfBytes": "32", "value": "t_struct(IERC20ToBytes32MapEntry)3166_storage" }, "t_struct(AddressSet)3520_storage": { "encoding": "inplace", "label": "struct EnumerableSet.AddressSet", "numberOfBytes": "64", "members": [ { "astId": 3515, "contract": "contracts/vault/Vault.sol:Vault", "label": "_values", "offset": 0, "slot": "0", "type": "t_array(t_address)dyn_storage" }, { "astId": 3519, "contract": "contracts/vault/Vault.sol:Vault", "label": "_indexes", "offset": 0, "slot": "1", "type": "t_mapping(t_address,t_uint256)" } ] }, "t_struct(IERC20ToBytes32Map)3177_storage": { "encoding": "inplace", "label": "struct EnumerableMap.IERC20ToBytes32Map", "numberOfBytes": "96", "members": [ { "astId": 3168, "contract": "contracts/vault/Vault.sol:Vault", "label": "_length", "offset": 0, "slot": "0", "type": "t_uint256" }, { "astId": 3172, "contract": "contracts/vault/Vault.sol:Vault", "label": "_entries", "offset": 0, "slot": "1", "type": "t_mapping(t_uint256,t_struct(IERC20ToBytes32MapEntry)3166_storage)" }, { "astId": 3176, "contract": "contracts/vault/Vault.sol:Vault", "label": "_indexes", "offset": 0, "slot": "2", "type": "t_mapping(t_contract(IERC20)3793,t_uint256)" } ] }, "t_struct(IERC20ToBytes32MapEntry)3166_storage": { "encoding": "inplace", "label": "struct EnumerableMap.IERC20ToBytes32MapEntry", "numberOfBytes": "64", "members": [ { "astId": 3163, "contract": "contracts/vault/Vault.sol:Vault", "label": "_key", "offset": 0, "slot": "0", "type": "t_contract(IERC20)3793" }, { "astId": 3165, "contract": "contracts/vault/Vault.sol:Vault", "label": "_value", "offset": 0, "slot": "1", "type": "t_bytes32" } ] }, "t_struct(TwoTokenPoolBalances)10360_storage": { "encoding": "inplace", "label": "struct TwoTokenPoolsBalance.TwoTokenPoolBalances", "numberOfBytes": "64", "members": [ { "astId": 10357, "contract": "contracts/vault/Vault.sol:Vault", "label": "sharedCash", "offset": 0, "slot": "0", "type": "t_bytes32" }, { "astId": 10359, "contract": "contracts/vault/Vault.sol:Vault", "label": "sharedManaged", "offset": 0, "slot": "1", "type": "t_bytes32" } ] }, "t_struct(TwoTokenPoolTokens)10369_storage": { "encoding": "inplace", "label": "struct TwoTokenPoolsBalance.TwoTokenPoolTokens", "numberOfBytes": "96", "members": [ { "astId": 10362, "contract": "contracts/vault/Vault.sol:Vault", "label": "tokenA", "offset": 0, "slot": "0", "type": "t_contract(IERC20)3793" }, { "astId": 10364, "contract": "contracts/vault/Vault.sol:Vault", "label": "tokenB", "offset": 0, "slot": "1", "type": "t_contract(IERC20)3793" }, { "astId": 10368, "contract": "contracts/vault/Vault.sol:Vault", "label": "balances", "offset": 0, "slot": "2", "type": "t_mapping(t_bytes32,t_struct(TwoTokenPoolBalances)10360_storage)" } ] }, "t_uint256": { "encoding": "inplace", "label": "uint256", "numberOfBytes": "32" } }, "values": [ "0x0000000000000000000000000000000000000000000000000000000000000001", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000006048a8c631fb7e77eca533cf9c29784e482391e7", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x00000000000000000000000000000000000000000000000000000000000006e0", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000" ] } ================================================ FILE: crates/cast/tests/fixtures/storage_layout_simple.json ================================================ { "storage": [ { "astId": 7, "contract": "contracts/Create2Deployer.sol:Create2Deployer", "label": "_owner", "offset": 0, "slot": "0", "type": "t_address" }, { "astId": 122, "contract": "contracts/Create2Deployer.sol:Create2Deployer", "label": "_paused", "offset": 20, "slot": "0", "type": "t_bool" } ], "types": { "t_address": { "encoding": "inplace", "label": "address", "numberOfBytes": "20" }, "t_bool": { "encoding": "inplace", "label": "bool", "numberOfBytes": "1" } }, "values": [ "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000" ] } ================================================ FILE: crates/cheatcodes/Cargo.toml ================================================ [package] name = "foundry-cheatcodes" description = "Foundry cheatcodes definitions and implementations" version.workspace = true edition.workspace = true rust-version.workspace = true authors.workspace = true license.workspace = true homepage.workspace = true repository.workspace = true exclude.workspace = true [lints] workspace = true [dependencies] foundry-cheatcodes-spec.workspace = true foundry-common.workspace = true foundry-compilers.workspace = true foundry-config.workspace = true foundry-evm-core.workspace = true foundry-primitives.workspace = true foundry-evm-fuzz.workspace = true foundry-evm-traces.workspace = true foundry-wallets.workspace = true forge-script-sequence.workspace = true solar.workspace = true alloy-dyn-abi.workspace = true alloy-evm.workspace = true alloy-json-abi.workspace = true alloy-primitives.workspace = true alloy-genesis.workspace = true alloy-sol-types.workspace = true alloy-provider.workspace = true alloy-rpc-types = { workspace = true, features = ["k256"] } alloy-signer.workspace = true alloy-signer-local = { workspace = true, features = [ "mnemonic-all-languages", "keystore", ] } parking_lot.workspace = true 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.workspace = true eyre.workspace = true itertools.workspace = true jsonpath_lib.workspace = true k256.workspace = true memchr.workspace = true p256 = "0.13" ed25519-consensus = "2.1" ecdsa = "0.16" rand.workspace = true revm.workspace = true revm-inspectors.workspace = true semver.workspace = true serde_json.workspace = true thiserror.workspace = true toml = { workspace = true, features = ["preserve_order"] } tracing.workspace = true walkdir.workspace = true proptest.workspace = true serde.workspace = true ================================================ FILE: crates/cheatcodes/README.md ================================================ # foundry-cheatcodes Foundry cheatcodes definitions and implementations. ## Structure - [`assets/`](./assets/): JSON interface and specification - [`spec/`](./spec/src/lib.rs): Defines common traits and structs - [`src/`](./src/lib.rs): Rust implementations of the cheatcodes ## Overview All cheatcodes are defined in a single [`sol!`] macro call in [`spec/src/vm.rs`]. This, combined with the use of an internal [`Cheatcode`](../../crates/cheatcodes/spec/src/cheatcode.rs) derive macro, allows us to generate both the Rust definitions and the JSON specification of the cheatcodes. Cheatcodes are manually implemented through the `Cheatcode` trait, which is called in the `Cheatcodes` inspector implementation. See the [cheatcodes dev documentation](../../docs/dev/cheatcodes.md#cheatcodes-implementation) for more details. ### JSON interface The JSON interface is guaranteed to be stable, and can be used by third-party tools to interact with the Foundry cheatcodes externally. For example, here are some tools that make use of the JSON interface: - Internally, this is used to generate [a simple Solidity interface](../../testdata/cheats/Vm.sol) for testing - Used by [`forge-std`](https://github.com/foundry-rs/forge-std) to generate [user-friendly Solidity interfaces](https://github.com/foundry-rs/forge-std/blob/master/src/Vm.sol) - (WIP) Used by [the Foundry book](https://github.com/foundry-rs/book) to generate [the cheatcodes reference](https://book.getfoundry.sh/cheatcodes) - ... If you are making use of the JSON interface, please don't hesitate to open a PR to add your project to this list! ### Adding a new cheatcode Please see the [cheatcodes dev documentation](../../docs/dev/cheatcodes.md#adding-a-new-cheatcode) on how to add new cheatcodes. [`sol!`]: https://docs.rs/alloy-sol-macro/latest/alloy_sol_macro/macro.sol.html [`spec/src/vm.rs`]: ./spec/src/vm.rs ================================================ FILE: crates/cheatcodes/assets/cheatcodes.json ================================================ { "errors": [ { "name": "CheatcodeError", "description": "Error thrown by cheatcodes.", "declaration": "error CheatcodeError(string message);" } ], "events": [], "enums": [ { "name": "CallerMode", "description": "A modification applied to either `msg.sender` or `tx.origin`. Returned by `readCallers`.", "variants": [ { "name": "None", "description": "No caller modification is currently active." }, { "name": "Broadcast", "description": "A one time broadcast triggered by a `vm.broadcast()` call is currently active." }, { "name": "RecurrentBroadcast", "description": "A recurrent broadcast triggered by a `vm.startBroadcast()` call is currently active." }, { "name": "Prank", "description": "A one time prank triggered by a `vm.prank()` call is currently active." }, { "name": "RecurrentPrank", "description": "A recurrent prank triggered by a `vm.startPrank()` call is currently active." } ] }, { "name": "AccountAccessKind", "description": "The kind of account access that occurred.", "variants": [ { "name": "Call", "description": "The account was called." }, { "name": "DelegateCall", "description": "The account was called via delegatecall." }, { "name": "CallCode", "description": "The account was called via callcode." }, { "name": "StaticCall", "description": "The account was called via staticcall." }, { "name": "Create", "description": "The account was created." }, { "name": "SelfDestruct", "description": "The account was selfdestructed." }, { "name": "Resume", "description": "Synthetic access indicating the current context has resumed after a previous sub-context (AccountAccess)." }, { "name": "Balance", "description": "The account's balance was read." }, { "name": "Extcodesize", "description": "The account's codesize was read." }, { "name": "Extcodehash", "description": "The account's codehash was read." }, { "name": "Extcodecopy", "description": "The account's code was copied." } ] }, { "name": "ForgeContext", "description": "Forge execution contexts.", "variants": [ { "name": "TestGroup", "description": "Test group execution context (test, coverage or snapshot)." }, { "name": "Test", "description": "`forge test` execution context." }, { "name": "Coverage", "description": "`forge coverage` execution context." }, { "name": "Snapshot", "description": "`forge snapshot` execution context." }, { "name": "ScriptGroup", "description": "Script group execution context (dry run, broadcast or resume)." }, { "name": "ScriptDryRun", "description": "`forge script` execution context." }, { "name": "ScriptBroadcast", "description": "`forge script --broadcast` execution context." }, { "name": "ScriptResume", "description": "`forge script --resume` execution context." }, { "name": "Unknown", "description": "Unknown `forge` execution context." } ] }, { "name": "BroadcastTxType", "description": "The transaction type (`txType`) of the broadcast.", "variants": [ { "name": "Call", "description": "Represents a CALL broadcast tx." }, { "name": "Create", "description": "Represents a CREATE broadcast tx." }, { "name": "Create2", "description": "Represents a CREATE2 broadcast tx." } ] } ], "structs": [ { "name": "Log", "description": "An Ethereum log. Returned by `getRecordedLogs`.", "fields": [ { "name": "topics", "ty": "bytes32[]", "description": "The topics of the log, including the signature, if any." }, { "name": "data", "ty": "bytes", "description": "The raw data of the log." }, { "name": "emitter", "ty": "address", "description": "The address of the log's emitter." } ] }, { "name": "Rpc", "description": "An RPC URL and its alias. Returned by `rpcUrlStructs`.", "fields": [ { "name": "key", "ty": "string", "description": "The alias of the RPC URL." }, { "name": "url", "ty": "string", "description": "The RPC URL." } ] }, { "name": "EthGetLogs", "description": "An RPC log object. Returned by `eth_getLogs`.", "fields": [ { "name": "emitter", "ty": "address", "description": "The address of the log's emitter." }, { "name": "topics", "ty": "bytes32[]", "description": "The topics of the log, including the signature, if any." }, { "name": "data", "ty": "bytes", "description": "The raw data of the log." }, { "name": "blockHash", "ty": "bytes32", "description": "The block hash." }, { "name": "blockNumber", "ty": "uint64", "description": "The block number." }, { "name": "transactionHash", "ty": "bytes32", "description": "The transaction hash." }, { "name": "transactionIndex", "ty": "uint64", "description": "The transaction index in the block." }, { "name": "logIndex", "ty": "uint256", "description": "The log index." }, { "name": "removed", "ty": "bool", "description": "Whether the log was removed." } ] }, { "name": "DirEntry", "description": "A single entry in a directory listing. Returned by `readDir`.", "fields": [ { "name": "errorMessage", "ty": "string", "description": "The error message, if any." }, { "name": "path", "ty": "string", "description": "The path of the entry." }, { "name": "depth", "ty": "uint64", "description": "The depth of the entry." }, { "name": "isDir", "ty": "bool", "description": "Whether the entry is a directory." }, { "name": "isSymlink", "ty": "bool", "description": "Whether the entry is a symlink." } ] }, { "name": "FsMetadata", "description": "Metadata information about a file.\n This structure is returned from the `fsMetadata` function and represents known\n metadata about a file such as its permissions, size, modification\n times, etc.", "fields": [ { "name": "isDir", "ty": "bool", "description": "True if this metadata is for a directory." }, { "name": "isSymlink", "ty": "bool", "description": "True if this metadata is for a symlink." }, { "name": "length", "ty": "uint256", "description": "The size of the file, in bytes, this metadata is for." }, { "name": "readOnly", "ty": "bool", "description": "True if this metadata is for a readonly (unwritable) file." }, { "name": "modified", "ty": "uint256", "description": "The last modification time listed in this metadata." }, { "name": "accessed", "ty": "uint256", "description": "The last access time of this metadata." }, { "name": "created", "ty": "uint256", "description": "The creation time listed in this metadata." } ] }, { "name": "Wallet", "description": "A wallet with a public and private key.", "fields": [ { "name": "addr", "ty": "address", "description": "The wallet's address." }, { "name": "publicKeyX", "ty": "uint256", "description": "The wallet's public key `X`." }, { "name": "publicKeyY", "ty": "uint256", "description": "The wallet's public key `Y`." }, { "name": "privateKey", "ty": "uint256", "description": "The wallet's private key." } ] }, { "name": "FfiResult", "description": "The result of a `tryFfi` call.", "fields": [ { "name": "exitCode", "ty": "int32", "description": "The exit code of the call." }, { "name": "stdout", "ty": "bytes", "description": "The optionally hex-decoded `stdout` data." }, { "name": "stderr", "ty": "bytes", "description": "The `stderr` data." } ] }, { "name": "ChainInfo", "description": "Information on the chain and fork.", "fields": [ { "name": "forkId", "ty": "uint256", "description": "The fork identifier. Set to zero if no fork is active." }, { "name": "chainId", "ty": "uint256", "description": "The chain ID of the current fork." } ] }, { "name": "Chain", "description": "Information about a blockchain.", "fields": [ { "name": "name", "ty": "string", "description": "The chain name." }, { "name": "chainId", "ty": "uint256", "description": "The chain's Chain ID." }, { "name": "chainAlias", "ty": "string", "description": "The chain's alias. (i.e. what gets specified in `foundry.toml`)." }, { "name": "rpcUrl", "ty": "string", "description": "A default RPC endpoint for this chain." } ] }, { "name": "AccountAccess", "description": "The result of a `stopAndReturnStateDiff` call.", "fields": [ { "name": "chainInfo", "ty": "ChainInfo", "description": "The chain and fork the access occurred." }, { "name": "kind", "ty": "AccountAccessKind", "description": "The kind of account access that determines what the account is.\n If kind is Call, DelegateCall, StaticCall or CallCode, then the account is the callee.\n If kind is Create, then the account is the newly created account.\n If kind is SelfDestruct, then the account is the selfdestruct recipient.\n If kind is a Resume, then account represents a account context that has resumed." }, { "name": "account", "ty": "address", "description": "The account that was accessed.\n It's either the account created, callee or a selfdestruct recipient for CREATE, CALL or SELFDESTRUCT." }, { "name": "accessor", "ty": "address", "description": "What accessed the account." }, { "name": "initialized", "ty": "bool", "description": "If the account was initialized or empty prior to the access.\n An account is considered initialized if it has code, a\n non-zero nonce, or a non-zero balance." }, { "name": "oldBalance", "ty": "uint256", "description": "The previous balance of the accessed account." }, { "name": "newBalance", "ty": "uint256", "description": "The potential new balance of the accessed account.\n That is, all balance changes are recorded here, even if reverts occurred." }, { "name": "deployedCode", "ty": "bytes", "description": "Code of the account deployed by CREATE." }, { "name": "value", "ty": "uint256", "description": "Value passed along with the account access" }, { "name": "data", "ty": "bytes", "description": "Input data provided to the CREATE or CALL" }, { "name": "reverted", "ty": "bool", "description": "If this access reverted in either the current or parent context." }, { "name": "storageAccesses", "ty": "StorageAccess[]", "description": "An ordered list of storage accesses made during an account access operation." }, { "name": "depth", "ty": "uint64", "description": "Call depth traversed during the recording of state differences" }, { "name": "oldNonce", "ty": "uint64", "description": "The previous nonce of the accessed account." }, { "name": "newNonce", "ty": "uint64", "description": "The new nonce of the accessed account." } ] }, { "name": "StorageAccess", "description": "The storage accessed during an `AccountAccess`.", "fields": [ { "name": "account", "ty": "address", "description": "The account whose storage was accessed." }, { "name": "slot", "ty": "bytes32", "description": "The slot that was accessed." }, { "name": "isWrite", "ty": "bool", "description": "If the access was a write." }, { "name": "previousValue", "ty": "bytes32", "description": "The previous value of the slot." }, { "name": "newValue", "ty": "bytes32", "description": "The new value of the slot." }, { "name": "reverted", "ty": "bool", "description": "If the access was reverted." } ] }, { "name": "Gas", "description": "Gas used. Returned by `lastCallGas`.", "fields": [ { "name": "gasLimit", "ty": "uint64", "description": "The gas limit of the call." }, { "name": "gasTotalUsed", "ty": "uint64", "description": "The total gas used." }, { "name": "gasMemoryUsed", "ty": "uint64", "description": "DEPRECATED: The amount of gas used for memory expansion. Ref: " }, { "name": "gasRefunded", "ty": "int64", "description": "The amount of gas refunded." }, { "name": "gasRemaining", "ty": "uint64", "description": "The amount of gas remaining." } ] }, { "name": "DebugStep", "description": "The result of the `stopDebugTraceRecording` call", "fields": [ { "name": "stack", "ty": "uint256[]", "description": "The stack before executing the step of the run.\n stack\\[0\\] represents the top of the stack.\n and only stack data relevant to the opcode execution is contained." }, { "name": "memoryInput", "ty": "bytes", "description": "The memory input data before executing the step of the run.\n only input data relevant to the opcode execution is contained.\n e.g. for MLOAD, it will have memory\\[offset:offset+32\\] copied here.\n the offset value can be get by the stack data." }, { "name": "opcode", "ty": "uint8", "description": "The opcode that was accessed." }, { "name": "depth", "ty": "uint64", "description": "The call depth of the step." }, { "name": "isOutOfGas", "ty": "bool", "description": "Whether the call end up with out of gas error." }, { "name": "contractAddr", "ty": "address", "description": "The contract address where the opcode is running" } ] }, { "name": "BroadcastTxSummary", "description": "Represents a transaction's broadcast details.", "fields": [ { "name": "txHash", "ty": "bytes32", "description": "The hash of the transaction that was broadcasted" }, { "name": "txType", "ty": "BroadcastTxType", "description": "Represent the type of transaction among CALL, CREATE, CREATE2" }, { "name": "contractAddress", "ty": "address", "description": "The address of the contract that was called or created.\n This is address of the contract that is created if the txType is CREATE or CREATE2." }, { "name": "blockNumber", "ty": "uint64", "description": "The block number the transaction landed in." }, { "name": "success", "ty": "bool", "description": "Status of the transaction, retrieved from the transaction receipt." } ] }, { "name": "SignedDelegation", "description": "Holds a signed EIP-7702 authorization for an authority account to delegate to an implementation.", "fields": [ { "name": "v", "ty": "uint8", "description": "The y-parity of the recovered secp256k1 signature (0 or 1)." }, { "name": "r", "ty": "bytes32", "description": "First 32 bytes of the signature." }, { "name": "s", "ty": "bytes32", "description": "Second 32 bytes of the signature." }, { "name": "nonce", "ty": "uint64", "description": "The current nonce of the authority account at signing time.\n Used to ensure signature can't be replayed after account nonce changes." }, { "name": "implementation", "ty": "address", "description": "Address of the contract implementation that will be delegated to.\n Gets encoded into delegation code: 0xef0100 || implementation." } ] }, { "name": "PotentialRevert", "description": "Represents a \"potential\" revert reason from a single subsequent call when using `vm.assumeNoReverts`.\n Reverts that match will result in a FOUNDRY::ASSUME rejection, whereas unmatched reverts will be surfaced\n as normal.", "fields": [ { "name": "reverter", "ty": "address", "description": "The allowed origin of the revert opcode; address(0) allows reverts from any address" }, { "name": "partialMatch", "ty": "bool", "description": "When true, only matches on the beginning of the revert data, otherwise, matches on entire revert data" }, { "name": "revertData", "ty": "bytes", "description": "The data to use to match encountered reverts" } ] }, { "name": "AccessListItem", "description": "An EIP-2930 access list item.", "fields": [ { "name": "target", "ty": "address", "description": "The address to be added in access list." }, { "name": "storageKeys", "ty": "bytes32[]", "description": "The storage keys to be added in access list." } ] } ], "cheatcodes": [ { "func": { "id": "_expectCheatcodeRevert_0", "description": "Expects an error on next cheatcode call with any revert data.", "declaration": "function _expectCheatcodeRevert() external;", "visibility": "external", "mutability": "", "signature": "_expectCheatcodeRevert()", "selector": "0x79a4f48a", "selectorBytes": [ 121, 164, 244, 138 ] }, "group": "testing", "status": "internal", "safety": "unsafe" }, { "func": { "id": "_expectCheatcodeRevert_1", "description": "Expects an error on next cheatcode call that starts with the revert data.", "declaration": "function _expectCheatcodeRevert(bytes4 revertData) external;", "visibility": "external", "mutability": "", "signature": "_expectCheatcodeRevert(bytes4)", "selector": "0x884cb0ae", "selectorBytes": [ 136, 76, 176, 174 ] }, "group": "testing", "status": "internal", "safety": "unsafe" }, { "func": { "id": "_expectCheatcodeRevert_2", "description": "Expects an error on next cheatcode call that exactly matches the revert data.", "declaration": "function _expectCheatcodeRevert(bytes calldata revertData) external;", "visibility": "external", "mutability": "", "signature": "_expectCheatcodeRevert(bytes)", "selector": "0x7843b44d", "selectorBytes": [ 120, 67, 180, 77 ] }, "group": "testing", "status": "internal", "safety": "unsafe" }, { "func": { "id": "accessList", "description": "Utility cheatcode to set an EIP-2930 access list for all subsequent transactions.", "declaration": "function accessList(AccessListItem[] calldata access) external;", "visibility": "external", "mutability": "", "signature": "accessList((address,bytes32[])[])", "selector": "0x743e4cb7", "selectorBytes": [ 116, 62, 76, 183 ] }, "group": "evm", "status": "stable", "safety": "unsafe" }, { "func": { "id": "accesses", "description": "Gets all accessed reads and write slot from a `vm.record` session, for a given address.", "declaration": "function accesses(address target) external view returns (bytes32[] memory readSlots, bytes32[] memory writeSlots);", "visibility": "external", "mutability": "view", "signature": "accesses(address)", "selector": "0x65bc9481", "selectorBytes": [ 101, 188, 148, 129 ] }, "group": "evm", "status": "stable", "safety": "safe" }, { "func": { "id": "activeFork", "description": "Returns the identifier of the currently active fork. Reverts if no fork is currently active.", "declaration": "function activeFork() external view returns (uint256 forkId);", "visibility": "external", "mutability": "view", "signature": "activeFork()", "selector": "0x2f103f22", "selectorBytes": [ 47, 16, 63, 34 ] }, "group": "evm", "status": "stable", "safety": "unsafe" }, { "func": { "id": "addr", "description": "Gets the address for a given private key.", "declaration": "function addr(uint256 privateKey) external pure returns (address keyAddr);", "visibility": "external", "mutability": "pure", "signature": "addr(uint256)", "selector": "0xffa18649", "selectorBytes": [ 255, 161, 134, 73 ] }, "group": "evm", "status": "stable", "safety": "safe" }, { "func": { "id": "allowCheatcodes", "description": "In forking mode, explicitly grant the given address cheatcode access.", "declaration": "function allowCheatcodes(address account) external;", "visibility": "external", "mutability": "", "signature": "allowCheatcodes(address)", "selector": "0xea060291", "selectorBytes": [ 234, 6, 2, 145 ] }, "group": "evm", "status": "stable", "safety": "unsafe" }, { "func": { "id": "assertApproxEqAbsDecimal_0", "description": "Compares two `uint256` values. Expects difference to be less than or equal to `maxDelta`.\nFormats values with decimals in failure message.", "declaration": "function assertApproxEqAbsDecimal(uint256 left, uint256 right, uint256 maxDelta, uint256 decimals) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertApproxEqAbsDecimal(uint256,uint256,uint256,uint256)", "selector": "0x045c55ce", "selectorBytes": [ 4, 92, 85, 206 ] }, "group": "testing", "status": "stable", "safety": "safe" }, { "func": { "id": "assertApproxEqAbsDecimal_1", "description": "Compares two `uint256` values. Expects difference to be less than or equal to `maxDelta`.\nFormats values with decimals in failure message. Includes error message into revert string on failure.", "declaration": "function assertApproxEqAbsDecimal(uint256 left, uint256 right, uint256 maxDelta, uint256 decimals, string calldata error) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertApproxEqAbsDecimal(uint256,uint256,uint256,uint256,string)", "selector": "0x60429eb2", "selectorBytes": [ 96, 66, 158, 178 ] }, "group": "testing", "status": "stable", "safety": "safe" }, { "func": { "id": "assertApproxEqAbsDecimal_2", "description": "Compares two `int256` values. Expects difference to be less than or equal to `maxDelta`.\nFormats values with decimals in failure message.", "declaration": "function assertApproxEqAbsDecimal(int256 left, int256 right, uint256 maxDelta, uint256 decimals) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertApproxEqAbsDecimal(int256,int256,uint256,uint256)", "selector": "0x3d5bc8bc", "selectorBytes": [ 61, 91, 200, 188 ] }, "group": "testing", "status": "stable", "safety": "safe" }, { "func": { "id": "assertApproxEqAbsDecimal_3", "description": "Compares two `int256` values. Expects difference to be less than or equal to `maxDelta`.\nFormats values with decimals in failure message. Includes error message into revert string on failure.", "declaration": "function assertApproxEqAbsDecimal(int256 left, int256 right, uint256 maxDelta, uint256 decimals, string calldata error) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertApproxEqAbsDecimal(int256,int256,uint256,uint256,string)", "selector": "0x6a5066d4", "selectorBytes": [ 106, 80, 102, 212 ] }, "group": "testing", "status": "stable", "safety": "safe" }, { "func": { "id": "assertApproxEqAbs_0", "description": "Compares two `uint256` values. Expects difference to be less than or equal to `maxDelta`.", "declaration": "function assertApproxEqAbs(uint256 left, uint256 right, uint256 maxDelta) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertApproxEqAbs(uint256,uint256,uint256)", "selector": "0x16d207c6", "selectorBytes": [ 22, 210, 7, 198 ] }, "group": "testing", "status": "stable", "safety": "safe" }, { "func": { "id": "assertApproxEqAbs_1", "description": "Compares two `uint256` values. Expects difference to be less than or equal to `maxDelta`.\nIncludes error message into revert string on failure.", "declaration": "function assertApproxEqAbs(uint256 left, uint256 right, uint256 maxDelta, string calldata error) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertApproxEqAbs(uint256,uint256,uint256,string)", "selector": "0xf710b062", "selectorBytes": [ 247, 16, 176, 98 ] }, "group": "testing", "status": "stable", "safety": "safe" }, { "func": { "id": "assertApproxEqAbs_2", "description": "Compares two `int256` values. Expects difference to be less than or equal to `maxDelta`.", "declaration": "function assertApproxEqAbs(int256 left, int256 right, uint256 maxDelta) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertApproxEqAbs(int256,int256,uint256)", "selector": "0x240f839d", "selectorBytes": [ 36, 15, 131, 157 ] }, "group": "testing", "status": "stable", "safety": "safe" }, { "func": { "id": "assertApproxEqAbs_3", "description": "Compares two `int256` values. Expects difference to be less than or equal to `maxDelta`.\nIncludes error message into revert string on failure.", "declaration": "function assertApproxEqAbs(int256 left, int256 right, uint256 maxDelta, string calldata error) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertApproxEqAbs(int256,int256,uint256,string)", "selector": "0x8289e621", "selectorBytes": [ 130, 137, 230, 33 ] }, "group": "testing", "status": "stable", "safety": "safe" }, { "func": { "id": "assertApproxEqRelDecimal_0", "description": "Compares two `uint256` values. Expects relative difference in percents to be less than or equal to `maxPercentDelta`.\n`maxPercentDelta` is an 18 decimal fixed point number, where 1e18 == 100%\nFormats values with decimals in failure message.", "declaration": "function assertApproxEqRelDecimal(uint256 left, uint256 right, uint256 maxPercentDelta, uint256 decimals) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertApproxEqRelDecimal(uint256,uint256,uint256,uint256)", "selector": "0x21ed2977", "selectorBytes": [ 33, 237, 41, 119 ] }, "group": "testing", "status": "stable", "safety": "safe" }, { "func": { "id": "assertApproxEqRelDecimal_1", "description": "Compares two `uint256` values. Expects relative difference in percents to be less than or equal to `maxPercentDelta`.\n`maxPercentDelta` is an 18 decimal fixed point number, where 1e18 == 100%\nFormats values with decimals in failure message. Includes error message into revert string on failure.", "declaration": "function assertApproxEqRelDecimal(uint256 left, uint256 right, uint256 maxPercentDelta, uint256 decimals, string calldata error) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertApproxEqRelDecimal(uint256,uint256,uint256,uint256,string)", "selector": "0x82d6c8fd", "selectorBytes": [ 130, 214, 200, 253 ] }, "group": "testing", "status": "stable", "safety": "safe" }, { "func": { "id": "assertApproxEqRelDecimal_2", "description": "Compares two `int256` values. Expects relative difference in percents to be less than or equal to `maxPercentDelta`.\n`maxPercentDelta` is an 18 decimal fixed point number, where 1e18 == 100%\nFormats values with decimals in failure message.", "declaration": "function assertApproxEqRelDecimal(int256 left, int256 right, uint256 maxPercentDelta, uint256 decimals) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertApproxEqRelDecimal(int256,int256,uint256,uint256)", "selector": "0xabbf21cc", "selectorBytes": [ 171, 191, 33, 204 ] }, "group": "testing", "status": "stable", "safety": "safe" }, { "func": { "id": "assertApproxEqRelDecimal_3", "description": "Compares two `int256` values. Expects relative difference in percents to be less than or equal to `maxPercentDelta`.\n`maxPercentDelta` is an 18 decimal fixed point number, where 1e18 == 100%\nFormats values with decimals in failure message. Includes error message into revert string on failure.", "declaration": "function assertApproxEqRelDecimal(int256 left, int256 right, uint256 maxPercentDelta, uint256 decimals, string calldata error) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertApproxEqRelDecimal(int256,int256,uint256,uint256,string)", "selector": "0xfccc11c4", "selectorBytes": [ 252, 204, 17, 196 ] }, "group": "testing", "status": "stable", "safety": "safe" }, { "func": { "id": "assertApproxEqRel_0", "description": "Compares two `uint256` values. Expects relative difference in percents to be less than or equal to `maxPercentDelta`.\n`maxPercentDelta` is an 18 decimal fixed point number, where 1e18 == 100%", "declaration": "function assertApproxEqRel(uint256 left, uint256 right, uint256 maxPercentDelta) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertApproxEqRel(uint256,uint256,uint256)", "selector": "0x8cf25ef4", "selectorBytes": [ 140, 242, 94, 244 ] }, "group": "testing", "status": "stable", "safety": "safe" }, { "func": { "id": "assertApproxEqRel_1", "description": "Compares two `uint256` values. Expects relative difference in percents to be less than or equal to `maxPercentDelta`.\n`maxPercentDelta` is an 18 decimal fixed point number, where 1e18 == 100%\nIncludes error message into revert string on failure.", "declaration": "function assertApproxEqRel(uint256 left, uint256 right, uint256 maxPercentDelta, string calldata error) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertApproxEqRel(uint256,uint256,uint256,string)", "selector": "0x1ecb7d33", "selectorBytes": [ 30, 203, 125, 51 ] }, "group": "testing", "status": "stable", "safety": "safe" }, { "func": { "id": "assertApproxEqRel_2", "description": "Compares two `int256` values. Expects relative difference in percents to be less than or equal to `maxPercentDelta`.\n`maxPercentDelta` is an 18 decimal fixed point number, where 1e18 == 100%", "declaration": "function assertApproxEqRel(int256 left, int256 right, uint256 maxPercentDelta) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertApproxEqRel(int256,int256,uint256)", "selector": "0xfea2d14f", "selectorBytes": [ 254, 162, 209, 79 ] }, "group": "testing", "status": "stable", "safety": "safe" }, { "func": { "id": "assertApproxEqRel_3", "description": "Compares two `int256` values. Expects relative difference in percents to be less than or equal to `maxPercentDelta`.\n`maxPercentDelta` is an 18 decimal fixed point number, where 1e18 == 100%\nIncludes error message into revert string on failure.", "declaration": "function assertApproxEqRel(int256 left, int256 right, uint256 maxPercentDelta, string calldata error) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertApproxEqRel(int256,int256,uint256,string)", "selector": "0xef277d72", "selectorBytes": [ 239, 39, 125, 114 ] }, "group": "testing", "status": "stable", "safety": "safe" }, { "func": { "id": "assertEqDecimal_0", "description": "Asserts that two `uint256` values are equal, formatting them with decimals in failure message.", "declaration": "function assertEqDecimal(uint256 left, uint256 right, uint256 decimals) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertEqDecimal(uint256,uint256,uint256)", "selector": "0x27af7d9c", "selectorBytes": [ 39, 175, 125, 156 ] }, "group": "testing", "status": "stable", "safety": "safe" }, { "func": { "id": "assertEqDecimal_1", "description": "Asserts that two `uint256` values are equal, formatting them with decimals in failure message.\nIncludes error message into revert string on failure.", "declaration": "function assertEqDecimal(uint256 left, uint256 right, uint256 decimals, string calldata error) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertEqDecimal(uint256,uint256,uint256,string)", "selector": "0xd0cbbdef", "selectorBytes": [ 208, 203, 189, 239 ] }, "group": "testing", "status": "stable", "safety": "safe" }, { "func": { "id": "assertEqDecimal_2", "description": "Asserts that two `int256` values are equal, formatting them with decimals in failure message.", "declaration": "function assertEqDecimal(int256 left, int256 right, uint256 decimals) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertEqDecimal(int256,int256,uint256)", "selector": "0x48016c04", "selectorBytes": [ 72, 1, 108, 4 ] }, "group": "testing", "status": "stable", "safety": "safe" }, { "func": { "id": "assertEqDecimal_3", "description": "Asserts that two `int256` values are equal, formatting them with decimals in failure message.\nIncludes error message into revert string on failure.", "declaration": "function assertEqDecimal(int256 left, int256 right, uint256 decimals, string calldata error) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertEqDecimal(int256,int256,uint256,string)", "selector": "0x7e77b0c5", "selectorBytes": [ 126, 119, 176, 197 ] }, "group": "testing", "status": "stable", "safety": "safe" }, { "func": { "id": "assertEq_0", "description": "Asserts that two `bool` values are equal.", "declaration": "function assertEq(bool left, bool right) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertEq(bool,bool)", "selector": "0xf7fe3477", "selectorBytes": [ 247, 254, 52, 119 ] }, "group": "testing", "status": "stable", "safety": "safe" }, { "func": { "id": "assertEq_1", "description": "Asserts that two `bool` values are equal and includes error message into revert string on failure.", "declaration": "function assertEq(bool left, bool right, string calldata error) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertEq(bool,bool,string)", "selector": "0x4db19e7e", "selectorBytes": [ 77, 177, 158, 126 ] }, "group": "testing", "status": "stable", "safety": "safe" }, { "func": { "id": "assertEq_10", "description": "Asserts that two `string` values are equal.", "declaration": "function assertEq(string calldata left, string calldata right) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertEq(string,string)", "selector": "0xf320d963", "selectorBytes": [ 243, 32, 217, 99 ] }, "group": "testing", "status": "stable", "safety": "safe" }, { "func": { "id": "assertEq_11", "description": "Asserts that two `string` values are equal and includes error message into revert string on failure.", "declaration": "function assertEq(string calldata left, string calldata right, string calldata error) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertEq(string,string,string)", "selector": "0x36f656d8", "selectorBytes": [ 54, 246, 86, 216 ] }, "group": "testing", "status": "stable", "safety": "safe" }, { "func": { "id": "assertEq_12", "description": "Asserts that two `bytes` values are equal.", "declaration": "function assertEq(bytes calldata left, bytes calldata right) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertEq(bytes,bytes)", "selector": "0x97624631", "selectorBytes": [ 151, 98, 70, 49 ] }, "group": "testing", "status": "stable", "safety": "safe" }, { "func": { "id": "assertEq_13", "description": "Asserts that two `bytes` values are equal and includes error message into revert string on failure.", "declaration": "function assertEq(bytes calldata left, bytes calldata right, string calldata error) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertEq(bytes,bytes,string)", "selector": "0xe24fed00", "selectorBytes": [ 226, 79, 237, 0 ] }, "group": "testing", "status": "stable", "safety": "safe" }, { "func": { "id": "assertEq_14", "description": "Asserts that two arrays of `bool` values are equal.", "declaration": "function assertEq(bool[] calldata left, bool[] calldata right) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertEq(bool[],bool[])", "selector": "0x707df785", "selectorBytes": [ 112, 125, 247, 133 ] }, "group": "testing", "status": "stable", "safety": "safe" }, { "func": { "id": "assertEq_15", "description": "Asserts that two arrays of `bool` values are equal and includes error message into revert string on failure.", "declaration": "function assertEq(bool[] calldata left, bool[] calldata right, string calldata error) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertEq(bool[],bool[],string)", "selector": "0xe48a8f8d", "selectorBytes": [ 228, 138, 143, 141 ] }, "group": "testing", "status": "stable", "safety": "safe" }, { "func": { "id": "assertEq_16", "description": "Asserts that two arrays of `uint256 values are equal.", "declaration": "function assertEq(uint256[] calldata left, uint256[] calldata right) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertEq(uint256[],uint256[])", "selector": "0x975d5a12", "selectorBytes": [ 151, 93, 90, 18 ] }, "group": "testing", "status": "stable", "safety": "safe" }, { "func": { "id": "assertEq_17", "description": "Asserts that two arrays of `uint256` values are equal and includes error message into revert string on failure.", "declaration": "function assertEq(uint256[] calldata left, uint256[] calldata right, string calldata error) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertEq(uint256[],uint256[],string)", "selector": "0x5d18c73a", "selectorBytes": [ 93, 24, 199, 58 ] }, "group": "testing", "status": "stable", "safety": "safe" }, { "func": { "id": "assertEq_18", "description": "Asserts that two arrays of `int256` values are equal.", "declaration": "function assertEq(int256[] calldata left, int256[] calldata right) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertEq(int256[],int256[])", "selector": "0x711043ac", "selectorBytes": [ 113, 16, 67, 172 ] }, "group": "testing", "status": "stable", "safety": "safe" }, { "func": { "id": "assertEq_19", "description": "Asserts that two arrays of `int256` values are equal and includes error message into revert string on failure.", "declaration": "function assertEq(int256[] calldata left, int256[] calldata right, string calldata error) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertEq(int256[],int256[],string)", "selector": "0x191f1b30", "selectorBytes": [ 25, 31, 27, 48 ] }, "group": "testing", "status": "stable", "safety": "safe" }, { "func": { "id": "assertEq_2", "description": "Asserts that two `uint256` values are equal.", "declaration": "function assertEq(uint256 left, uint256 right) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertEq(uint256,uint256)", "selector": "0x98296c54", "selectorBytes": [ 152, 41, 108, 84 ] }, "group": "testing", "status": "stable", "safety": "safe" }, { "func": { "id": "assertEq_20", "description": "Asserts that two arrays of `address` values are equal.", "declaration": "function assertEq(address[] calldata left, address[] calldata right) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertEq(address[],address[])", "selector": "0x3868ac34", "selectorBytes": [ 56, 104, 172, 52 ] }, "group": "testing", "status": "stable", "safety": "safe" }, { "func": { "id": "assertEq_21", "description": "Asserts that two arrays of `address` values are equal and includes error message into revert string on failure.", "declaration": "function assertEq(address[] calldata left, address[] calldata right, string calldata error) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertEq(address[],address[],string)", "selector": "0x3e9173c5", "selectorBytes": [ 62, 145, 115, 197 ] }, "group": "testing", "status": "stable", "safety": "safe" }, { "func": { "id": "assertEq_22", "description": "Asserts that two arrays of `bytes32` values are equal.", "declaration": "function assertEq(bytes32[] calldata left, bytes32[] calldata right) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertEq(bytes32[],bytes32[])", "selector": "0x0cc9ee84", "selectorBytes": [ 12, 201, 238, 132 ] }, "group": "testing", "status": "stable", "safety": "safe" }, { "func": { "id": "assertEq_23", "description": "Asserts that two arrays of `bytes32` values are equal and includes error message into revert string on failure.", "declaration": "function assertEq(bytes32[] calldata left, bytes32[] calldata right, string calldata error) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertEq(bytes32[],bytes32[],string)", "selector": "0xe03e9177", "selectorBytes": [ 224, 62, 145, 119 ] }, "group": "testing", "status": "stable", "safety": "safe" }, { "func": { "id": "assertEq_24", "description": "Asserts that two arrays of `string` values are equal.", "declaration": "function assertEq(string[] calldata left, string[] calldata right) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertEq(string[],string[])", "selector": "0xcf1c049c", "selectorBytes": [ 207, 28, 4, 156 ] }, "group": "testing", "status": "stable", "safety": "safe" }, { "func": { "id": "assertEq_25", "description": "Asserts that two arrays of `string` values are equal and includes error message into revert string on failure.", "declaration": "function assertEq(string[] calldata left, string[] calldata right, string calldata error) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertEq(string[],string[],string)", "selector": "0xeff6b27d", "selectorBytes": [ 239, 246, 178, 125 ] }, "group": "testing", "status": "stable", "safety": "safe" }, { "func": { "id": "assertEq_26", "description": "Asserts that two arrays of `bytes` values are equal.", "declaration": "function assertEq(bytes[] calldata left, bytes[] calldata right) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertEq(bytes[],bytes[])", "selector": "0xe5fb9b4a", "selectorBytes": [ 229, 251, 155, 74 ] }, "group": "testing", "status": "stable", "safety": "safe" }, { "func": { "id": "assertEq_27", "description": "Asserts that two arrays of `bytes` values are equal and includes error message into revert string on failure.", "declaration": "function assertEq(bytes[] calldata left, bytes[] calldata right, string calldata error) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertEq(bytes[],bytes[],string)", "selector": "0xf413f0b6", "selectorBytes": [ 244, 19, 240, 182 ] }, "group": "testing", "status": "stable", "safety": "safe" }, { "func": { "id": "assertEq_3", "description": "Asserts that two `uint256` values are equal and includes error message into revert string on failure.", "declaration": "function assertEq(uint256 left, uint256 right, string calldata error) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertEq(uint256,uint256,string)", "selector": "0x88b44c85", "selectorBytes": [ 136, 180, 76, 133 ] }, "group": "testing", "status": "stable", "safety": "safe" }, { "func": { "id": "assertEq_4", "description": "Asserts that two `int256` values are equal.", "declaration": "function assertEq(int256 left, int256 right) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertEq(int256,int256)", "selector": "0xfe74f05b", "selectorBytes": [ 254, 116, 240, 91 ] }, "group": "testing", "status": "stable", "safety": "safe" }, { "func": { "id": "assertEq_5", "description": "Asserts that two `int256` values are equal and includes error message into revert string on failure.", "declaration": "function assertEq(int256 left, int256 right, string calldata error) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertEq(int256,int256,string)", "selector": "0x714a2f13", "selectorBytes": [ 113, 74, 47, 19 ] }, "group": "testing", "status": "stable", "safety": "safe" }, { "func": { "id": "assertEq_6", "description": "Asserts that two `address` values are equal.", "declaration": "function assertEq(address left, address right) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertEq(address,address)", "selector": "0x515361f6", "selectorBytes": [ 81, 83, 97, 246 ] }, "group": "testing", "status": "stable", "safety": "safe" }, { "func": { "id": "assertEq_7", "description": "Asserts that two `address` values are equal and includes error message into revert string on failure.", "declaration": "function assertEq(address left, address right, string calldata error) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertEq(address,address,string)", "selector": "0x2f2769d1", "selectorBytes": [ 47, 39, 105, 209 ] }, "group": "testing", "status": "stable", "safety": "safe" }, { "func": { "id": "assertEq_8", "description": "Asserts that two `bytes32` values are equal.", "declaration": "function assertEq(bytes32 left, bytes32 right) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertEq(bytes32,bytes32)", "selector": "0x7c84c69b", "selectorBytes": [ 124, 132, 198, 155 ] }, "group": "testing", "status": "stable", "safety": "safe" }, { "func": { "id": "assertEq_9", "description": "Asserts that two `bytes32` values are equal and includes error message into revert string on failure.", "declaration": "function assertEq(bytes32 left, bytes32 right, string calldata error) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertEq(bytes32,bytes32,string)", "selector": "0xc1fa1ed0", "selectorBytes": [ 193, 250, 30, 208 ] }, "group": "testing", "status": "stable", "safety": "safe" }, { "func": { "id": "assertFalse_0", "description": "Asserts that the given condition is false.", "declaration": "function assertFalse(bool condition) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertFalse(bool)", "selector": "0xa5982885", "selectorBytes": [ 165, 152, 40, 133 ] }, "group": "testing", "status": "stable", "safety": "safe" }, { "func": { "id": "assertFalse_1", "description": "Asserts that the given condition is false and includes error message into revert string on failure.", "declaration": "function assertFalse(bool condition, string calldata error) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertFalse(bool,string)", "selector": "0x7ba04809", "selectorBytes": [ 123, 160, 72, 9 ] }, "group": "testing", "status": "stable", "safety": "safe" }, { "func": { "id": "assertGeDecimal_0", "description": "Compares two `uint256` values. Expects first value to be greater than or equal to second.\nFormats values with decimals in failure message.", "declaration": "function assertGeDecimal(uint256 left, uint256 right, uint256 decimals) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertGeDecimal(uint256,uint256,uint256)", "selector": "0x3d1fe08a", "selectorBytes": [ 61, 31, 224, 138 ] }, "group": "testing", "status": "stable", "safety": "safe" }, { "func": { "id": "assertGeDecimal_1", "description": "Compares two `uint256` values. Expects first value to be greater than or equal to second.\nFormats values with decimals in failure message. Includes error message into revert string on failure.", "declaration": "function assertGeDecimal(uint256 left, uint256 right, uint256 decimals, string calldata error) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertGeDecimal(uint256,uint256,uint256,string)", "selector": "0x8bff9133", "selectorBytes": [ 139, 255, 145, 51 ] }, "group": "testing", "status": "stable", "safety": "safe" }, { "func": { "id": "assertGeDecimal_2", "description": "Compares two `int256` values. Expects first value to be greater than or equal to second.\nFormats values with decimals in failure message.", "declaration": "function assertGeDecimal(int256 left, int256 right, uint256 decimals) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertGeDecimal(int256,int256,uint256)", "selector": "0xdc28c0f1", "selectorBytes": [ 220, 40, 192, 241 ] }, "group": "testing", "status": "stable", "safety": "safe" }, { "func": { "id": "assertGeDecimal_3", "description": "Compares two `int256` values. Expects first value to be greater than or equal to second.\nFormats values with decimals in failure message. Includes error message into revert string on failure.", "declaration": "function assertGeDecimal(int256 left, int256 right, uint256 decimals, string calldata error) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertGeDecimal(int256,int256,uint256,string)", "selector": "0x5df93c9b", "selectorBytes": [ 93, 249, 60, 155 ] }, "group": "testing", "status": "stable", "safety": "safe" }, { "func": { "id": "assertGe_0", "description": "Compares two `uint256` values. Expects first value to be greater than or equal to second.", "declaration": "function assertGe(uint256 left, uint256 right) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertGe(uint256,uint256)", "selector": "0xa8d4d1d9", "selectorBytes": [ 168, 212, 209, 217 ] }, "group": "testing", "status": "stable", "safety": "safe" }, { "func": { "id": "assertGe_1", "description": "Compares two `uint256` values. Expects first value to be greater than or equal to second.\nIncludes error message into revert string on failure.", "declaration": "function assertGe(uint256 left, uint256 right, string calldata error) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertGe(uint256,uint256,string)", "selector": "0xe25242c0", "selectorBytes": [ 226, 82, 66, 192 ] }, "group": "testing", "status": "stable", "safety": "safe" }, { "func": { "id": "assertGe_2", "description": "Compares two `int256` values. Expects first value to be greater than or equal to second.", "declaration": "function assertGe(int256 left, int256 right) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertGe(int256,int256)", "selector": "0x0a30b771", "selectorBytes": [ 10, 48, 183, 113 ] }, "group": "testing", "status": "stable", "safety": "safe" }, { "func": { "id": "assertGe_3", "description": "Compares two `int256` values. Expects first value to be greater than or equal to second.\nIncludes error message into revert string on failure.", "declaration": "function assertGe(int256 left, int256 right, string calldata error) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertGe(int256,int256,string)", "selector": "0xa84328dd", "selectorBytes": [ 168, 67, 40, 221 ] }, "group": "testing", "status": "stable", "safety": "safe" }, { "func": { "id": "assertGtDecimal_0", "description": "Compares two `uint256` values. Expects first value to be greater than second.\nFormats values with decimals in failure message.", "declaration": "function assertGtDecimal(uint256 left, uint256 right, uint256 decimals) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertGtDecimal(uint256,uint256,uint256)", "selector": "0xeccd2437", "selectorBytes": [ 236, 205, 36, 55 ] }, "group": "testing", "status": "stable", "safety": "safe" }, { "func": { "id": "assertGtDecimal_1", "description": "Compares two `uint256` values. Expects first value to be greater than second.\nFormats values with decimals in failure message. Includes error message into revert string on failure.", "declaration": "function assertGtDecimal(uint256 left, uint256 right, uint256 decimals, string calldata error) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertGtDecimal(uint256,uint256,uint256,string)", "selector": "0x64949a8d", "selectorBytes": [ 100, 148, 154, 141 ] }, "group": "testing", "status": "stable", "safety": "safe" }, { "func": { "id": "assertGtDecimal_2", "description": "Compares two `int256` values. Expects first value to be greater than second.\nFormats values with decimals in failure message.", "declaration": "function assertGtDecimal(int256 left, int256 right, uint256 decimals) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertGtDecimal(int256,int256,uint256)", "selector": "0x78611f0e", "selectorBytes": [ 120, 97, 31, 14 ] }, "group": "testing", "status": "stable", "safety": "safe" }, { "func": { "id": "assertGtDecimal_3", "description": "Compares two `int256` values. Expects first value to be greater than second.\nFormats values with decimals in failure message. Includes error message into revert string on failure.", "declaration": "function assertGtDecimal(int256 left, int256 right, uint256 decimals, string calldata error) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertGtDecimal(int256,int256,uint256,string)", "selector": "0x04a5c7ab", "selectorBytes": [ 4, 165, 199, 171 ] }, "group": "testing", "status": "stable", "safety": "safe" }, { "func": { "id": "assertGt_0", "description": "Compares two `uint256` values. Expects first value to be greater than second.", "declaration": "function assertGt(uint256 left, uint256 right) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertGt(uint256,uint256)", "selector": "0xdb07fcd2", "selectorBytes": [ 219, 7, 252, 210 ] }, "group": "testing", "status": "stable", "safety": "safe" }, { "func": { "id": "assertGt_1", "description": "Compares two `uint256` values. Expects first value to be greater than second.\nIncludes error message into revert string on failure.", "declaration": "function assertGt(uint256 left, uint256 right, string calldata error) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertGt(uint256,uint256,string)", "selector": "0xd9a3c4d2", "selectorBytes": [ 217, 163, 196, 210 ] }, "group": "testing", "status": "stable", "safety": "safe" }, { "func": { "id": "assertGt_2", "description": "Compares two `int256` values. Expects first value to be greater than second.", "declaration": "function assertGt(int256 left, int256 right) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertGt(int256,int256)", "selector": "0x5a362d45", "selectorBytes": [ 90, 54, 45, 69 ] }, "group": "testing", "status": "stable", "safety": "safe" }, { "func": { "id": "assertGt_3", "description": "Compares two `int256` values. Expects first value to be greater than second.\nIncludes error message into revert string on failure.", "declaration": "function assertGt(int256 left, int256 right, string calldata error) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertGt(int256,int256,string)", "selector": "0xf8d33b9b", "selectorBytes": [ 248, 211, 59, 155 ] }, "group": "testing", "status": "stable", "safety": "safe" }, { "func": { "id": "assertLeDecimal_0", "description": "Compares two `uint256` values. Expects first value to be less than or equal to second.\nFormats values with decimals in failure message.", "declaration": "function assertLeDecimal(uint256 left, uint256 right, uint256 decimals) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertLeDecimal(uint256,uint256,uint256)", "selector": "0xc304aab7", "selectorBytes": [ 195, 4, 170, 183 ] }, "group": "testing", "status": "stable", "safety": "safe" }, { "func": { "id": "assertLeDecimal_1", "description": "Compares two `uint256` values. Expects first value to be less than or equal to second.\nFormats values with decimals in failure message. Includes error message into revert string on failure.", "declaration": "function assertLeDecimal(uint256 left, uint256 right, uint256 decimals, string calldata error) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertLeDecimal(uint256,uint256,uint256,string)", "selector": "0x7fefbbe0", "selectorBytes": [ 127, 239, 187, 224 ] }, "group": "testing", "status": "stable", "safety": "safe" }, { "func": { "id": "assertLeDecimal_2", "description": "Compares two `int256` values. Expects first value to be less than or equal to second.\nFormats values with decimals in failure message.", "declaration": "function assertLeDecimal(int256 left, int256 right, uint256 decimals) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertLeDecimal(int256,int256,uint256)", "selector": "0x11d1364a", "selectorBytes": [ 17, 209, 54, 74 ] }, "group": "testing", "status": "stable", "safety": "safe" }, { "func": { "id": "assertLeDecimal_3", "description": "Compares two `int256` values. Expects first value to be less than or equal to second.\nFormats values with decimals in failure message. Includes error message into revert string on failure.", "declaration": "function assertLeDecimal(int256 left, int256 right, uint256 decimals, string calldata error) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertLeDecimal(int256,int256,uint256,string)", "selector": "0xaa5cf788", "selectorBytes": [ 170, 92, 247, 136 ] }, "group": "testing", "status": "stable", "safety": "safe" }, { "func": { "id": "assertLe_0", "description": "Compares two `uint256` values. Expects first value to be less than or equal to second.", "declaration": "function assertLe(uint256 left, uint256 right) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertLe(uint256,uint256)", "selector": "0x8466f415", "selectorBytes": [ 132, 102, 244, 21 ] }, "group": "testing", "status": "stable", "safety": "safe" }, { "func": { "id": "assertLe_1", "description": "Compares two `uint256` values. Expects first value to be less than or equal to second.\nIncludes error message into revert string on failure.", "declaration": "function assertLe(uint256 left, uint256 right, string calldata error) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertLe(uint256,uint256,string)", "selector": "0xd17d4b0d", "selectorBytes": [ 209, 125, 75, 13 ] }, "group": "testing", "status": "stable", "safety": "safe" }, { "func": { "id": "assertLe_2", "description": "Compares two `int256` values. Expects first value to be less than or equal to second.", "declaration": "function assertLe(int256 left, int256 right) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertLe(int256,int256)", "selector": "0x95fd154e", "selectorBytes": [ 149, 253, 21, 78 ] }, "group": "testing", "status": "stable", "safety": "safe" }, { "func": { "id": "assertLe_3", "description": "Compares two `int256` values. Expects first value to be less than or equal to second.\nIncludes error message into revert string on failure.", "declaration": "function assertLe(int256 left, int256 right, string calldata error) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertLe(int256,int256,string)", "selector": "0x4dfe692c", "selectorBytes": [ 77, 254, 105, 44 ] }, "group": "testing", "status": "stable", "safety": "safe" }, { "func": { "id": "assertLtDecimal_0", "description": "Compares two `uint256` values. Expects first value to be less than second.\nFormats values with decimals in failure message.", "declaration": "function assertLtDecimal(uint256 left, uint256 right, uint256 decimals) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertLtDecimal(uint256,uint256,uint256)", "selector": "0x2077337e", "selectorBytes": [ 32, 119, 51, 126 ] }, "group": "testing", "status": "stable", "safety": "safe" }, { "func": { "id": "assertLtDecimal_1", "description": "Compares two `uint256` values. Expects first value to be less than second.\nFormats values with decimals in failure message. Includes error message into revert string on failure.", "declaration": "function assertLtDecimal(uint256 left, uint256 right, uint256 decimals, string calldata error) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertLtDecimal(uint256,uint256,uint256,string)", "selector": "0xa972d037", "selectorBytes": [ 169, 114, 208, 55 ] }, "group": "testing", "status": "stable", "safety": "safe" }, { "func": { "id": "assertLtDecimal_2", "description": "Compares two `int256` values. Expects first value to be less than second.\nFormats values with decimals in failure message.", "declaration": "function assertLtDecimal(int256 left, int256 right, uint256 decimals) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertLtDecimal(int256,int256,uint256)", "selector": "0xdbe8d88b", "selectorBytes": [ 219, 232, 216, 139 ] }, "group": "testing", "status": "stable", "safety": "safe" }, { "func": { "id": "assertLtDecimal_3", "description": "Compares two `int256` values. Expects first value to be less than second.\nFormats values with decimals in failure message. Includes error message into revert string on failure.", "declaration": "function assertLtDecimal(int256 left, int256 right, uint256 decimals, string calldata error) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertLtDecimal(int256,int256,uint256,string)", "selector": "0x40f0b4e0", "selectorBytes": [ 64, 240, 180, 224 ] }, "group": "testing", "status": "stable", "safety": "safe" }, { "func": { "id": "assertLt_0", "description": "Compares two `uint256` values. Expects first value to be less than second.", "declaration": "function assertLt(uint256 left, uint256 right) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertLt(uint256,uint256)", "selector": "0xb12fc005", "selectorBytes": [ 177, 47, 192, 5 ] }, "group": "testing", "status": "stable", "safety": "safe" }, { "func": { "id": "assertLt_1", "description": "Compares two `uint256` values. Expects first value to be less than second.\nIncludes error message into revert string on failure.", "declaration": "function assertLt(uint256 left, uint256 right, string calldata error) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertLt(uint256,uint256,string)", "selector": "0x65d5c135", "selectorBytes": [ 101, 213, 193, 53 ] }, "group": "testing", "status": "stable", "safety": "safe" }, { "func": { "id": "assertLt_2", "description": "Compares two `int256` values. Expects first value to be less than second.", "declaration": "function assertLt(int256 left, int256 right) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertLt(int256,int256)", "selector": "0x3e914080", "selectorBytes": [ 62, 145, 64, 128 ] }, "group": "testing", "status": "stable", "safety": "safe" }, { "func": { "id": "assertLt_3", "description": "Compares two `int256` values. Expects first value to be less than second.\nIncludes error message into revert string on failure.", "declaration": "function assertLt(int256 left, int256 right, string calldata error) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertLt(int256,int256,string)", "selector": "0x9ff531e3", "selectorBytes": [ 159, 245, 49, 227 ] }, "group": "testing", "status": "stable", "safety": "safe" }, { "func": { "id": "assertNotEqDecimal_0", "description": "Asserts that two `uint256` values are not equal, formatting them with decimals in failure message.", "declaration": "function assertNotEqDecimal(uint256 left, uint256 right, uint256 decimals) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertNotEqDecimal(uint256,uint256,uint256)", "selector": "0x669efca7", "selectorBytes": [ 102, 158, 252, 167 ] }, "group": "testing", "status": "stable", "safety": "safe" }, { "func": { "id": "assertNotEqDecimal_1", "description": "Asserts that two `uint256` values are not equal, formatting them with decimals in failure message.\nIncludes error message into revert string on failure.", "declaration": "function assertNotEqDecimal(uint256 left, uint256 right, uint256 decimals, string calldata error) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertNotEqDecimal(uint256,uint256,uint256,string)", "selector": "0xf5a55558", "selectorBytes": [ 245, 165, 85, 88 ] }, "group": "testing", "status": "stable", "safety": "safe" }, { "func": { "id": "assertNotEqDecimal_2", "description": "Asserts that two `int256` values are not equal, formatting them with decimals in failure message.", "declaration": "function assertNotEqDecimal(int256 left, int256 right, uint256 decimals) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertNotEqDecimal(int256,int256,uint256)", "selector": "0x14e75680", "selectorBytes": [ 20, 231, 86, 128 ] }, "group": "testing", "status": "stable", "safety": "safe" }, { "func": { "id": "assertNotEqDecimal_3", "description": "Asserts that two `int256` values are not equal, formatting them with decimals in failure message.\nIncludes error message into revert string on failure.", "declaration": "function assertNotEqDecimal(int256 left, int256 right, uint256 decimals, string calldata error) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertNotEqDecimal(int256,int256,uint256,string)", "selector": "0x33949f0b", "selectorBytes": [ 51, 148, 159, 11 ] }, "group": "testing", "status": "stable", "safety": "safe" }, { "func": { "id": "assertNotEq_0", "description": "Asserts that two `bool` values are not equal.", "declaration": "function assertNotEq(bool left, bool right) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertNotEq(bool,bool)", "selector": "0x236e4d66", "selectorBytes": [ 35, 110, 77, 102 ] }, "group": "testing", "status": "stable", "safety": "safe" }, { "func": { "id": "assertNotEq_1", "description": "Asserts that two `bool` values are not equal and includes error message into revert string on failure.", "declaration": "function assertNotEq(bool left, bool right, string calldata error) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertNotEq(bool,bool,string)", "selector": "0x1091a261", "selectorBytes": [ 16, 145, 162, 97 ] }, "group": "testing", "status": "stable", "safety": "safe" }, { "func": { "id": "assertNotEq_10", "description": "Asserts that two `string` values are not equal.", "declaration": "function assertNotEq(string calldata left, string calldata right) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertNotEq(string,string)", "selector": "0x6a8237b3", "selectorBytes": [ 106, 130, 55, 179 ] }, "group": "testing", "status": "stable", "safety": "safe" }, { "func": { "id": "assertNotEq_11", "description": "Asserts that two `string` values are not equal and includes error message into revert string on failure.", "declaration": "function assertNotEq(string calldata left, string calldata right, string calldata error) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertNotEq(string,string,string)", "selector": "0x78bdcea7", "selectorBytes": [ 120, 189, 206, 167 ] }, "group": "testing", "status": "stable", "safety": "safe" }, { "func": { "id": "assertNotEq_12", "description": "Asserts that two `bytes` values are not equal.", "declaration": "function assertNotEq(bytes calldata left, bytes calldata right) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertNotEq(bytes,bytes)", "selector": "0x3cf78e28", "selectorBytes": [ 60, 247, 142, 40 ] }, "group": "testing", "status": "stable", "safety": "safe" }, { "func": { "id": "assertNotEq_13", "description": "Asserts that two `bytes` values are not equal and includes error message into revert string on failure.", "declaration": "function assertNotEq(bytes calldata left, bytes calldata right, string calldata error) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertNotEq(bytes,bytes,string)", "selector": "0x9507540e", "selectorBytes": [ 149, 7, 84, 14 ] }, "group": "testing", "status": "stable", "safety": "safe" }, { "func": { "id": "assertNotEq_14", "description": "Asserts that two arrays of `bool` values are not equal.", "declaration": "function assertNotEq(bool[] calldata left, bool[] calldata right) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertNotEq(bool[],bool[])", "selector": "0x286fafea", "selectorBytes": [ 40, 111, 175, 234 ] }, "group": "testing", "status": "stable", "safety": "safe" }, { "func": { "id": "assertNotEq_15", "description": "Asserts that two arrays of `bool` values are not equal and includes error message into revert string on failure.", "declaration": "function assertNotEq(bool[] calldata left, bool[] calldata right, string calldata error) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertNotEq(bool[],bool[],string)", "selector": "0x62c6f9fb", "selectorBytes": [ 98, 198, 249, 251 ] }, "group": "testing", "status": "stable", "safety": "safe" }, { "func": { "id": "assertNotEq_16", "description": "Asserts that two arrays of `uint256` values are not equal.", "declaration": "function assertNotEq(uint256[] calldata left, uint256[] calldata right) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertNotEq(uint256[],uint256[])", "selector": "0x56f29cba", "selectorBytes": [ 86, 242, 156, 186 ] }, "group": "testing", "status": "stable", "safety": "safe" }, { "func": { "id": "assertNotEq_17", "description": "Asserts that two arrays of `uint256` values are not equal and includes error message into revert string on failure.", "declaration": "function assertNotEq(uint256[] calldata left, uint256[] calldata right, string calldata error) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertNotEq(uint256[],uint256[],string)", "selector": "0x9a7fbd8f", "selectorBytes": [ 154, 127, 189, 143 ] }, "group": "testing", "status": "stable", "safety": "safe" }, { "func": { "id": "assertNotEq_18", "description": "Asserts that two arrays of `int256` values are not equal.", "declaration": "function assertNotEq(int256[] calldata left, int256[] calldata right) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertNotEq(int256[],int256[])", "selector": "0x0b72f4ef", "selectorBytes": [ 11, 114, 244, 239 ] }, "group": "testing", "status": "stable", "safety": "safe" }, { "func": { "id": "assertNotEq_19", "description": "Asserts that two arrays of `int256` values are not equal and includes error message into revert string on failure.", "declaration": "function assertNotEq(int256[] calldata left, int256[] calldata right, string calldata error) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertNotEq(int256[],int256[],string)", "selector": "0xd3977322", "selectorBytes": [ 211, 151, 115, 34 ] }, "group": "testing", "status": "stable", "safety": "safe" }, { "func": { "id": "assertNotEq_2", "description": "Asserts that two `uint256` values are not equal.", "declaration": "function assertNotEq(uint256 left, uint256 right) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertNotEq(uint256,uint256)", "selector": "0xb7909320", "selectorBytes": [ 183, 144, 147, 32 ] }, "group": "testing", "status": "stable", "safety": "safe" }, { "func": { "id": "assertNotEq_20", "description": "Asserts that two arrays of `address` values are not equal.", "declaration": "function assertNotEq(address[] calldata left, address[] calldata right) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertNotEq(address[],address[])", "selector": "0x46d0b252", "selectorBytes": [ 70, 208, 178, 82 ] }, "group": "testing", "status": "stable", "safety": "safe" }, { "func": { "id": "assertNotEq_21", "description": "Asserts that two arrays of `address` values are not equal and includes error message into revert string on failure.", "declaration": "function assertNotEq(address[] calldata left, address[] calldata right, string calldata error) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertNotEq(address[],address[],string)", "selector": "0x72c7e0b5", "selectorBytes": [ 114, 199, 224, 181 ] }, "group": "testing", "status": "stable", "safety": "safe" }, { "func": { "id": "assertNotEq_22", "description": "Asserts that two arrays of `bytes32` values are not equal.", "declaration": "function assertNotEq(bytes32[] calldata left, bytes32[] calldata right) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertNotEq(bytes32[],bytes32[])", "selector": "0x0603ea68", "selectorBytes": [ 6, 3, 234, 104 ] }, "group": "testing", "status": "stable", "safety": "safe" }, { "func": { "id": "assertNotEq_23", "description": "Asserts that two arrays of `bytes32` values are not equal and includes error message into revert string on failure.", "declaration": "function assertNotEq(bytes32[] calldata left, bytes32[] calldata right, string calldata error) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertNotEq(bytes32[],bytes32[],string)", "selector": "0xb873634c", "selectorBytes": [ 184, 115, 99, 76 ] }, "group": "testing", "status": "stable", "safety": "safe" }, { "func": { "id": "assertNotEq_24", "description": "Asserts that two arrays of `string` values are not equal.", "declaration": "function assertNotEq(string[] calldata left, string[] calldata right) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertNotEq(string[],string[])", "selector": "0xbdfacbe8", "selectorBytes": [ 189, 250, 203, 232 ] }, "group": "testing", "status": "stable", "safety": "safe" }, { "func": { "id": "assertNotEq_25", "description": "Asserts that two arrays of `string` values are not equal and includes error message into revert string on failure.", "declaration": "function assertNotEq(string[] calldata left, string[] calldata right, string calldata error) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertNotEq(string[],string[],string)", "selector": "0xb67187f3", "selectorBytes": [ 182, 113, 135, 243 ] }, "group": "testing", "status": "stable", "safety": "safe" }, { "func": { "id": "assertNotEq_26", "description": "Asserts that two arrays of `bytes` values are not equal.", "declaration": "function assertNotEq(bytes[] calldata left, bytes[] calldata right) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertNotEq(bytes[],bytes[])", "selector": "0xedecd035", "selectorBytes": [ 237, 236, 208, 53 ] }, "group": "testing", "status": "stable", "safety": "safe" }, { "func": { "id": "assertNotEq_27", "description": "Asserts that two arrays of `bytes` values are not equal and includes error message into revert string on failure.", "declaration": "function assertNotEq(bytes[] calldata left, bytes[] calldata right, string calldata error) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertNotEq(bytes[],bytes[],string)", "selector": "0x1dcd1f68", "selectorBytes": [ 29, 205, 31, 104 ] }, "group": "testing", "status": "stable", "safety": "safe" }, { "func": { "id": "assertNotEq_3", "description": "Asserts that two `uint256` values are not equal and includes error message into revert string on failure.", "declaration": "function assertNotEq(uint256 left, uint256 right, string calldata error) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertNotEq(uint256,uint256,string)", "selector": "0x98f9bdbd", "selectorBytes": [ 152, 249, 189, 189 ] }, "group": "testing", "status": "stable", "safety": "safe" }, { "func": { "id": "assertNotEq_4", "description": "Asserts that two `int256` values are not equal.", "declaration": "function assertNotEq(int256 left, int256 right) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertNotEq(int256,int256)", "selector": "0xf4c004e3", "selectorBytes": [ 244, 192, 4, 227 ] }, "group": "testing", "status": "stable", "safety": "safe" }, { "func": { "id": "assertNotEq_5", "description": "Asserts that two `int256` values are not equal and includes error message into revert string on failure.", "declaration": "function assertNotEq(int256 left, int256 right, string calldata error) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertNotEq(int256,int256,string)", "selector": "0x4724c5b9", "selectorBytes": [ 71, 36, 197, 185 ] }, "group": "testing", "status": "stable", "safety": "safe" }, { "func": { "id": "assertNotEq_6", "description": "Asserts that two `address` values are not equal.", "declaration": "function assertNotEq(address left, address right) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertNotEq(address,address)", "selector": "0xb12e1694", "selectorBytes": [ 177, 46, 22, 148 ] }, "group": "testing", "status": "stable", "safety": "safe" }, { "func": { "id": "assertNotEq_7", "description": "Asserts that two `address` values are not equal and includes error message into revert string on failure.", "declaration": "function assertNotEq(address left, address right, string calldata error) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertNotEq(address,address,string)", "selector": "0x8775a591", "selectorBytes": [ 135, 117, 165, 145 ] }, "group": "testing", "status": "stable", "safety": "safe" }, { "func": { "id": "assertNotEq_8", "description": "Asserts that two `bytes32` values are not equal.", "declaration": "function assertNotEq(bytes32 left, bytes32 right) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertNotEq(bytes32,bytes32)", "selector": "0x898e83fc", "selectorBytes": [ 137, 142, 131, 252 ] }, "group": "testing", "status": "stable", "safety": "safe" }, { "func": { "id": "assertNotEq_9", "description": "Asserts that two `bytes32` values are not equal and includes error message into revert string on failure.", "declaration": "function assertNotEq(bytes32 left, bytes32 right, string calldata error) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertNotEq(bytes32,bytes32,string)", "selector": "0xb2332f51", "selectorBytes": [ 178, 51, 47, 81 ] }, "group": "testing", "status": "stable", "safety": "safe" }, { "func": { "id": "assertTrue_0", "description": "Asserts that the given condition is true.", "declaration": "function assertTrue(bool condition) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertTrue(bool)", "selector": "0x0c9fd581", "selectorBytes": [ 12, 159, 213, 129 ] }, "group": "testing", "status": "stable", "safety": "safe" }, { "func": { "id": "assertTrue_1", "description": "Asserts that the given condition is true and includes error message into revert string on failure.", "declaration": "function assertTrue(bool condition, string calldata error) external pure;", "visibility": "external", "mutability": "pure", "signature": "assertTrue(bool,string)", "selector": "0xa34edc03", "selectorBytes": [ 163, 78, 220, 3 ] }, "group": "testing", "status": "stable", "safety": "safe" }, { "func": { "id": "assume", "description": "If the condition is false, discard this run's fuzz inputs and generate new ones.", "declaration": "function assume(bool condition) external pure;", "visibility": "external", "mutability": "pure", "signature": "assume(bool)", "selector": "0x4c63e562", "selectorBytes": [ 76, 99, 229, 98 ] }, "group": "testing", "status": "stable", "safety": "safe" }, { "func": { "id": "assumeNoRevert_0", "description": "Discard this run's fuzz inputs and generate new ones if next call reverted.", "declaration": "function assumeNoRevert() external pure;", "visibility": "external", "mutability": "pure", "signature": "assumeNoRevert()", "selector": "0x285b366a", "selectorBytes": [ 40, 91, 54, 106 ] }, "group": "testing", "status": "stable", "safety": "safe" }, { "func": { "id": "assumeNoRevert_1", "description": "Discard this run's fuzz inputs and generate new ones if next call reverts with the potential revert parameters.", "declaration": "function assumeNoRevert(PotentialRevert calldata potentialRevert) external pure;", "visibility": "external", "mutability": "pure", "signature": "assumeNoRevert((address,bool,bytes))", "selector": "0xd8591eeb", "selectorBytes": [ 216, 89, 30, 235 ] }, "group": "testing", "status": "stable", "safety": "safe" }, { "func": { "id": "assumeNoRevert_2", "description": "Discard this run's fuzz inputs and generate new ones if next call reverts with the any of the potential revert parameters.", "declaration": "function assumeNoRevert(PotentialRevert[] calldata potentialReverts) external pure;", "visibility": "external", "mutability": "pure", "signature": "assumeNoRevert((address,bool,bytes)[])", "selector": "0x8a4592cc", "selectorBytes": [ 138, 69, 146, 204 ] }, "group": "testing", "status": "stable", "safety": "safe" }, { "func": { "id": "attachBlob", "description": "Attach an EIP-4844 blob to the next call", "declaration": "function attachBlob(bytes calldata blob) external;", "visibility": "external", "mutability": "", "signature": "attachBlob(bytes)", "selector": "0x10cb385c", "selectorBytes": [ 16, 203, 56, 92 ] }, "group": "scripting", "status": "stable", "safety": "safe" }, { "func": { "id": "attachDelegation_0", "description": "Designate the next call as an EIP-7702 transaction", "declaration": "function attachDelegation(SignedDelegation calldata signedDelegation) external;", "visibility": "external", "mutability": "", "signature": "attachDelegation((uint8,bytes32,bytes32,uint64,address))", "selector": "0x14ae3519", "selectorBytes": [ 20, 174, 53, 25 ] }, "group": "scripting", "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", "description": "Sets `block.blobbasefee`", "declaration": "function blobBaseFee(uint256 newBlobBaseFee) external;", "visibility": "external", "mutability": "", "signature": "blobBaseFee(uint256)", "selector": "0x6d315d7e", "selectorBytes": [ 109, 49, 93, 126 ] }, "group": "evm", "status": "stable", "safety": "unsafe" }, { "func": { "id": "blobhashes", "description": "Sets the blobhashes in the transaction.\nNot available on EVM versions before Cancun.\nIf used on unsupported EVM versions it will revert.", "declaration": "function blobhashes(bytes32[] calldata hashes) external;", "visibility": "external", "mutability": "", "signature": "blobhashes(bytes32[])", "selector": "0x129de7eb", "selectorBytes": [ 18, 157, 231, 235 ] }, "group": "evm", "status": "stable", "safety": "unsafe" }, { "func": { "id": "bound_0", "description": "Returns an uint256 value bounded in given range and different from the current one.", "declaration": "function bound(uint256 current, uint256 min, uint256 max) external view returns (uint256);", "visibility": "external", "mutability": "view", "signature": "bound(uint256,uint256,uint256)", "selector": "0x5a6c1eed", "selectorBytes": [ 90, 108, 30, 237 ] }, "group": "utilities", "status": "stable", "safety": "safe" }, { "func": { "id": "bound_1", "description": "Returns an int256 value bounded in given range and different from the current one.", "declaration": "function bound(int256 current, int256 min, int256 max) external view returns (int256);", "visibility": "external", "mutability": "view", "signature": "bound(int256,int256,int256)", "selector": "0x8f48fc07", "selectorBytes": [ 143, 72, 252, 7 ] }, "group": "utilities", "status": "stable", "safety": "safe" }, { "func": { "id": "breakpoint_0", "description": "Writes a breakpoint to jump to in the debugger.", "declaration": "function breakpoint(string calldata char) external pure;", "visibility": "external", "mutability": "pure", "signature": "breakpoint(string)", "selector": "0xf0259e92", "selectorBytes": [ 240, 37, 158, 146 ] }, "group": "testing", "status": "stable", "safety": "safe" }, { "func": { "id": "breakpoint_1", "description": "Writes a conditional breakpoint to jump to in the debugger.", "declaration": "function breakpoint(string calldata char, bool value) external pure;", "visibility": "external", "mutability": "pure", "signature": "breakpoint(string,bool)", "selector": "0xf7d39a8d", "selectorBytes": [ 247, 211, 154, 141 ] }, "group": "testing", "status": "stable", "safety": "safe" }, { "func": { "id": "broadcastRawTransaction", "description": "Takes a signed transaction and broadcasts it to the network.", "declaration": "function broadcastRawTransaction(bytes calldata data) external;", "visibility": "external", "mutability": "", "signature": "broadcastRawTransaction(bytes)", "selector": "0x8c0c72e0", "selectorBytes": [ 140, 12, 114, 224 ] }, "group": "scripting", "status": "stable", "safety": "safe" }, { "func": { "id": "broadcast_0", "description": "Has the next call (at this call depth only) create transactions that can later be signed and sent onchain.\nBroadcasting address is determined by checking the following in order:\n1. If `--sender` argument was provided, that address is used.\n2. If exactly one signer (e.g. private key, hw wallet, keystore) is set when `forge broadcast` is invoked, that signer is used.\n3. Otherwise, default foundry sender (1804c8AB1F12E6bbf3894d4083f33e07309d1f38) is used.", "declaration": "function broadcast() external;", "visibility": "external", "mutability": "", "signature": "broadcast()", "selector": "0xafc98040", "selectorBytes": [ 175, 201, 128, 64 ] }, "group": "scripting", "status": "stable", "safety": "safe" }, { "func": { "id": "broadcast_1", "description": "Has the next call (at this call depth only) create a transaction with the address provided\nas the sender that can later be signed and sent onchain.", "declaration": "function broadcast(address signer) external;", "visibility": "external", "mutability": "", "signature": "broadcast(address)", "selector": "0xe6962cdb", "selectorBytes": [ 230, 150, 44, 219 ] }, "group": "scripting", "status": "stable", "safety": "safe" }, { "func": { "id": "broadcast_2", "description": "Has the next call (at this call depth only) create a transaction with the private key\nprovided as the sender that can later be signed and sent onchain.", "declaration": "function broadcast(uint256 privateKey) external;", "visibility": "external", "mutability": "", "signature": "broadcast(uint256)", "selector": "0xf67a965b", "selectorBytes": [ 246, 122, 150, 91 ] }, "group": "scripting", "status": "stable", "safety": "safe" }, { "func": { "id": "chainId", "description": "Sets `block.chainid`.", "declaration": "function chainId(uint256 newChainId) external;", "visibility": "external", "mutability": "", "signature": "chainId(uint256)", "selector": "0x4049ddd2", "selectorBytes": [ 64, 73, 221, 210 ] }, "group": "evm", "status": "stable", "safety": "unsafe" }, { "func": { "id": "clearMockedCalls", "description": "Clears all mocked calls.", "declaration": "function clearMockedCalls() external;", "visibility": "external", "mutability": "", "signature": "clearMockedCalls()", "selector": "0x3fdf4e15", "selectorBytes": [ 63, 223, 78, 21 ] }, "group": "evm", "status": "stable", "safety": "unsafe" }, { "func": { "id": "cloneAccount", "description": "Clones a source account code, state, balance and nonce to a target account and updates in-memory EVM state.", "declaration": "function cloneAccount(address source, address target) external;", "visibility": "external", "mutability": "", "signature": "cloneAccount(address,address)", "selector": "0x533d61c9", "selectorBytes": [ 83, 61, 97, 201 ] }, "group": "evm", "status": "stable", "safety": "unsafe" }, { "func": { "id": "closeFile", "description": "Closes file for reading, resetting the offset and allowing to read it from beginning with readLine.\n`path` is relative to the project root.", "declaration": "function closeFile(string calldata path) external;", "visibility": "external", "mutability": "", "signature": "closeFile(string)", "selector": "0x48c3241f", "selectorBytes": [ 72, 195, 36, 31 ] }, "group": "filesystem", "status": "stable", "safety": "safe" }, { "func": { "id": "coinbase", "description": "Sets `block.coinbase`.", "declaration": "function coinbase(address newCoinbase) external;", "visibility": "external", "mutability": "", "signature": "coinbase(address)", "selector": "0xff483c54", "selectorBytes": [ 255, 72, 60, 84 ] }, "group": "evm", "status": "stable", "safety": "unsafe" }, { "func": { "id": "computeCreate2Address_0", "description": "Compute the address of a contract created with CREATE2 using the given CREATE2 deployer.", "declaration": "function computeCreate2Address(bytes32 salt, bytes32 initCodeHash, address deployer) external pure returns (address);", "visibility": "external", "mutability": "pure", "signature": "computeCreate2Address(bytes32,bytes32,address)", "selector": "0xd323826a", "selectorBytes": [ 211, 35, 130, 106 ] }, "group": "utilities", "status": "stable", "safety": "safe" }, { "func": { "id": "computeCreate2Address_1", "description": "Compute the address of a contract created with CREATE2 using the default CREATE2 deployer.", "declaration": "function computeCreate2Address(bytes32 salt, bytes32 initCodeHash) external pure returns (address);", "visibility": "external", "mutability": "pure", "signature": "computeCreate2Address(bytes32,bytes32)", "selector": "0x890c283b", "selectorBytes": [ 137, 12, 40, 59 ] }, "group": "utilities", "status": "stable", "safety": "safe" }, { "func": { "id": "computeCreateAddress", "description": "Compute the address a contract will be deployed at for a given deployer address and nonce.", "declaration": "function computeCreateAddress(address deployer, uint256 nonce) external pure returns (address);", "visibility": "external", "mutability": "pure", "signature": "computeCreateAddress(address,uint256)", "selector": "0x74637a7a", "selectorBytes": [ 116, 99, 122, 122 ] }, "group": "utilities", "status": "stable", "safety": "safe" }, { "func": { "id": "contains", "description": "Returns true if `search` is found in `subject`, false otherwise.", "declaration": "function contains(string calldata subject, string calldata search) external pure returns (bool result);", "visibility": "external", "mutability": "pure", "signature": "contains(string,string)", "selector": "0x3fb18aec", "selectorBytes": [ 63, 177, 138, 236 ] }, "group": "string", "status": "stable", "safety": "safe" }, { "func": { "id": "cool", "description": "Marks the slots of an account and the account address as cold.", "declaration": "function cool(address target) external;", "visibility": "external", "mutability": "", "signature": "cool(address)", "selector": "0x40ff9f21", "selectorBytes": [ 64, 255, 159, 33 ] }, "group": "evm", "status": "stable", "safety": "unsafe" }, { "func": { "id": "coolSlot", "description": "Utility cheatcode to mark specific storage slot as cold, simulating no prior read.", "declaration": "function coolSlot(address target, bytes32 slot) external;", "visibility": "external", "mutability": "", "signature": "coolSlot(address,bytes32)", "selector": "0x8c78e654", "selectorBytes": [ 140, 120, 230, 84 ] }, "group": "evm", "status": "stable", "safety": "unsafe" }, { "func": { "id": "copyFile", "description": "Copies the contents of one file to another. This function will **overwrite** the contents of `to`.\nOn success, the total number of bytes copied is returned and it is equal to the length of the `to` file as reported by `metadata`.\nBoth `from` and `to` are relative to the project root.", "declaration": "function copyFile(string calldata from, string calldata to) external returns (uint64 copied);", "visibility": "external", "mutability": "", "signature": "copyFile(string,string)", "selector": "0xa54a87d8", "selectorBytes": [ 165, 74, 135, 216 ] }, "group": "filesystem", "status": "stable", "safety": "safe" }, { "func": { "id": "copyStorage", "description": "Utility cheatcode to copy storage of `from` contract to another `to` contract.", "declaration": "function copyStorage(address from, address to) external;", "visibility": "external", "mutability": "", "signature": "copyStorage(address,address)", "selector": "0x203dac0d", "selectorBytes": [ 32, 61, 172, 13 ] }, "group": "utilities", "status": "stable", "safety": "safe" }, { "func": { "id": "createDir", "description": "Creates a new, empty directory at the provided path.\nThis cheatcode will revert in the following situations, but is not limited to just these cases:\n- User lacks permissions to modify `path`.\n- A parent of the given path doesn't exist and `recursive` is false.\n- `path` already exists and `recursive` is false.\n`path` is relative to the project root.", "declaration": "function createDir(string calldata path, bool recursive) external;", "visibility": "external", "mutability": "", "signature": "createDir(string,bool)", "selector": "0x168b64d3", "selectorBytes": [ 22, 139, 100, 211 ] }, "group": "filesystem", "status": "stable", "safety": "safe" }, { "func": { "id": "createEd25519Key", "description": "Generates an Ed25519 key pair from a deterministic salt.\nReturns (publicKey, privateKey) as 32-byte values.", "declaration": "function createEd25519Key(bytes32 salt) external pure returns (bytes32 publicKey, bytes32 privateKey);", "visibility": "external", "mutability": "pure", "signature": "createEd25519Key(bytes32)", "selector": "0x1ef3f27a", "selectorBytes": [ 30, 243, 242, 122 ] }, "group": "crypto", "status": "stable", "safety": "safe" }, { "func": { "id": "createFork_0", "description": "Creates a new fork with the given endpoint and the _latest_ block and returns the identifier of the fork.", "declaration": "function createFork(string calldata urlOrAlias) external returns (uint256 forkId);", "visibility": "external", "mutability": "", "signature": "createFork(string)", "selector": "0x31ba3498", "selectorBytes": [ 49, 186, 52, 152 ] }, "group": "evm", "status": "stable", "safety": "unsafe" }, { "func": { "id": "createFork_1", "description": "Creates a new fork with the given endpoint and block and returns the identifier of the fork.", "declaration": "function createFork(string calldata urlOrAlias, uint256 blockNumber) external returns (uint256 forkId);", "visibility": "external", "mutability": "", "signature": "createFork(string,uint256)", "selector": "0x6ba3ba2b", "selectorBytes": [ 107, 163, 186, 43 ] }, "group": "evm", "status": "stable", "safety": "unsafe" }, { "func": { "id": "createFork_2", "description": "Creates a new fork with the given endpoint and at the block the given transaction was mined in,\nreplays all transaction mined in the block before the transaction, and returns the identifier of the fork.", "declaration": "function createFork(string calldata urlOrAlias, bytes32 txHash) external returns (uint256 forkId);", "visibility": "external", "mutability": "", "signature": "createFork(string,bytes32)", "selector": "0x7ca29682", "selectorBytes": [ 124, 162, 150, 130 ] }, "group": "evm", "status": "stable", "safety": "unsafe" }, { "func": { "id": "createSelectFork_0", "description": "Creates and also selects a new fork with the given endpoint and the latest block and returns the identifier of the fork.", "declaration": "function createSelectFork(string calldata urlOrAlias) external returns (uint256 forkId);", "visibility": "external", "mutability": "", "signature": "createSelectFork(string)", "selector": "0x98680034", "selectorBytes": [ 152, 104, 0, 52 ] }, "group": "evm", "status": "stable", "safety": "unsafe" }, { "func": { "id": "createSelectFork_1", "description": "Creates and also selects a new fork with the given endpoint and block and returns the identifier of the fork.", "declaration": "function createSelectFork(string calldata urlOrAlias, uint256 blockNumber) external returns (uint256 forkId);", "visibility": "external", "mutability": "", "signature": "createSelectFork(string,uint256)", "selector": "0x71ee464d", "selectorBytes": [ 113, 238, 70, 77 ] }, "group": "evm", "status": "stable", "safety": "unsafe" }, { "func": { "id": "createSelectFork_2", "description": "Creates and also selects new fork with the given endpoint and at the block the given transaction was mined in,\nreplays all transaction mined in the block before the transaction, returns the identifier of the fork.", "declaration": "function createSelectFork(string calldata urlOrAlias, bytes32 txHash) external returns (uint256 forkId);", "visibility": "external", "mutability": "", "signature": "createSelectFork(string,bytes32)", "selector": "0x84d52b7a", "selectorBytes": [ 132, 213, 43, 122 ] }, "group": "evm", "status": "stable", "safety": "unsafe" }, { "func": { "id": "createWallet_0", "description": "Derives a private key from the name, labels the account with that name, and returns the wallet.", "declaration": "function createWallet(string calldata walletLabel) external returns (Wallet memory wallet);", "visibility": "external", "mutability": "", "signature": "createWallet(string)", "selector": "0x7404f1d2", "selectorBytes": [ 116, 4, 241, 210 ] }, "group": "crypto", "status": "stable", "safety": "safe" }, { "func": { "id": "createWallet_1", "description": "Generates a wallet from the private key and returns the wallet.", "declaration": "function createWallet(uint256 privateKey) external returns (Wallet memory wallet);", "visibility": "external", "mutability": "", "signature": "createWallet(uint256)", "selector": "0x7a675bb6", "selectorBytes": [ 122, 103, 91, 182 ] }, "group": "crypto", "status": "stable", "safety": "safe" }, { "func": { "id": "createWallet_2", "description": "Generates a wallet from the private key, labels the account with that name, and returns the wallet.", "declaration": "function createWallet(uint256 privateKey, string calldata walletLabel) external returns (Wallet memory wallet);", "visibility": "external", "mutability": "", "signature": "createWallet(uint256,string)", "selector": "0xed7c5462", "selectorBytes": [ 237, 124, 84, 98 ] }, "group": "crypto", "status": "stable", "safety": "safe" }, { "func": { "id": "currentFilePath", "description": "Get the source file path of the currently running test or script contract,\nrelative to the project root.", "declaration": "function currentFilePath() external view returns (string memory path);", "visibility": "external", "mutability": "view", "signature": "currentFilePath()", "selector": "0x9b45555c", "selectorBytes": [ 155, 69, 85, 92 ] }, "group": "filesystem", "status": "stable", "safety": "safe" }, { "func": { "id": "deal", "description": "Sets an address' balance.", "declaration": "function deal(address account, uint256 newBalance) external;", "visibility": "external", "mutability": "", "signature": "deal(address,uint256)", "selector": "0xc88a5e6d", "selectorBytes": [ 200, 138, 94, 109 ] }, "group": "evm", "status": "stable", "safety": "unsafe" }, { "func": { "id": "deleteSnapshot", "description": "`deleteSnapshot` is being deprecated in favor of `deleteStateSnapshot`. It will be removed in future versions.", "declaration": "function deleteSnapshot(uint256 snapshotId) external returns (bool success);", "visibility": "external", "mutability": "", "signature": "deleteSnapshot(uint256)", "selector": "0xa6368557", "selectorBytes": [ 166, 54, 133, 87 ] }, "group": "evm", "status": { "deprecated": "replaced by `deleteStateSnapshot`" }, "safety": "unsafe" }, { "func": { "id": "deleteSnapshots", "description": "`deleteSnapshots` is being deprecated in favor of `deleteStateSnapshots`. It will be removed in future versions.", "declaration": "function deleteSnapshots() external;", "visibility": "external", "mutability": "", "signature": "deleteSnapshots()", "selector": "0x421ae469", "selectorBytes": [ 66, 26, 228, 105 ] }, "group": "evm", "status": { "deprecated": "replaced by `deleteStateSnapshots`" }, "safety": "unsafe" }, { "func": { "id": "deleteStateSnapshot", "description": "Removes the snapshot with the given ID created by `snapshot`.\nTakes the snapshot ID to delete.\nReturns `true` if the snapshot was successfully deleted.\nReturns `false` if the snapshot does not exist.", "declaration": "function deleteStateSnapshot(uint256 snapshotId) external returns (bool success);", "visibility": "external", "mutability": "", "signature": "deleteStateSnapshot(uint256)", "selector": "0x08d6b37a", "selectorBytes": [ 8, 214, 179, 122 ] }, "group": "evm", "status": "stable", "safety": "unsafe" }, { "func": { "id": "deleteStateSnapshots", "description": "Removes _all_ snapshots previously created by `snapshot`.", "declaration": "function deleteStateSnapshots() external;", "visibility": "external", "mutability": "", "signature": "deleteStateSnapshots()", "selector": "0xe0933c74", "selectorBytes": [ 224, 147, 60, 116 ] }, "group": "evm", "status": "stable", "safety": "unsafe" }, { "func": { "id": "deployCode_0", "description": "Deploys a contract from an artifact file. Takes in the relative path to the json file or the path to the\nartifact in the form of :: where and parts are optional.\nReverts if the target artifact contains unlinked library placeholders.", "declaration": "function deployCode(string calldata artifactPath) external returns (address deployedAddress);", "visibility": "external", "mutability": "", "signature": "deployCode(string)", "selector": "0x9a8325a0", "selectorBytes": [ 154, 131, 37, 160 ] }, "group": "filesystem", "status": "stable", "safety": "safe" }, { "func": { "id": "deployCode_1", "description": "Deploys a contract from an artifact file. Takes in the relative path to the json file or the path to the\nartifact in the form of :: where and parts are optional.\nReverts if the target artifact contains unlinked library placeholders.\nAdditionally accepts abi-encoded constructor arguments.", "declaration": "function deployCode(string calldata artifactPath, bytes calldata constructorArgs) external returns (address deployedAddress);", "visibility": "external", "mutability": "", "signature": "deployCode(string,bytes)", "selector": "0x29ce9dde", "selectorBytes": [ 41, 206, 157, 222 ] }, "group": "filesystem", "status": "stable", "safety": "safe" }, { "func": { "id": "deployCode_2", "description": "Deploys a contract from an artifact file. Takes in the relative path to the json file or the path to the\nartifact in the form of :: where and parts are optional.\nReverts if the target artifact contains unlinked library placeholders.\nAdditionally accepts `msg.value`.", "declaration": "function deployCode(string calldata artifactPath, uint256 value) external returns (address deployedAddress);", "visibility": "external", "mutability": "", "signature": "deployCode(string,uint256)", "selector": "0x0af6a701", "selectorBytes": [ 10, 246, 167, 1 ] }, "group": "filesystem", "status": "stable", "safety": "safe" }, { "func": { "id": "deployCode_3", "description": "Deploys a contract from an artifact file. Takes in the relative path to the json file or the path to the\nartifact in the form of :: where and parts are optional.\nReverts if the target artifact contains unlinked library placeholders.\nAdditionally accepts abi-encoded constructor arguments and `msg.value`.", "declaration": "function deployCode(string calldata artifactPath, bytes calldata constructorArgs, uint256 value) external returns (address deployedAddress);", "visibility": "external", "mutability": "", "signature": "deployCode(string,bytes,uint256)", "selector": "0xff5d64e4", "selectorBytes": [ 255, 93, 100, 228 ] }, "group": "filesystem", "status": "stable", "safety": "safe" }, { "func": { "id": "deployCode_4", "description": "Deploys a contract from an artifact file, using the CREATE2 salt. Takes in the relative path to the json file or the path to the\nartifact in the form of :: where and parts are optional.\nReverts if the target artifact contains unlinked library placeholders.", "declaration": "function deployCode(string calldata artifactPath, bytes32 salt) external returns (address deployedAddress);", "visibility": "external", "mutability": "", "signature": "deployCode(string,bytes32)", "selector": "0x17ab1d79", "selectorBytes": [ 23, 171, 29, 121 ] }, "group": "filesystem", "status": "stable", "safety": "safe" }, { "func": { "id": "deployCode_5", "description": "Deploys a contract from an artifact file, using the CREATE2 salt. Takes in the relative path to the json file or the path to the\nartifact in the form of :: where and parts are optional.\nReverts if the target artifact contains unlinked library placeholders.\nAdditionally accepts abi-encoded constructor arguments.", "declaration": "function deployCode(string calldata artifactPath, bytes calldata constructorArgs, bytes32 salt) external returns (address deployedAddress);", "visibility": "external", "mutability": "", "signature": "deployCode(string,bytes,bytes32)", "selector": "0x016155bf", "selectorBytes": [ 1, 97, 85, 191 ] }, "group": "filesystem", "status": "stable", "safety": "safe" }, { "func": { "id": "deployCode_6", "description": "Deploys a contract from an artifact file, using the CREATE2 salt. Takes in the relative path to the json file or the path to the\nartifact in the form of :: where and parts are optional.\nReverts if the target artifact contains unlinked library placeholders.\nAdditionally accepts `msg.value`.", "declaration": "function deployCode(string calldata artifactPath, uint256 value, bytes32 salt) external returns (address deployedAddress);", "visibility": "external", "mutability": "", "signature": "deployCode(string,uint256,bytes32)", "selector": "0x002cb687", "selectorBytes": [ 0, 44, 182, 135 ] }, "group": "filesystem", "status": "stable", "safety": "safe" }, { "func": { "id": "deployCode_7", "description": "Deploys a contract from an artifact file, using the CREATE2 salt. Takes in the relative path to the json file or the path to the\nartifact in the form of :: where and parts are optional.\nReverts if the target artifact contains unlinked library placeholders.\nAdditionally accepts abi-encoded constructor arguments and `msg.value`.", "declaration": "function deployCode(string calldata artifactPath, bytes calldata constructorArgs, uint256 value, bytes32 salt) external returns (address deployedAddress);", "visibility": "external", "mutability": "", "signature": "deployCode(string,bytes,uint256,bytes32)", "selector": "0x3aa773ea", "selectorBytes": [ 58, 167, 115, 234 ] }, "group": "filesystem", "status": "stable", "safety": "safe" }, { "func": { "id": "deriveKey_0", "description": "Derive a private key from a provided mnemonic string (or mnemonic file path)\nat the derivation path `m/44'/60'/0'/0/{index}`.", "declaration": "function deriveKey(string calldata mnemonic, uint32 index) external pure returns (uint256 privateKey);", "visibility": "external", "mutability": "pure", "signature": "deriveKey(string,uint32)", "selector": "0x6229498b", "selectorBytes": [ 98, 41, 73, 139 ] }, "group": "crypto", "status": "stable", "safety": "safe" }, { "func": { "id": "deriveKey_1", "description": "Derive a private key from a provided mnemonic string (or mnemonic file path)\nat `{derivationPath}{index}`.", "declaration": "function deriveKey(string calldata mnemonic, string calldata derivationPath, uint32 index) external pure returns (uint256 privateKey);", "visibility": "external", "mutability": "pure", "signature": "deriveKey(string,string,uint32)", "selector": "0x6bcb2c1b", "selectorBytes": [ 107, 203, 44, 27 ] }, "group": "crypto", "status": "stable", "safety": "safe" }, { "func": { "id": "deriveKey_2", "description": "Derive a private key from a provided mnemonic string (or mnemonic file path) in the specified language\nat the derivation path `m/44'/60'/0'/0/{index}`.", "declaration": "function deriveKey(string calldata mnemonic, uint32 index, string calldata language) external pure returns (uint256 privateKey);", "visibility": "external", "mutability": "pure", "signature": "deriveKey(string,uint32,string)", "selector": "0x32c8176d", "selectorBytes": [ 50, 200, 23, 109 ] }, "group": "crypto", "status": "stable", "safety": "safe" }, { "func": { "id": "deriveKey_3", "description": "Derive a private key from a provided mnemonic string (or mnemonic file path) in the specified language\nat `{derivationPath}{index}`.", "declaration": "function deriveKey(string calldata mnemonic, string calldata derivationPath, uint32 index, string calldata language) external pure returns (uint256 privateKey);", "visibility": "external", "mutability": "pure", "signature": "deriveKey(string,string,uint32,string)", "selector": "0x29233b1f", "selectorBytes": [ 41, 35, 59, 31 ] }, "group": "crypto", "status": "stable", "safety": "safe" }, { "func": { "id": "difficulty", "description": "Sets `block.difficulty`.\nNot available on EVM versions from Paris onwards. Use `prevrandao` instead.\nReverts if used on unsupported EVM versions.", "declaration": "function difficulty(uint256 newDifficulty) external;", "visibility": "external", "mutability": "", "signature": "difficulty(uint256)", "selector": "0x46cc92d9", "selectorBytes": [ 70, 204, 146, 217 ] }, "group": "evm", "status": "stable", "safety": "unsafe" }, { "func": { "id": "dumpState", "description": "Dump a genesis JSON file's `allocs` to disk.", "declaration": "function dumpState(string calldata pathToStateJson) external;", "visibility": "external", "mutability": "", "signature": "dumpState(string)", "selector": "0x709ecd3f", "selectorBytes": [ 112, 158, 205, 63 ] }, "group": "evm", "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", "description": "Returns ENS namehash for provided string.", "declaration": "function ensNamehash(string calldata name) external pure returns (bytes32);", "visibility": "external", "mutability": "pure", "signature": "ensNamehash(string)", "selector": "0x8c374c65", "selectorBytes": [ 140, 55, 76, 101 ] }, "group": "utilities", "status": "stable", "safety": "safe" }, { "func": { "id": "envAddress_0", "description": "Gets the environment variable `name` and parses it as `address`.\nReverts if the variable was not found or could not be parsed.", "declaration": "function envAddress(string calldata name) external view returns (address value);", "visibility": "external", "mutability": "view", "signature": "envAddress(string)", "selector": "0x350d56bf", "selectorBytes": [ 53, 13, 86, 191 ] }, "group": "environment", "status": "stable", "safety": "safe" }, { "func": { "id": "envAddress_1", "description": "Gets the environment variable `name` and parses it as an array of `address`, delimited by `delim`.\nReverts if the variable was not found or could not be parsed.", "declaration": "function envAddress(string calldata name, string calldata delim) external view returns (address[] memory value);", "visibility": "external", "mutability": "view", "signature": "envAddress(string,string)", "selector": "0xad31b9fa", "selectorBytes": [ 173, 49, 185, 250 ] }, "group": "environment", "status": "stable", "safety": "safe" }, { "func": { "id": "envBool_0", "description": "Gets the environment variable `name` and parses it as `bool`.\nReverts if the variable was not found or could not be parsed.", "declaration": "function envBool(string calldata name) external view returns (bool value);", "visibility": "external", "mutability": "view", "signature": "envBool(string)", "selector": "0x7ed1ec7d", "selectorBytes": [ 126, 209, 236, 125 ] }, "group": "environment", "status": "stable", "safety": "safe" }, { "func": { "id": "envBool_1", "description": "Gets the environment variable `name` and parses it as an array of `bool`, delimited by `delim`.\nReverts if the variable was not found or could not be parsed.", "declaration": "function envBool(string calldata name, string calldata delim) external view returns (bool[] memory value);", "visibility": "external", "mutability": "view", "signature": "envBool(string,string)", "selector": "0xaaaddeaf", "selectorBytes": [ 170, 173, 222, 175 ] }, "group": "environment", "status": "stable", "safety": "safe" }, { "func": { "id": "envBytes32_0", "description": "Gets the environment variable `name` and parses it as `bytes32`.\nReverts if the variable was not found or could not be parsed.", "declaration": "function envBytes32(string calldata name) external view returns (bytes32 value);", "visibility": "external", "mutability": "view", "signature": "envBytes32(string)", "selector": "0x97949042", "selectorBytes": [ 151, 148, 144, 66 ] }, "group": "environment", "status": "stable", "safety": "safe" }, { "func": { "id": "envBytes32_1", "description": "Gets the environment variable `name` and parses it as an array of `bytes32`, delimited by `delim`.\nReverts if the variable was not found or could not be parsed.", "declaration": "function envBytes32(string calldata name, string calldata delim) external view returns (bytes32[] memory value);", "visibility": "external", "mutability": "view", "signature": "envBytes32(string,string)", "selector": "0x5af231c1", "selectorBytes": [ 90, 242, 49, 193 ] }, "group": "environment", "status": "stable", "safety": "safe" }, { "func": { "id": "envBytes_0", "description": "Gets the environment variable `name` and parses it as `bytes`.\nReverts if the variable was not found or could not be parsed.", "declaration": "function envBytes(string calldata name) external view returns (bytes memory value);", "visibility": "external", "mutability": "view", "signature": "envBytes(string)", "selector": "0x4d7baf06", "selectorBytes": [ 77, 123, 175, 6 ] }, "group": "environment", "status": "stable", "safety": "safe" }, { "func": { "id": "envBytes_1", "description": "Gets the environment variable `name` and parses it as an array of `bytes`, delimited by `delim`.\nReverts if the variable was not found or could not be parsed.", "declaration": "function envBytes(string calldata name, string calldata delim) external view returns (bytes[] memory value);", "visibility": "external", "mutability": "view", "signature": "envBytes(string,string)", "selector": "0xddc2651b", "selectorBytes": [ 221, 194, 101, 27 ] }, "group": "environment", "status": "stable", "safety": "safe" }, { "func": { "id": "envExists", "description": "Gets the environment variable `name` and returns true if it exists, else returns false.", "declaration": "function envExists(string calldata name) external view returns (bool result);", "visibility": "external", "mutability": "view", "signature": "envExists(string)", "selector": "0xce8365f9", "selectorBytes": [ 206, 131, 101, 249 ] }, "group": "environment", "status": "stable", "safety": "safe" }, { "func": { "id": "envInt_0", "description": "Gets the environment variable `name` and parses it as `int256`.\nReverts if the variable was not found or could not be parsed.", "declaration": "function envInt(string calldata name) external view returns (int256 value);", "visibility": "external", "mutability": "view", "signature": "envInt(string)", "selector": "0x892a0c61", "selectorBytes": [ 137, 42, 12, 97 ] }, "group": "environment", "status": "stable", "safety": "safe" }, { "func": { "id": "envInt_1", "description": "Gets the environment variable `name` and parses it as an array of `int256`, delimited by `delim`.\nReverts if the variable was not found or could not be parsed.", "declaration": "function envInt(string calldata name, string calldata delim) external view returns (int256[] memory value);", "visibility": "external", "mutability": "view", "signature": "envInt(string,string)", "selector": "0x42181150", "selectorBytes": [ 66, 24, 17, 80 ] }, "group": "environment", "status": "stable", "safety": "safe" }, { "func": { "id": "envOr_0", "description": "Gets the environment variable `name` and parses it as `bool`.\nReverts if the variable could not be parsed.\nReturns `defaultValue` if the variable was not found.", "declaration": "function envOr(string calldata name, bool defaultValue) external view returns (bool value);", "visibility": "external", "mutability": "view", "signature": "envOr(string,bool)", "selector": "0x4777f3cf", "selectorBytes": [ 71, 119, 243, 207 ] }, "group": "environment", "status": "stable", "safety": "safe" }, { "func": { "id": "envOr_1", "description": "Gets the environment variable `name` and parses it as `uint256`.\nReverts if the variable could not be parsed.\nReturns `defaultValue` if the variable was not found.", "declaration": "function envOr(string calldata name, uint256 defaultValue) external view returns (uint256 value);", "visibility": "external", "mutability": "view", "signature": "envOr(string,uint256)", "selector": "0x5e97348f", "selectorBytes": [ 94, 151, 52, 143 ] }, "group": "environment", "status": "stable", "safety": "safe" }, { "func": { "id": "envOr_10", "description": "Gets the environment variable `name` and parses it as an array of `address`, delimited by `delim`.\nReverts if the variable could not be parsed.\nReturns `defaultValue` if the variable was not found.", "declaration": "function envOr(string calldata name, string calldata delim, address[] calldata defaultValue) external view returns (address[] memory value);", "visibility": "external", "mutability": "view", "signature": "envOr(string,string,address[])", "selector": "0xc74e9deb", "selectorBytes": [ 199, 78, 157, 235 ] }, "group": "environment", "status": "stable", "safety": "safe" }, { "func": { "id": "envOr_11", "description": "Gets the environment variable `name` and parses it as an array of `bytes32`, delimited by `delim`.\nReverts if the variable could not be parsed.\nReturns `defaultValue` if the variable was not found.", "declaration": "function envOr(string calldata name, string calldata delim, bytes32[] calldata defaultValue) external view returns (bytes32[] memory value);", "visibility": "external", "mutability": "view", "signature": "envOr(string,string,bytes32[])", "selector": "0x2281f367", "selectorBytes": [ 34, 129, 243, 103 ] }, "group": "environment", "status": "stable", "safety": "safe" }, { "func": { "id": "envOr_12", "description": "Gets the environment variable `name` and parses it as an array of `string`, delimited by `delim`.\nReverts if the variable could not be parsed.\nReturns `defaultValue` if the variable was not found.", "declaration": "function envOr(string calldata name, string calldata delim, string[] calldata defaultValue) external view returns (string[] memory value);", "visibility": "external", "mutability": "view", "signature": "envOr(string,string,string[])", "selector": "0x859216bc", "selectorBytes": [ 133, 146, 22, 188 ] }, "group": "environment", "status": "stable", "safety": "safe" }, { "func": { "id": "envOr_13", "description": "Gets the environment variable `name` and parses it as an array of `bytes`, delimited by `delim`.\nReverts if the variable could not be parsed.\nReturns `defaultValue` if the variable was not found.", "declaration": "function envOr(string calldata name, string calldata delim, bytes[] calldata defaultValue) external view returns (bytes[] memory value);", "visibility": "external", "mutability": "view", "signature": "envOr(string,string,bytes[])", "selector": "0x64bc3e64", "selectorBytes": [ 100, 188, 62, 100 ] }, "group": "environment", "status": "stable", "safety": "safe" }, { "func": { "id": "envOr_2", "description": "Gets the environment variable `name` and parses it as `int256`.\nReverts if the variable could not be parsed.\nReturns `defaultValue` if the variable was not found.", "declaration": "function envOr(string calldata name, int256 defaultValue) external view returns (int256 value);", "visibility": "external", "mutability": "view", "signature": "envOr(string,int256)", "selector": "0xbbcb713e", "selectorBytes": [ 187, 203, 113, 62 ] }, "group": "environment", "status": "stable", "safety": "safe" }, { "func": { "id": "envOr_3", "description": "Gets the environment variable `name` and parses it as `address`.\nReverts if the variable could not be parsed.\nReturns `defaultValue` if the variable was not found.", "declaration": "function envOr(string calldata name, address defaultValue) external view returns (address value);", "visibility": "external", "mutability": "view", "signature": "envOr(string,address)", "selector": "0x561fe540", "selectorBytes": [ 86, 31, 229, 64 ] }, "group": "environment", "status": "stable", "safety": "safe" }, { "func": { "id": "envOr_4", "description": "Gets the environment variable `name` and parses it as `bytes32`.\nReverts if the variable could not be parsed.\nReturns `defaultValue` if the variable was not found.", "declaration": "function envOr(string calldata name, bytes32 defaultValue) external view returns (bytes32 value);", "visibility": "external", "mutability": "view", "signature": "envOr(string,bytes32)", "selector": "0xb4a85892", "selectorBytes": [ 180, 168, 88, 146 ] }, "group": "environment", "status": "stable", "safety": "safe" }, { "func": { "id": "envOr_5", "description": "Gets the environment variable `name` and parses it as `string`.\nReverts if the variable could not be parsed.\nReturns `defaultValue` if the variable was not found.", "declaration": "function envOr(string calldata name, string calldata defaultValue) external view returns (string memory value);", "visibility": "external", "mutability": "view", "signature": "envOr(string,string)", "selector": "0xd145736c", "selectorBytes": [ 209, 69, 115, 108 ] }, "group": "environment", "status": "stable", "safety": "safe" }, { "func": { "id": "envOr_6", "description": "Gets the environment variable `name` and parses it as `bytes`.\nReverts if the variable could not be parsed.\nReturns `defaultValue` if the variable was not found.", "declaration": "function envOr(string calldata name, bytes calldata defaultValue) external view returns (bytes memory value);", "visibility": "external", "mutability": "view", "signature": "envOr(string,bytes)", "selector": "0xb3e47705", "selectorBytes": [ 179, 228, 119, 5 ] }, "group": "environment", "status": "stable", "safety": "safe" }, { "func": { "id": "envOr_7", "description": "Gets the environment variable `name` and parses it as an array of `bool`, delimited by `delim`.\nReverts if the variable could not be parsed.\nReturns `defaultValue` if the variable was not found.", "declaration": "function envOr(string calldata name, string calldata delim, bool[] calldata defaultValue) external view returns (bool[] memory value);", "visibility": "external", "mutability": "view", "signature": "envOr(string,string,bool[])", "selector": "0xeb85e83b", "selectorBytes": [ 235, 133, 232, 59 ] }, "group": "environment", "status": "stable", "safety": "safe" }, { "func": { "id": "envOr_8", "description": "Gets the environment variable `name` and parses it as an array of `uint256`, delimited by `delim`.\nReverts if the variable could not be parsed.\nReturns `defaultValue` if the variable was not found.", "declaration": "function envOr(string calldata name, string calldata delim, uint256[] calldata defaultValue) external view returns (uint256[] memory value);", "visibility": "external", "mutability": "view", "signature": "envOr(string,string,uint256[])", "selector": "0x74318528", "selectorBytes": [ 116, 49, 133, 40 ] }, "group": "environment", "status": "stable", "safety": "safe" }, { "func": { "id": "envOr_9", "description": "Gets the environment variable `name` and parses it as an array of `int256`, delimited by `delim`.\nReverts if the variable could not be parsed.\nReturns `defaultValue` if the variable was not found.", "declaration": "function envOr(string calldata name, string calldata delim, int256[] calldata defaultValue) external view returns (int256[] memory value);", "visibility": "external", "mutability": "view", "signature": "envOr(string,string,int256[])", "selector": "0x4700d74b", "selectorBytes": [ 71, 0, 215, 75 ] }, "group": "environment", "status": "stable", "safety": "safe" }, { "func": { "id": "envString_0", "description": "Gets the environment variable `name` and parses it as `string`.\nReverts if the variable was not found or could not be parsed.", "declaration": "function envString(string calldata name) external view returns (string memory value);", "visibility": "external", "mutability": "view", "signature": "envString(string)", "selector": "0xf877cb19", "selectorBytes": [ 248, 119, 203, 25 ] }, "group": "environment", "status": "stable", "safety": "safe" }, { "func": { "id": "envString_1", "description": "Gets the environment variable `name` and parses it as an array of `string`, delimited by `delim`.\nReverts if the variable was not found or could not be parsed.", "declaration": "function envString(string calldata name, string calldata delim) external view returns (string[] memory value);", "visibility": "external", "mutability": "view", "signature": "envString(string,string)", "selector": "0x14b02bc9", "selectorBytes": [ 20, 176, 43, 201 ] }, "group": "environment", "status": "stable", "safety": "safe" }, { "func": { "id": "envUint_0", "description": "Gets the environment variable `name` and parses it as `uint256`.\nReverts if the variable was not found or could not be parsed.", "declaration": "function envUint(string calldata name) external view returns (uint256 value);", "visibility": "external", "mutability": "view", "signature": "envUint(string)", "selector": "0xc1978d1f", "selectorBytes": [ 193, 151, 141, 31 ] }, "group": "environment", "status": "stable", "safety": "safe" }, { "func": { "id": "envUint_1", "description": "Gets the environment variable `name` and parses it as an array of `uint256`, delimited by `delim`.\nReverts if the variable was not found or could not be parsed.", "declaration": "function envUint(string calldata name, string calldata delim) external view returns (uint256[] memory value);", "visibility": "external", "mutability": "view", "signature": "envUint(string,string)", "selector": "0xf3dec099", "selectorBytes": [ 243, 222, 192, 153 ] }, "group": "environment", "status": "stable", "safety": "safe" }, { "func": { "id": "etch", "description": "Sets an address' code.", "declaration": "function etch(address target, bytes calldata newRuntimeBytecode) external;", "visibility": "external", "mutability": "", "signature": "etch(address,bytes)", "selector": "0xb4d6c782", "selectorBytes": [ 180, 214, 199, 130 ] }, "group": "evm", "status": "stable", "safety": "unsafe" }, { "func": { "id": "eth_getLogs", "description": "Gets all the logs according to specified filter.", "declaration": "function eth_getLogs(uint256 fromBlock, uint256 toBlock, address target, bytes32[] calldata topics) external view returns (EthGetLogs[] memory logs);", "visibility": "external", "mutability": "view", "signature": "eth_getLogs(uint256,uint256,address,bytes32[])", "selector": "0x35e1349b", "selectorBytes": [ 53, 225, 52, 155 ] }, "group": "evm", "status": "stable", "safety": "safe" }, { "func": { "id": "executeTransaction", "description": "Executes an RLP-encoded signed transaction with full EVM semantics (like `--isolate` mode).\nThe transaction is decoded from EIP-2718 format (type byte prefix + RLP payload) or legacy RLP.\nReturns the execution output bytes.\nThis cheatcode is not allowed in `forge script` contexts.", "declaration": "function executeTransaction(bytes calldata rawTx) external returns (bytes memory);", "visibility": "external", "mutability": "", "signature": "executeTransaction(bytes)", "selector": "0x943d7209", "selectorBytes": [ 148, 61, 114, 9 ] }, "group": "evm", "status": "stable", "safety": "unsafe" }, { "func": { "id": "exists", "description": "Returns true if the given path points to an existing entity, else returns false.", "declaration": "function exists(string calldata path) external view returns (bool result);", "visibility": "external", "mutability": "view", "signature": "exists(string)", "selector": "0x261a323e", "selectorBytes": [ 38, 26, 50, 62 ] }, "group": "filesystem", "status": "stable", "safety": "safe" }, { "func": { "id": "expectCallMinGas_0", "description": "Expect a call to an address with the specified `msg.value` and calldata, and a *minimum* amount of gas.", "declaration": "function expectCallMinGas(address callee, uint256 msgValue, uint64 minGas, bytes calldata data) external;", "visibility": "external", "mutability": "", "signature": "expectCallMinGas(address,uint256,uint64,bytes)", "selector": "0x08e4e116", "selectorBytes": [ 8, 228, 225, 22 ] }, "group": "testing", "status": "stable", "safety": "unsafe" }, { "func": { "id": "expectCallMinGas_1", "description": "Expect given number of calls to an address with the specified `msg.value` and calldata, and a *minimum* amount of gas.", "declaration": "function expectCallMinGas(address callee, uint256 msgValue, uint64 minGas, bytes calldata data, uint64 count) external;", "visibility": "external", "mutability": "", "signature": "expectCallMinGas(address,uint256,uint64,bytes,uint64)", "selector": "0xe13a1834", "selectorBytes": [ 225, 58, 24, 52 ] }, "group": "testing", "status": "stable", "safety": "unsafe" }, { "func": { "id": "expectCall_0", "description": "Expects a call to an address with the specified calldata.\nCalldata can either be a strict or a partial match.", "declaration": "function expectCall(address callee, bytes calldata data) external;", "visibility": "external", "mutability": "", "signature": "expectCall(address,bytes)", "selector": "0xbd6af434", "selectorBytes": [ 189, 106, 244, 52 ] }, "group": "testing", "status": "stable", "safety": "unsafe" }, { "func": { "id": "expectCall_1", "description": "Expects given number of calls to an address with the specified calldata.", "declaration": "function expectCall(address callee, bytes calldata data, uint64 count) external;", "visibility": "external", "mutability": "", "signature": "expectCall(address,bytes,uint64)", "selector": "0xc1adbbff", "selectorBytes": [ 193, 173, 187, 255 ] }, "group": "testing", "status": "stable", "safety": "unsafe" }, { "func": { "id": "expectCall_2", "description": "Expects a call to an address with the specified `msg.value` and calldata.", "declaration": "function expectCall(address callee, uint256 msgValue, bytes calldata data) external;", "visibility": "external", "mutability": "", "signature": "expectCall(address,uint256,bytes)", "selector": "0xf30c7ba3", "selectorBytes": [ 243, 12, 123, 163 ] }, "group": "testing", "status": "stable", "safety": "unsafe" }, { "func": { "id": "expectCall_3", "description": "Expects given number of calls to an address with the specified `msg.value` and calldata.", "declaration": "function expectCall(address callee, uint256 msgValue, bytes calldata data, uint64 count) external;", "visibility": "external", "mutability": "", "signature": "expectCall(address,uint256,bytes,uint64)", "selector": "0xa2b1a1ae", "selectorBytes": [ 162, 177, 161, 174 ] }, "group": "testing", "status": "stable", "safety": "unsafe" }, { "func": { "id": "expectCall_4", "description": "Expect a call to an address with the specified `msg.value`, gas, and calldata.", "declaration": "function expectCall(address callee, uint256 msgValue, uint64 gas, bytes calldata data) external;", "visibility": "external", "mutability": "", "signature": "expectCall(address,uint256,uint64,bytes)", "selector": "0x23361207", "selectorBytes": [ 35, 54, 18, 7 ] }, "group": "testing", "status": "stable", "safety": "unsafe" }, { "func": { "id": "expectCall_5", "description": "Expects given number of calls to an address with the specified `msg.value`, gas, and calldata.", "declaration": "function expectCall(address callee, uint256 msgValue, uint64 gas, bytes calldata data, uint64 count) external;", "visibility": "external", "mutability": "", "signature": "expectCall(address,uint256,uint64,bytes,uint64)", "selector": "0x65b7b7cc", "selectorBytes": [ 101, 183, 183, 204 ] }, "group": "testing", "status": "stable", "safety": "unsafe" }, { "func": { "id": "expectCreate", "description": "Expects the deployment of the specified bytecode by the specified address using the CREATE opcode", "declaration": "function expectCreate(bytes calldata bytecode, address deployer) external;", "visibility": "external", "mutability": "", "signature": "expectCreate(bytes,address)", "selector": "0x73cdce36", "selectorBytes": [ 115, 205, 206, 54 ] }, "group": "testing", "status": "stable", "safety": "unsafe" }, { "func": { "id": "expectCreate2", "description": "Expects the deployment of the specified bytecode by the specified address using the CREATE2 opcode", "declaration": "function expectCreate2(bytes calldata bytecode, address deployer) external;", "visibility": "external", "mutability": "", "signature": "expectCreate2(bytes,address)", "selector": "0xea54a472", "selectorBytes": [ 234, 84, 164, 114 ] }, "group": "testing", "status": "stable", "safety": "unsafe" }, { "func": { "id": "expectEmitAnonymous_0", "description": "Prepare an expected anonymous log with (bool checkTopic1, bool checkTopic2, bool checkTopic3, bool checkData.).\nCall this function, then emit an anonymous event, then call a function. Internally after the call, we check if\nlogs were emitted in the expected order with the expected topics and data (as specified by the booleans).", "declaration": "function expectEmitAnonymous(bool checkTopic0, bool checkTopic1, bool checkTopic2, bool checkTopic3, bool checkData) external;", "visibility": "external", "mutability": "", "signature": "expectEmitAnonymous(bool,bool,bool,bool,bool)", "selector": "0xc948db5e", "selectorBytes": [ 201, 72, 219, 94 ] }, "group": "testing", "status": "stable", "safety": "unsafe" }, { "func": { "id": "expectEmitAnonymous_1", "description": "Same as the previous method, but also checks supplied address against emitting contract.", "declaration": "function expectEmitAnonymous(bool checkTopic0, bool checkTopic1, bool checkTopic2, bool checkTopic3, bool checkData, address emitter) external;", "visibility": "external", "mutability": "", "signature": "expectEmitAnonymous(bool,bool,bool,bool,bool,address)", "selector": "0x71c95899", "selectorBytes": [ 113, 201, 88, 153 ] }, "group": "testing", "status": "stable", "safety": "unsafe" }, { "func": { "id": "expectEmitAnonymous_2", "description": "Prepare an expected anonymous log with all topic and data checks enabled.\nCall this function, then emit an anonymous event, then call a function. Internally after the call, we check if\nlogs were emitted in the expected order with the expected topics and data.", "declaration": "function expectEmitAnonymous() external;", "visibility": "external", "mutability": "", "signature": "expectEmitAnonymous()", "selector": "0x2e5f270c", "selectorBytes": [ 46, 95, 39, 12 ] }, "group": "testing", "status": "stable", "safety": "unsafe" }, { "func": { "id": "expectEmitAnonymous_3", "description": "Same as the previous method, but also checks supplied address against emitting contract.", "declaration": "function expectEmitAnonymous(address emitter) external;", "visibility": "external", "mutability": "", "signature": "expectEmitAnonymous(address)", "selector": "0x6fc68705", "selectorBytes": [ 111, 198, 135, 5 ] }, "group": "testing", "status": "stable", "safety": "unsafe" }, { "func": { "id": "expectEmit_0", "description": "Prepare an expected log with (bool checkTopic1, bool checkTopic2, bool checkTopic3, bool checkData.).\nCall this function, then emit an event, then call a function. Internally after the call, we check if\nlogs were emitted in the expected order with the expected topics and data (as specified by the booleans).", "declaration": "function expectEmit(bool checkTopic1, bool checkTopic2, bool checkTopic3, bool checkData) external;", "visibility": "external", "mutability": "", "signature": "expectEmit(bool,bool,bool,bool)", "selector": "0x491cc7c2", "selectorBytes": [ 73, 28, 199, 194 ] }, "group": "testing", "status": "stable", "safety": "unsafe" }, { "func": { "id": "expectEmit_1", "description": "Same as the previous method, but also checks supplied address against emitting contract.", "declaration": "function expectEmit(bool checkTopic1, bool checkTopic2, bool checkTopic3, bool checkData, address emitter) external;", "visibility": "external", "mutability": "", "signature": "expectEmit(bool,bool,bool,bool,address)", "selector": "0x81bad6f3", "selectorBytes": [ 129, 186, 214, 243 ] }, "group": "testing", "status": "stable", "safety": "unsafe" }, { "func": { "id": "expectEmit_2", "description": "Prepare an expected log with all topic and data checks enabled.\nCall this function, then emit an event, then call a function. Internally after the call, we check if\nlogs were emitted in the expected order with the expected topics and data.", "declaration": "function expectEmit() external;", "visibility": "external", "mutability": "", "signature": "expectEmit()", "selector": "0x440ed10d", "selectorBytes": [ 68, 14, 209, 13 ] }, "group": "testing", "status": "stable", "safety": "unsafe" }, { "func": { "id": "expectEmit_3", "description": "Same as the previous method, but also checks supplied address against emitting contract.", "declaration": "function expectEmit(address emitter) external;", "visibility": "external", "mutability": "", "signature": "expectEmit(address)", "selector": "0x86b9620d", "selectorBytes": [ 134, 185, 98, 13 ] }, "group": "testing", "status": "stable", "safety": "unsafe" }, { "func": { "id": "expectEmit_4", "description": "Expect a given number of logs with the provided topics.", "declaration": "function expectEmit(bool checkTopic1, bool checkTopic2, bool checkTopic3, bool checkData, uint64 count) external;", "visibility": "external", "mutability": "", "signature": "expectEmit(bool,bool,bool,bool,uint64)", "selector": "0x5e1d1c33", "selectorBytes": [ 94, 29, 28, 51 ] }, "group": "testing", "status": "stable", "safety": "unsafe" }, { "func": { "id": "expectEmit_5", "description": "Expect a given number of logs from a specific emitter with the provided topics.", "declaration": "function expectEmit(bool checkTopic1, bool checkTopic2, bool checkTopic3, bool checkData, address emitter, uint64 count) external;", "visibility": "external", "mutability": "", "signature": "expectEmit(bool,bool,bool,bool,address,uint64)", "selector": "0xc339d02c", "selectorBytes": [ 195, 57, 208, 44 ] }, "group": "testing", "status": "stable", "safety": "unsafe" }, { "func": { "id": "expectEmit_6", "description": "Expect a given number of logs with all topic and data checks enabled.", "declaration": "function expectEmit(uint64 count) external;", "visibility": "external", "mutability": "", "signature": "expectEmit(uint64)", "selector": "0x4c74a335", "selectorBytes": [ 76, 116, 163, 53 ] }, "group": "testing", "status": "stable", "safety": "unsafe" }, { "func": { "id": "expectEmit_7", "description": "Expect a given number of logs from a specific emitter with all topic and data checks enabled.", "declaration": "function expectEmit(address emitter, uint64 count) external;", "visibility": "external", "mutability": "", "signature": "expectEmit(address,uint64)", "selector": "0xb43aece3", "selectorBytes": [ 180, 58, 236, 227 ] }, "group": "testing", "status": "stable", "safety": "unsafe" }, { "func": { "id": "expectPartialRevert_0", "description": "Expects an error on next call that starts with the revert data.", "declaration": "function expectPartialRevert(bytes4 revertData) external;", "visibility": "external", "mutability": "", "signature": "expectPartialRevert(bytes4)", "selector": "0x11fb5b9c", "selectorBytes": [ 17, 251, 91, 156 ] }, "group": "testing", "status": "stable", "safety": "unsafe" }, { "func": { "id": "expectPartialRevert_1", "description": "Expects an error on next call to reverter address, that starts with the revert data.", "declaration": "function expectPartialRevert(bytes4 revertData, address reverter) external;", "visibility": "external", "mutability": "", "signature": "expectPartialRevert(bytes4,address)", "selector": "0x51aa008a", "selectorBytes": [ 81, 170, 0, 138 ] }, "group": "testing", "status": "stable", "safety": "unsafe" }, { "func": { "id": "expectRevert_0", "description": "Expects an error on next call with any revert data.", "declaration": "function expectRevert() external;", "visibility": "external", "mutability": "", "signature": "expectRevert()", "selector": "0xf4844814", "selectorBytes": [ 244, 132, 72, 20 ] }, "group": "testing", "status": "stable", "safety": "unsafe" }, { "func": { "id": "expectRevert_1", "description": "Expects an error on next call that exactly matches the revert data.", "declaration": "function expectRevert(bytes4 revertData) external;", "visibility": "external", "mutability": "", "signature": "expectRevert(bytes4)", "selector": "0xc31eb0e0", "selectorBytes": [ 195, 30, 176, 224 ] }, "group": "testing", "status": "stable", "safety": "unsafe" }, { "func": { "id": "expectRevert_10", "description": "Expects a `count` number of reverts from the upcoming calls from the reverter address that match the revert data.", "declaration": "function expectRevert(bytes4 revertData, address reverter, uint64 count) external;", "visibility": "external", "mutability": "", "signature": "expectRevert(bytes4,address,uint64)", "selector": "0xb0762d73", "selectorBytes": [ 176, 118, 45, 115 ] }, "group": "testing", "status": "stable", "safety": "unsafe" }, { "func": { "id": "expectRevert_11", "description": "Expects a `count` number of reverts from the upcoming calls from the reverter address that exactly match the revert data.", "declaration": "function expectRevert(bytes calldata revertData, address reverter, uint64 count) external;", "visibility": "external", "mutability": "", "signature": "expectRevert(bytes,address,uint64)", "selector": "0xd345fb1f", "selectorBytes": [ 211, 69, 251, 31 ] }, "group": "testing", "status": "stable", "safety": "unsafe" }, { "func": { "id": "expectRevert_2", "description": "Expects an error on next call that exactly matches the revert data.", "declaration": "function expectRevert(bytes calldata revertData) external;", "visibility": "external", "mutability": "", "signature": "expectRevert(bytes)", "selector": "0xf28dceb3", "selectorBytes": [ 242, 141, 206, 179 ] }, "group": "testing", "status": "stable", "safety": "unsafe" }, { "func": { "id": "expectRevert_3", "description": "Expects an error with any revert data on next call to reverter address.", "declaration": "function expectRevert(address reverter) external;", "visibility": "external", "mutability": "", "signature": "expectRevert(address)", "selector": "0xd814f38a", "selectorBytes": [ 216, 20, 243, 138 ] }, "group": "testing", "status": "stable", "safety": "unsafe" }, { "func": { "id": "expectRevert_4", "description": "Expects an error from reverter address on next call, with any revert data.", "declaration": "function expectRevert(bytes4 revertData, address reverter) external;", "visibility": "external", "mutability": "", "signature": "expectRevert(bytes4,address)", "selector": "0x260bc5de", "selectorBytes": [ 38, 11, 197, 222 ] }, "group": "testing", "status": "stable", "safety": "unsafe" }, { "func": { "id": "expectRevert_5", "description": "Expects an error from reverter address on next call, that exactly matches the revert data.", "declaration": "function expectRevert(bytes calldata revertData, address reverter) external;", "visibility": "external", "mutability": "", "signature": "expectRevert(bytes,address)", "selector": "0x61ebcf12", "selectorBytes": [ 97, 235, 207, 18 ] }, "group": "testing", "status": "stable", "safety": "unsafe" }, { "func": { "id": "expectRevert_6", "description": "Expects a `count` number of reverts from the upcoming calls with any revert data or reverter.", "declaration": "function expectRevert(uint64 count) external;", "visibility": "external", "mutability": "", "signature": "expectRevert(uint64)", "selector": "0x4ee38244", "selectorBytes": [ 78, 227, 130, 68 ] }, "group": "testing", "status": "stable", "safety": "unsafe" }, { "func": { "id": "expectRevert_7", "description": "Expects a `count` number of reverts from the upcoming calls that match the revert data.", "declaration": "function expectRevert(bytes4 revertData, uint64 count) external;", "visibility": "external", "mutability": "", "signature": "expectRevert(bytes4,uint64)", "selector": "0xe45ca72d", "selectorBytes": [ 228, 92, 167, 45 ] }, "group": "testing", "status": "stable", "safety": "unsafe" }, { "func": { "id": "expectRevert_8", "description": "Expects a `count` number of reverts from the upcoming calls that exactly match the revert data.", "declaration": "function expectRevert(bytes calldata revertData, uint64 count) external;", "visibility": "external", "mutability": "", "signature": "expectRevert(bytes,uint64)", "selector": "0x4994c273", "selectorBytes": [ 73, 148, 194, 115 ] }, "group": "testing", "status": "stable", "safety": "unsafe" }, { "func": { "id": "expectRevert_9", "description": "Expects a `count` number of reverts from the upcoming calls from the reverter address.", "declaration": "function expectRevert(address reverter, uint64 count) external;", "visibility": "external", "mutability": "", "signature": "expectRevert(address,uint64)", "selector": "0x1ff5f952", "selectorBytes": [ 31, 245, 249, 82 ] }, "group": "testing", "status": "stable", "safety": "unsafe" }, { "func": { "id": "expectSafeMemory", "description": "Only allows memory writes to offsets [0x00, 0x60) ∪ [min, max) in the current subcontext. If any other\nmemory is written to, the test will fail. Can be called multiple times to add more ranges to the set.", "declaration": "function expectSafeMemory(uint64 min, uint64 max) external;", "visibility": "external", "mutability": "", "signature": "expectSafeMemory(uint64,uint64)", "selector": "0x6d016688", "selectorBytes": [ 109, 1, 102, 136 ] }, "group": "testing", "status": "stable", "safety": "unsafe" }, { "func": { "id": "expectSafeMemoryCall", "description": "Only allows memory writes to offsets [0x00, 0x60) ∪ [min, max) in the next created subcontext.\nIf any other memory is written to, the test will fail. Can be called multiple times to add more ranges\nto the set.", "declaration": "function expectSafeMemoryCall(uint64 min, uint64 max) external;", "visibility": "external", "mutability": "", "signature": "expectSafeMemoryCall(uint64,uint64)", "selector": "0x05838bf4", "selectorBytes": [ 5, 131, 139, 244 ] }, "group": "testing", "status": "stable", "safety": "unsafe" }, { "func": { "id": "fee", "description": "Sets `block.basefee`.", "declaration": "function fee(uint256 newBasefee) external;", "visibility": "external", "mutability": "", "signature": "fee(uint256)", "selector": "0x39b37ab0", "selectorBytes": [ 57, 179, 122, 176 ] }, "group": "evm", "status": "stable", "safety": "unsafe" }, { "func": { "id": "ffi", "description": "Performs a foreign function call via the terminal.", "declaration": "function ffi(string[] calldata commandInput) external returns (bytes memory result);", "visibility": "external", "mutability": "", "signature": "ffi(string[])", "selector": "0x89160467", "selectorBytes": [ 137, 22, 4, 103 ] }, "group": "filesystem", "status": "stable", "safety": "safe" }, { "func": { "id": "foundryVersionAtLeast", "description": "Returns true if the current Foundry version is greater than or equal to the given version.\nThe given version string must be in the format `major.minor.patch`.\nThis is equivalent to `foundryVersionCmp(version) >= 0`.", "declaration": "function foundryVersionAtLeast(string calldata version) external view returns (bool);", "visibility": "external", "mutability": "view", "signature": "foundryVersionAtLeast(string)", "selector": "0x6248be1f", "selectorBytes": [ 98, 72, 190, 31 ] }, "group": "testing", "status": "stable", "safety": "safe" }, { "func": { "id": "foundryVersionCmp", "description": "Compares the current Foundry version with the given version string.\nThe given version string must be in the format `major.minor.patch`.\nReturns:\n-1 if current Foundry version is less than the given version\n0 if current Foundry version equals the given version\n1 if current Foundry version is greater than the given version\nThis result can then be used with a comparison operator against `0`.\nFor example, to check if the current Foundry version is greater than or equal to `1.0.0`:\n`if (foundryVersionCmp(\"1.0.0\") >= 0) { ... }`", "declaration": "function foundryVersionCmp(string calldata version) external view returns (int256);", "visibility": "external", "mutability": "view", "signature": "foundryVersionCmp(string)", "selector": "0xca7b0a09", "selectorBytes": [ 202, 123, 10, 9 ] }, "group": "testing", "status": "stable", "safety": "safe" }, { "func": { "id": "fromRlp", "description": "RLP decodes an RLP payload into a list of bytes.", "declaration": "function fromRlp(bytes calldata rlp) external pure returns (bytes[] memory data);", "visibility": "external", "mutability": "pure", "signature": "fromRlp(bytes)", "selector": "0x1e1d8b63", "selectorBytes": [ 30, 29, 139, 99 ] }, "group": "utilities", "status": "stable", "safety": "safe" }, { "func": { "id": "fsMetadata", "description": "Given a path, query the file system to get information about a file, directory, etc.", "declaration": "function fsMetadata(string calldata path) external view returns (FsMetadata memory metadata);", "visibility": "external", "mutability": "view", "signature": "fsMetadata(string)", "selector": "0xaf368a08", "selectorBytes": [ 175, 54, 138, 8 ] }, "group": "filesystem", "status": "stable", "safety": "safe" }, { "func": { "id": "getArtifactPathByCode", "description": "Gets the artifact path from code (aka. creation code).", "declaration": "function getArtifactPathByCode(bytes calldata code) external view returns (string memory path);", "visibility": "external", "mutability": "view", "signature": "getArtifactPathByCode(bytes)", "selector": "0xeb74848c", "selectorBytes": [ 235, 116, 132, 140 ] }, "group": "filesystem", "status": "stable", "safety": "safe" }, { "func": { "id": "getArtifactPathByDeployedCode", "description": "Gets the artifact path from deployed code (aka. runtime code).", "declaration": "function getArtifactPathByDeployedCode(bytes calldata deployedCode) external view returns (string memory path);", "visibility": "external", "mutability": "view", "signature": "getArtifactPathByDeployedCode(bytes)", "selector": "0x6d853ba5", "selectorBytes": [ 109, 133, 59, 165 ] }, "group": "filesystem", "status": "stable", "safety": "safe" }, { "func": { "id": "getBlobBaseFee", "description": "Gets the current `block.blobbasefee`.\nYou should use this instead of `block.blobbasefee` if you use `vm.blobBaseFee`, as `block.blobbasefee` is assumed to be constant across a transaction,\nand as a result will get optimized out by the compiler.\nSee https://github.com/foundry-rs/foundry/issues/6180", "declaration": "function getBlobBaseFee() external view returns (uint256 blobBaseFee);", "visibility": "external", "mutability": "view", "signature": "getBlobBaseFee()", "selector": "0x1f6d6ef7", "selectorBytes": [ 31, 109, 110, 247 ] }, "group": "evm", "status": "stable", "safety": "safe" }, { "func": { "id": "getBlobhashes", "description": "Gets the blockhashes from the current transaction.\nNot available on EVM versions before Cancun.\nIf used on unsupported EVM versions it will revert.", "declaration": "function getBlobhashes() external view returns (bytes32[] memory hashes);", "visibility": "external", "mutability": "view", "signature": "getBlobhashes()", "selector": "0xf56ff18b", "selectorBytes": [ 245, 111, 241, 139 ] }, "group": "evm", "status": "stable", "safety": "unsafe" }, { "func": { "id": "getBlockNumber", "description": "Gets the current `block.number`.\nYou should use this instead of `block.number` if you use `vm.roll`, as `block.number` is assumed to be constant across a transaction,\nand as a result will get optimized out by the compiler.\nSee https://github.com/foundry-rs/foundry/issues/6180", "declaration": "function getBlockNumber() external view returns (uint256 height);", "visibility": "external", "mutability": "view", "signature": "getBlockNumber()", "selector": "0x42cbb15c", "selectorBytes": [ 66, 203, 177, 92 ] }, "group": "evm", "status": "stable", "safety": "safe" }, { "func": { "id": "getBlockTimestamp", "description": "Gets the current `block.timestamp`.\nYou should use this instead of `block.timestamp` if you use `vm.warp`, as `block.timestamp` is assumed to be constant across a transaction,\nand as a result will get optimized out by the compiler.\nSee https://github.com/foundry-rs/foundry/issues/6180", "declaration": "function getBlockTimestamp() external view returns (uint256 timestamp);", "visibility": "external", "mutability": "view", "signature": "getBlockTimestamp()", "selector": "0x796b89b9", "selectorBytes": [ 121, 107, 137, 185 ] }, "group": "evm", "status": "stable", "safety": "safe" }, { "func": { "id": "getBroadcast", "description": "Returns the most recent broadcast for the given contract on `chainId` matching `txType`.\nFor example:\nThe most recent deployment can be fetched by passing `txType` as `CREATE` or `CREATE2`.\nThe most recent call can be fetched by passing `txType` as `CALL`.", "declaration": "function getBroadcast(string calldata contractName, uint64 chainId, BroadcastTxType txType) external view returns (BroadcastTxSummary memory);", "visibility": "external", "mutability": "view", "signature": "getBroadcast(string,uint64,uint8)", "selector": "0x3dc90cb3", "selectorBytes": [ 61, 201, 12, 179 ] }, "group": "filesystem", "status": "stable", "safety": "safe" }, { "func": { "id": "getBroadcasts_0", "description": "Returns all broadcasts for the given contract on `chainId` with the specified `txType`.\nSorted such that the most recent broadcast is the first element, and the oldest is the last. i.e descending order of BroadcastTxSummary.blockNumber.", "declaration": "function getBroadcasts(string calldata contractName, uint64 chainId, BroadcastTxType txType) external view returns (BroadcastTxSummary[] memory);", "visibility": "external", "mutability": "view", "signature": "getBroadcasts(string,uint64,uint8)", "selector": "0xf7afe919", "selectorBytes": [ 247, 175, 233, 25 ] }, "group": "filesystem", "status": "stable", "safety": "safe" }, { "func": { "id": "getBroadcasts_1", "description": "Returns all broadcasts for the given contract on `chainId`.\nSorted such that the most recent broadcast is the first element, and the oldest is the last. i.e descending order of BroadcastTxSummary.blockNumber.", "declaration": "function getBroadcasts(string calldata contractName, uint64 chainId) external view returns (BroadcastTxSummary[] memory);", "visibility": "external", "mutability": "view", "signature": "getBroadcasts(string,uint64)", "selector": "0xf2fa4a26", "selectorBytes": [ 242, 250, 74, 38 ] }, "group": "filesystem", "status": "stable", "safety": "safe" }, { "func": { "id": "getChainId", "description": "Gets the current `block.chainid` of the currently selected environment.\nYou should use this instead of `block.chainid` if you use `vm.selectFork` or `vm.createSelectFork`, as `block.chainid` could be assumed\nto be constant across a transaction, and as a result will get optimized out by the compiler.\nSee https://github.com/foundry-rs/foundry/issues/6180", "declaration": "function getChainId() external view returns (uint256 blockChainId);", "visibility": "external", "mutability": "view", "signature": "getChainId()", "selector": "0x3408e470", "selectorBytes": [ 52, 8, 228, 112 ] }, "group": "evm", "status": "stable", "safety": "safe" }, { "func": { "id": "getChain_0", "description": "Returns a Chain struct for specific alias", "declaration": "function getChain(string calldata chainAlias) external view returns (Chain memory chain);", "visibility": "external", "mutability": "view", "signature": "getChain(string)", "selector": "0x4cc1c2bb", "selectorBytes": [ 76, 193, 194, 187 ] }, "group": "testing", "status": "stable", "safety": "safe" }, { "func": { "id": "getChain_1", "description": "Returns a Chain struct for specific chainId", "declaration": "function getChain(uint256 chainId) external view returns (Chain memory chain);", "visibility": "external", "mutability": "view", "signature": "getChain(uint256)", "selector": "0xb6791ad4", "selectorBytes": [ 182, 121, 26, 212 ] }, "group": "testing", "status": "stable", "safety": "safe" }, { "func": { "id": "getCode", "description": "Gets the creation bytecode from an artifact file. Takes in the relative path to the json file or the path to the\nartifact in the form of :: where and parts are optional.", "declaration": "function getCode(string calldata artifactPath) external view returns (bytes memory creationBytecode);", "visibility": "external", "mutability": "view", "signature": "getCode(string)", "selector": "0x8d1cc925", "selectorBytes": [ 141, 28, 201, 37 ] }, "group": "filesystem", "status": "stable", "safety": "safe" }, { "func": { "id": "getDeployedCode", "description": "Gets the deployed bytecode from an artifact file. Takes in the relative path to the json file or the path to the\nartifact in the form of :: where and parts are optional.", "declaration": "function getDeployedCode(string calldata artifactPath) external view returns (bytes memory runtimeBytecode);", "visibility": "external", "mutability": "view", "signature": "getDeployedCode(string)", "selector": "0x3ebf73b4", "selectorBytes": [ 62, 191, 115, 180 ] }, "group": "filesystem", "status": "stable", "safety": "safe" }, { "func": { "id": "getDeployment_0", "description": "Returns the most recent deployment for the current `chainId`.", "declaration": "function getDeployment(string calldata contractName) external view returns (address deployedAddress);", "visibility": "external", "mutability": "view", "signature": "getDeployment(string)", "selector": "0xa8091d97", "selectorBytes": [ 168, 9, 29, 151 ] }, "group": "filesystem", "status": "stable", "safety": "safe" }, { "func": { "id": "getDeployment_1", "description": "Returns the most recent deployment for the given contract on `chainId`", "declaration": "function getDeployment(string calldata contractName, uint64 chainId) external view returns (address deployedAddress);", "visibility": "external", "mutability": "view", "signature": "getDeployment(string,uint64)", "selector": "0x0debd5d6", "selectorBytes": [ 13, 235, 213, 214 ] }, "group": "filesystem", "status": "stable", "safety": "safe" }, { "func": { "id": "getDeployments", "description": "Returns all deployments for the given contract on `chainId`\nSorted in descending order of deployment time i.e descending order of BroadcastTxSummary.blockNumber.\nThe most recent deployment is the first element, and the oldest is the last.", "declaration": "function getDeployments(string calldata contractName, uint64 chainId) external view returns (address[] memory deployedAddresses);", "visibility": "external", "mutability": "view", "signature": "getDeployments(string,uint64)", "selector": "0x74e133dd", "selectorBytes": [ 116, 225, 51, 221 ] }, "group": "filesystem", "status": "stable", "safety": "safe" }, { "func": { "id": "getEvmVersion", "description": "Returns the test or script execution evm version.\n**Note:** The execution evm version is not the same as the compilation one.", "declaration": "function getEvmVersion() external pure returns (string memory evm);", "visibility": "external", "mutability": "pure", "signature": "getEvmVersion()", "selector": "0xaa2bb222", "selectorBytes": [ 170, 43, 178, 34 ] }, "group": "evm", "status": "stable", "safety": "safe" }, { "func": { "id": "getFoundryVersion", "description": "Returns the Foundry version.\nFormat: -+..\nSample output: 0.3.0-nightly+3cb96bde9b.1737036656.debug\nNote: Build timestamps may vary slightly across platforms due to separate CI jobs.\nFor reliable version comparisons, use UNIX format (e.g., >= 1700000000)\nto compare timestamps while ignoring minor time differences.", "declaration": "function getFoundryVersion() external view returns (string memory version);", "visibility": "external", "mutability": "view", "signature": "getFoundryVersion()", "selector": "0xea991bb5", "selectorBytes": [ 234, 153, 27, 181 ] }, "group": "testing", "status": "stable", "safety": "safe" }, { "func": { "id": "getLabel", "description": "Gets the label for the specified address.", "declaration": "function getLabel(address account) external view returns (string memory currentLabel);", "visibility": "external", "mutability": "view", "signature": "getLabel(address)", "selector": "0x28a249b0", "selectorBytes": [ 40, 162, 73, 176 ] }, "group": "utilities", "status": "stable", "safety": "safe" }, { "func": { "id": "getMappingKeyAndParentOf", "description": "Gets the map key and parent of a mapping at a given slot, for a given address.", "declaration": "function getMappingKeyAndParentOf(address target, bytes32 elementSlot) external view returns (bool found, bytes32 key, bytes32 parent);", "visibility": "external", "mutability": "view", "signature": "getMappingKeyAndParentOf(address,bytes32)", "selector": "0x876e24e6", "selectorBytes": [ 135, 110, 36, 230 ] }, "group": "evm", "status": "stable", "safety": "safe" }, { "func": { "id": "getMappingLength", "description": "Gets the number of elements in the mapping at the given slot, for a given address.", "declaration": "function getMappingLength(address target, bytes32 mappingSlot) external view returns (uint256 length);", "visibility": "external", "mutability": "view", "signature": "getMappingLength(address,bytes32)", "selector": "0x2f2fd63f", "selectorBytes": [ 47, 47, 214, 63 ] }, "group": "evm", "status": "stable", "safety": "safe" }, { "func": { "id": "getMappingSlotAt", "description": "Gets the elements at index idx of the mapping at the given slot, for a given address. The\nindex must be less than the length of the mapping (i.e. the number of keys in the mapping).", "declaration": "function getMappingSlotAt(address target, bytes32 mappingSlot, uint256 idx) external view returns (bytes32 value);", "visibility": "external", "mutability": "view", "signature": "getMappingSlotAt(address,bytes32,uint256)", "selector": "0xebc73ab4", "selectorBytes": [ 235, 199, 58, 180 ] }, "group": "evm", "status": "stable", "safety": "safe" }, { "func": { "id": "getNonce_0", "description": "Gets the nonce of an account.", "declaration": "function getNonce(address account) external view returns (uint64 nonce);", "visibility": "external", "mutability": "view", "signature": "getNonce(address)", "selector": "0x2d0335ab", "selectorBytes": [ 45, 3, 53, 171 ] }, "group": "evm", "status": "stable", "safety": "safe" }, { "func": { "id": "getNonce_1", "description": "Get the nonce of a `Wallet`.", "declaration": "function getNonce(Wallet calldata wallet) external view returns (uint64 nonce);", "visibility": "external", "mutability": "view", "signature": "getNonce((address,uint256,uint256,uint256))", "selector": "0xa5748aad", "selectorBytes": [ 165, 116, 138, 173 ] }, "group": "evm", "status": "stable", "safety": "safe" }, { "func": { "id": "getRawBlockHeader", "description": "Gets the RLP encoded block header for a given block number.\nReturns the block header in the same format as `cast block --raw`.", "declaration": "function getRawBlockHeader(uint256 blockNumber) external view returns (bytes memory rlpHeader);", "visibility": "external", "mutability": "view", "signature": "getRawBlockHeader(uint256)", "selector": "0x2c667606", "selectorBytes": [ 44, 102, 118, 6 ] }, "group": "evm", "status": "stable", "safety": "safe" }, { "func": { "id": "getRecordedLogs", "description": "Gets all the recorded logs.", "declaration": "function getRecordedLogs() external view returns (Log[] memory logs);", "visibility": "external", "mutability": "view", "signature": "getRecordedLogs()", "selector": "0x191553a4", "selectorBytes": [ 25, 21, 83, 164 ] }, "group": "evm", "status": "stable", "safety": "safe" }, { "func": { "id": "getRecordedLogsJson", "description": "Gets all the recorded logs, in JSON format.", "declaration": "function getRecordedLogsJson() external view returns (string memory logsJson);", "visibility": "external", "mutability": "view", "signature": "getRecordedLogsJson()", "selector": "0x3b171111", "selectorBytes": [ 59, 23, 17, 17 ] }, "group": "evm", "status": "stable", "safety": "safe" }, { "func": { "id": "getStateDiff", "description": "Returns state diffs from current `vm.startStateDiffRecording` session.", "declaration": "function getStateDiff() external view returns (string memory diff);", "visibility": "external", "mutability": "view", "signature": "getStateDiff()", "selector": "0x80df01cc", "selectorBytes": [ 128, 223, 1, 204 ] }, "group": "evm", "status": "stable", "safety": "safe" }, { "func": { "id": "getStateDiffJson", "description": "Returns state diffs from current `vm.startStateDiffRecording` session, in json format.", "declaration": "function getStateDiffJson() external view returns (string memory diff);", "visibility": "external", "mutability": "view", "signature": "getStateDiffJson()", "selector": "0xf54fe009", "selectorBytes": [ 245, 79, 224, 9 ] }, "group": "evm", "status": "stable", "safety": "safe" }, { "func": { "id": "getStorageAccesses", "description": "Returns an array of `StorageAccess` from current `vm.stateStateDiffRecording` session", "declaration": "function getStorageAccesses() external view returns (StorageAccess[] memory storageAccesses);", "visibility": "external", "mutability": "view", "signature": "getStorageAccesses()", "selector": "0x2899b1d0", "selectorBytes": [ 40, 153, 177, 208 ] }, "group": "evm", "status": "stable", "safety": "safe" }, { "func": { "id": "getStorageSlots", "description": "Returns an array of storage slots occupied by the specified variable.", "declaration": "function getStorageSlots(address target, string calldata variableName) external view returns (uint256[] memory slots);", "visibility": "external", "mutability": "view", "signature": "getStorageSlots(address,string)", "selector": "0xefa136d9", "selectorBytes": [ 239, 161, 54, 217 ] }, "group": "evm", "status": "stable", "safety": "safe" }, { "func": { "id": "getWallets", "description": "Returns addresses of available unlocked wallets in the script environment.", "declaration": "function getWallets() external view returns (address[] memory wallets);", "visibility": "external", "mutability": "view", "signature": "getWallets()", "selector": "0xdb7a4605", "selectorBytes": [ 219, 122, 70, 5 ] }, "group": "scripting", "status": "stable", "safety": "safe" }, { "func": { "id": "indexOf", "description": "Returns the index of the first occurrence of a `key` in an `input` string.\nReturns `NOT_FOUND` (i.e. `type(uint256).max`) if the `key` is not found.\nReturns 0 in case of an empty `key`.", "declaration": "function indexOf(string calldata input, string calldata key) external pure returns (uint256);", "visibility": "external", "mutability": "pure", "signature": "indexOf(string,string)", "selector": "0x8a0807b7", "selectorBytes": [ 138, 8, 7, 183 ] }, "group": "string", "status": "stable", "safety": "safe" }, { "func": { "id": "interceptInitcode", "description": "Causes the next contract creation (via new) to fail and return its initcode in the returndata buffer.\nThis allows type-safe access to the initcode payload that would be used for contract creation.\nExample usage:\nvm.interceptInitcode();\nbytes memory initcode;\ntry new MyContract(param1, param2) { assert(false); }\ncatch (bytes memory interceptedInitcode) { initcode = interceptedInitcode; }", "declaration": "function interceptInitcode() external;", "visibility": "external", "mutability": "", "signature": "interceptInitcode()", "selector": "0x838653c7", "selectorBytes": [ 131, 134, 83, 199 ] }, "group": "utilities", "status": "stable", "safety": "unsafe" }, { "func": { "id": "isContext", "description": "Returns true if `forge` command was executed in given context.", "declaration": "function isContext(ForgeContext context) external view returns (bool result);", "visibility": "external", "mutability": "view", "signature": "isContext(uint8)", "selector": "0x64af255d", "selectorBytes": [ 100, 175, 37, 93 ] }, "group": "environment", "status": "stable", "safety": "safe" }, { "func": { "id": "isDir", "description": "Returns true if the path exists on disk and is pointing at a directory, else returns false.", "declaration": "function isDir(string calldata path) external view returns (bool result);", "visibility": "external", "mutability": "view", "signature": "isDir(string)", "selector": "0x7d15d019", "selectorBytes": [ 125, 21, 208, 25 ] }, "group": "filesystem", "status": "stable", "safety": "safe" }, { "func": { "id": "isFile", "description": "Returns true if the path exists on disk and is pointing at a regular file, else returns false.", "declaration": "function isFile(string calldata path) external view returns (bool result);", "visibility": "external", "mutability": "view", "signature": "isFile(string)", "selector": "0xe0eb04d4", "selectorBytes": [ 224, 235, 4, 212 ] }, "group": "filesystem", "status": "stable", "safety": "safe" }, { "func": { "id": "isPersistent", "description": "Returns true if the account is marked as persistent.", "declaration": "function isPersistent(address account) external view returns (bool persistent);", "visibility": "external", "mutability": "view", "signature": "isPersistent(address)", "selector": "0xd92d8efd", "selectorBytes": [ 217, 45, 142, 253 ] }, "group": "evm", "status": "stable", "safety": "unsafe" }, { "func": { "id": "keyExists", "description": "Checks if `key` exists in a JSON object\n`keyExists` is being deprecated in favor of `keyExistsJson`. It will be removed in future versions.", "declaration": "function keyExists(string calldata json, string calldata key) external view returns (bool);", "visibility": "external", "mutability": "view", "signature": "keyExists(string,string)", "selector": "0x528a683c", "selectorBytes": [ 82, 138, 104, 60 ] }, "group": "json", "status": { "deprecated": "replaced by `keyExistsJson`" }, "safety": "safe" }, { "func": { "id": "keyExistsJson", "description": "Checks if `key` exists in a JSON object.", "declaration": "function keyExistsJson(string calldata json, string calldata key) external view returns (bool);", "visibility": "external", "mutability": "view", "signature": "keyExistsJson(string,string)", "selector": "0xdb4235f6", "selectorBytes": [ 219, 66, 53, 246 ] }, "group": "json", "status": "stable", "safety": "safe" }, { "func": { "id": "keyExistsToml", "description": "Checks if `key` exists in a TOML table.", "declaration": "function keyExistsToml(string calldata toml, string calldata key) external view returns (bool);", "visibility": "external", "mutability": "view", "signature": "keyExistsToml(string,string)", "selector": "0x600903ad", "selectorBytes": [ 96, 9, 3, 173 ] }, "group": "toml", "status": "stable", "safety": "safe" }, { "func": { "id": "label", "description": "Labels an address in call traces.", "declaration": "function label(address account, string calldata newLabel) external;", "visibility": "external", "mutability": "", "signature": "label(address,string)", "selector": "0xc657c718", "selectorBytes": [ 198, 87, 199, 24 ] }, "group": "utilities", "status": "stable", "safety": "safe" }, { "func": { "id": "lastCallGas", "description": "Gets the gas used in the last call from the callee perspective.", "declaration": "function lastCallGas() external view returns (Gas memory gas);", "visibility": "external", "mutability": "view", "signature": "lastCallGas()", "selector": "0x2b589b28", "selectorBytes": [ 43, 88, 155, 40 ] }, "group": "evm", "status": "stable", "safety": "safe" }, { "func": { "id": "load", "description": "Loads a storage slot from an address.", "declaration": "function load(address target, bytes32 slot) external view returns (bytes32 data);", "visibility": "external", "mutability": "view", "signature": "load(address,bytes32)", "selector": "0x667f9d70", "selectorBytes": [ 102, 127, 157, 112 ] }, "group": "evm", "status": "stable", "safety": "safe" }, { "func": { "id": "loadAllocs", "description": "Load a genesis JSON file's `allocs` into the in-memory EVM state.", "declaration": "function loadAllocs(string calldata pathToAllocsJson) external;", "visibility": "external", "mutability": "", "signature": "loadAllocs(string)", "selector": "0xb3a056d7", "selectorBytes": [ 179, 160, 86, 215 ] }, "group": "evm", "status": "stable", "safety": "unsafe" }, { "func": { "id": "makePersistent_0", "description": "Marks that the account(s) should use persistent storage across fork swaps in a multifork setup\nMeaning, changes made to the state of this account will be kept when switching forks.", "declaration": "function makePersistent(address account) external;", "visibility": "external", "mutability": "", "signature": "makePersistent(address)", "selector": "0x57e22dde", "selectorBytes": [ 87, 226, 45, 222 ] }, "group": "evm", "status": "stable", "safety": "unsafe" }, { "func": { "id": "makePersistent_1", "description": "See `makePersistent(address)`.", "declaration": "function makePersistent(address account0, address account1) external;", "visibility": "external", "mutability": "", "signature": "makePersistent(address,address)", "selector": "0x4074e0a8", "selectorBytes": [ 64, 116, 224, 168 ] }, "group": "evm", "status": "stable", "safety": "unsafe" }, { "func": { "id": "makePersistent_2", "description": "See `makePersistent(address)`.", "declaration": "function makePersistent(address account0, address account1, address account2) external;", "visibility": "external", "mutability": "", "signature": "makePersistent(address,address,address)", "selector": "0xefb77a75", "selectorBytes": [ 239, 183, 122, 117 ] }, "group": "evm", "status": "stable", "safety": "unsafe" }, { "func": { "id": "makePersistent_3", "description": "See `makePersistent(address)`.", "declaration": "function makePersistent(address[] calldata accounts) external;", "visibility": "external", "mutability": "", "signature": "makePersistent(address[])", "selector": "0x1d9e269e", "selectorBytes": [ 29, 158, 38, 158 ] }, "group": "evm", "status": "stable", "safety": "unsafe" }, { "func": { "id": "mockCallRevert_0", "description": "Reverts a call to an address with specified revert data.", "declaration": "function mockCallRevert(address callee, bytes calldata data, bytes calldata revertData) external;", "visibility": "external", "mutability": "", "signature": "mockCallRevert(address,bytes,bytes)", "selector": "0xdbaad147", "selectorBytes": [ 219, 170, 209, 71 ] }, "group": "evm", "status": "stable", "safety": "unsafe" }, { "func": { "id": "mockCallRevert_1", "description": "Reverts a call to an address with a specific `msg.value`, with specified revert data.", "declaration": "function mockCallRevert(address callee, uint256 msgValue, bytes calldata data, bytes calldata revertData) external;", "visibility": "external", "mutability": "", "signature": "mockCallRevert(address,uint256,bytes,bytes)", "selector": "0xd23cd037", "selectorBytes": [ 210, 60, 208, 55 ] }, "group": "evm", "status": "stable", "safety": "unsafe" }, { "func": { "id": "mockCallRevert_2", "description": "Reverts a call to an address with specified revert data.\nOverload to pass the function selector directly `token.approve.selector` instead of `abi.encodeWithSelector(token.approve.selector)`.", "declaration": "function mockCallRevert(address callee, bytes4 data, bytes calldata revertData) external;", "visibility": "external", "mutability": "", "signature": "mockCallRevert(address,bytes4,bytes)", "selector": "0x2dfba5df", "selectorBytes": [ 45, 251, 165, 223 ] }, "group": "evm", "status": "stable", "safety": "unsafe" }, { "func": { "id": "mockCallRevert_3", "description": "Reverts a call to an address with a specific `msg.value`, with specified revert data.\nOverload to pass the function selector directly `token.approve.selector` instead of `abi.encodeWithSelector(token.approve.selector)`.", "declaration": "function mockCallRevert(address callee, uint256 msgValue, bytes4 data, bytes calldata revertData) external;", "visibility": "external", "mutability": "", "signature": "mockCallRevert(address,uint256,bytes4,bytes)", "selector": "0x596c8f04", "selectorBytes": [ 89, 108, 143, 4 ] }, "group": "evm", "status": "stable", "safety": "unsafe" }, { "func": { "id": "mockCall_0", "description": "Mocks a call to an address, returning specified data.\nCalldata can either be strict or a partial match, e.g. if you only\npass a Solidity selector to the expected calldata, then the entire Solidity\nfunction will be mocked.", "declaration": "function mockCall(address callee, bytes calldata data, bytes calldata returnData) external;", "visibility": "external", "mutability": "", "signature": "mockCall(address,bytes,bytes)", "selector": "0xb96213e4", "selectorBytes": [ 185, 98, 19, 228 ] }, "group": "evm", "status": "stable", "safety": "unsafe" }, { "func": { "id": "mockCall_1", "description": "Mocks a call to an address with a specific `msg.value`, returning specified data.\nCalldata match takes precedence over `msg.value` in case of ambiguity.", "declaration": "function mockCall(address callee, uint256 msgValue, bytes calldata data, bytes calldata returnData) external;", "visibility": "external", "mutability": "", "signature": "mockCall(address,uint256,bytes,bytes)", "selector": "0x81409b91", "selectorBytes": [ 129, 64, 155, 145 ] }, "group": "evm", "status": "stable", "safety": "unsafe" }, { "func": { "id": "mockCall_2", "description": "Mocks a call to an address, returning specified data.\nCalldata can either be strict or a partial match, e.g. if you only\npass a Solidity selector to the expected calldata, then the entire Solidity\nfunction will be mocked.\nOverload to pass the function selector directly `token.approve.selector` instead of `abi.encodeWithSelector(token.approve.selector)`.", "declaration": "function mockCall(address callee, bytes4 data, bytes calldata returnData) external;", "visibility": "external", "mutability": "", "signature": "mockCall(address,bytes4,bytes)", "selector": "0x08e0c537", "selectorBytes": [ 8, 224, 197, 55 ] }, "group": "evm", "status": "stable", "safety": "unsafe" }, { "func": { "id": "mockCall_3", "description": "Mocks a call to an address with a specific `msg.value`, returning specified data.\nCalldata match takes precedence over `msg.value` in case of ambiguity.\nOverload to pass the function selector directly `token.approve.selector` instead of `abi.encodeWithSelector(token.approve.selector)`.", "declaration": "function mockCall(address callee, uint256 msgValue, bytes4 data, bytes calldata returnData) external;", "visibility": "external", "mutability": "", "signature": "mockCall(address,uint256,bytes4,bytes)", "selector": "0xe7b36a3d", "selectorBytes": [ 231, 179, 106, 61 ] }, "group": "evm", "status": "stable", "safety": "unsafe" }, { "func": { "id": "mockCalls_0", "description": "Mocks multiple calls to an address, returning specified data for each call.", "declaration": "function mockCalls(address callee, bytes calldata data, bytes[] calldata returnData) external;", "visibility": "external", "mutability": "", "signature": "mockCalls(address,bytes,bytes[])", "selector": "0x5c5c3de9", "selectorBytes": [ 92, 92, 61, 233 ] }, "group": "evm", "status": "stable", "safety": "unsafe" }, { "func": { "id": "mockCalls_1", "description": "Mocks multiple calls to an address with a specific `msg.value`, returning specified data for each call.", "declaration": "function mockCalls(address callee, uint256 msgValue, bytes calldata data, bytes[] calldata returnData) external;", "visibility": "external", "mutability": "", "signature": "mockCalls(address,uint256,bytes,bytes[])", "selector": "0x08bcbae1", "selectorBytes": [ 8, 188, 186, 225 ] }, "group": "evm", "status": "stable", "safety": "unsafe" }, { "func": { "id": "mockFunction", "description": "Whenever a call is made to `callee` with calldata `data`, this cheatcode instead calls\n`target` with the same calldata. This functionality is similar to a delegate call made to\n`target` contract from `callee`.\nCan be used to substitute a call to a function with another implementation that captures\nthe primary logic of the original function but is easier to reason about.\nIf calldata is not a strict match then partial match by selector is attempted.", "declaration": "function mockFunction(address callee, address target, bytes calldata data) external;", "visibility": "external", "mutability": "", "signature": "mockFunction(address,address,bytes)", "selector": "0xadf84d21", "selectorBytes": [ 173, 248, 77, 33 ] }, "group": "evm", "status": "stable", "safety": "unsafe" }, { "func": { "id": "noAccessList", "description": "Utility cheatcode to remove any EIP-2930 access list set by `accessList` cheatcode.", "declaration": "function noAccessList() external;", "visibility": "external", "mutability": "", "signature": "noAccessList()", "selector": "0x238ad778", "selectorBytes": [ 35, 138, 215, 120 ] }, "group": "evm", "status": "stable", "safety": "unsafe" }, { "func": { "id": "parseAddress", "description": "Parses the given `string` into an `address`.", "declaration": "function parseAddress(string calldata stringifiedValue) external pure returns (address parsedValue);", "visibility": "external", "mutability": "pure", "signature": "parseAddress(string)", "selector": "0xc6ce059d", "selectorBytes": [ 198, 206, 5, 157 ] }, "group": "string", "status": "stable", "safety": "safe" }, { "func": { "id": "parseBool", "description": "Parses the given `string` into a `bool`.", "declaration": "function parseBool(string calldata stringifiedValue) external pure returns (bool parsedValue);", "visibility": "external", "mutability": "pure", "signature": "parseBool(string)", "selector": "0x974ef924", "selectorBytes": [ 151, 78, 249, 36 ] }, "group": "string", "status": "stable", "safety": "safe" }, { "func": { "id": "parseBytes", "description": "Parses the given `string` into `bytes`.", "declaration": "function parseBytes(string calldata stringifiedValue) external pure returns (bytes memory parsedValue);", "visibility": "external", "mutability": "pure", "signature": "parseBytes(string)", "selector": "0x8f5d232d", "selectorBytes": [ 143, 93, 35, 45 ] }, "group": "string", "status": "stable", "safety": "safe" }, { "func": { "id": "parseBytes32", "description": "Parses the given `string` into a `bytes32`.", "declaration": "function parseBytes32(string calldata stringifiedValue) external pure returns (bytes32 parsedValue);", "visibility": "external", "mutability": "pure", "signature": "parseBytes32(string)", "selector": "0x087e6e81", "selectorBytes": [ 8, 126, 110, 129 ] }, "group": "string", "status": "stable", "safety": "safe" }, { "func": { "id": "parseInt", "description": "Parses the given `string` into a `int256`.", "declaration": "function parseInt(string calldata stringifiedValue) external pure returns (int256 parsedValue);", "visibility": "external", "mutability": "pure", "signature": "parseInt(string)", "selector": "0x42346c5e", "selectorBytes": [ 66, 52, 108, 94 ] }, "group": "string", "status": "stable", "safety": "safe" }, { "func": { "id": "parseJsonAddress", "description": "Parses a string of JSON data at `key` and coerces it to `address`.", "declaration": "function parseJsonAddress(string calldata json, string calldata key) external pure returns (address);", "visibility": "external", "mutability": "pure", "signature": "parseJsonAddress(string,string)", "selector": "0x1e19e657", "selectorBytes": [ 30, 25, 230, 87 ] }, "group": "json", "status": "stable", "safety": "safe" }, { "func": { "id": "parseJsonAddressArray", "description": "Parses a string of JSON data at `key` and coerces it to `address[]`.", "declaration": "function parseJsonAddressArray(string calldata json, string calldata key) external pure returns (address[] memory);", "visibility": "external", "mutability": "pure", "signature": "parseJsonAddressArray(string,string)", "selector": "0x2fce7883", "selectorBytes": [ 47, 206, 120, 131 ] }, "group": "json", "status": "stable", "safety": "safe" }, { "func": { "id": "parseJsonBool", "description": "Parses a string of JSON data at `key` and coerces it to `bool`.", "declaration": "function parseJsonBool(string calldata json, string calldata key) external pure returns (bool);", "visibility": "external", "mutability": "pure", "signature": "parseJsonBool(string,string)", "selector": "0x9f86dc91", "selectorBytes": [ 159, 134, 220, 145 ] }, "group": "json", "status": "stable", "safety": "safe" }, { "func": { "id": "parseJsonBoolArray", "description": "Parses a string of JSON data at `key` and coerces it to `bool[]`.", "declaration": "function parseJsonBoolArray(string calldata json, string calldata key) external pure returns (bool[] memory);", "visibility": "external", "mutability": "pure", "signature": "parseJsonBoolArray(string,string)", "selector": "0x91f3b94f", "selectorBytes": [ 145, 243, 185, 79 ] }, "group": "json", "status": "stable", "safety": "safe" }, { "func": { "id": "parseJsonBytes", "description": "Parses a string of JSON data at `key` and coerces it to `bytes`.", "declaration": "function parseJsonBytes(string calldata json, string calldata key) external pure returns (bytes memory);", "visibility": "external", "mutability": "pure", "signature": "parseJsonBytes(string,string)", "selector": "0xfd921be8", "selectorBytes": [ 253, 146, 27, 232 ] }, "group": "json", "status": "stable", "safety": "safe" }, { "func": { "id": "parseJsonBytes32", "description": "Parses a string of JSON data at `key` and coerces it to `bytes32`.", "declaration": "function parseJsonBytes32(string calldata json, string calldata key) external pure returns (bytes32);", "visibility": "external", "mutability": "pure", "signature": "parseJsonBytes32(string,string)", "selector": "0x1777e59d", "selectorBytes": [ 23, 119, 229, 157 ] }, "group": "json", "status": "stable", "safety": "safe" }, { "func": { "id": "parseJsonBytes32Array", "description": "Parses a string of JSON data at `key` and coerces it to `bytes32[]`.", "declaration": "function parseJsonBytes32Array(string calldata json, string calldata key) external pure returns (bytes32[] memory);", "visibility": "external", "mutability": "pure", "signature": "parseJsonBytes32Array(string,string)", "selector": "0x91c75bc3", "selectorBytes": [ 145, 199, 91, 195 ] }, "group": "json", "status": "stable", "safety": "safe" }, { "func": { "id": "parseJsonBytesArray", "description": "Parses a string of JSON data at `key` and coerces it to `bytes[]`.", "declaration": "function parseJsonBytesArray(string calldata json, string calldata key) external pure returns (bytes[] memory);", "visibility": "external", "mutability": "pure", "signature": "parseJsonBytesArray(string,string)", "selector": "0x6631aa99", "selectorBytes": [ 102, 49, 170, 153 ] }, "group": "json", "status": "stable", "safety": "safe" }, { "func": { "id": "parseJsonInt", "description": "Parses a string of JSON data at `key` and coerces it to `int256`.", "declaration": "function parseJsonInt(string calldata json, string calldata key) external pure returns (int256);", "visibility": "external", "mutability": "pure", "signature": "parseJsonInt(string,string)", "selector": "0x7b048ccd", "selectorBytes": [ 123, 4, 140, 205 ] }, "group": "json", "status": "stable", "safety": "safe" }, { "func": { "id": "parseJsonIntArray", "description": "Parses a string of JSON data at `key` and coerces it to `int256[]`.", "declaration": "function parseJsonIntArray(string calldata json, string calldata key) external pure returns (int256[] memory);", "visibility": "external", "mutability": "pure", "signature": "parseJsonIntArray(string,string)", "selector": "0x9983c28a", "selectorBytes": [ 153, 131, 194, 138 ] }, "group": "json", "status": "stable", "safety": "safe" }, { "func": { "id": "parseJsonKeys", "description": "Returns an array of all the keys in a JSON object.", "declaration": "function parseJsonKeys(string calldata json, string calldata key) external pure returns (string[] memory keys);", "visibility": "external", "mutability": "pure", "signature": "parseJsonKeys(string,string)", "selector": "0x213e4198", "selectorBytes": [ 33, 62, 65, 152 ] }, "group": "json", "status": "stable", "safety": "safe" }, { "func": { "id": "parseJsonString", "description": "Parses a string of JSON data at `key` and coerces it to `string`.", "declaration": "function parseJsonString(string calldata json, string calldata key) external pure returns (string memory);", "visibility": "external", "mutability": "pure", "signature": "parseJsonString(string,string)", "selector": "0x49c4fac8", "selectorBytes": [ 73, 196, 250, 200 ] }, "group": "json", "status": "stable", "safety": "safe" }, { "func": { "id": "parseJsonStringArray", "description": "Parses a string of JSON data at `key` and coerces it to `string[]`.", "declaration": "function parseJsonStringArray(string calldata json, string calldata key) external pure returns (string[] memory);", "visibility": "external", "mutability": "pure", "signature": "parseJsonStringArray(string,string)", "selector": "0x498fdcf4", "selectorBytes": [ 73, 143, 220, 244 ] }, "group": "json", "status": "stable", "safety": "safe" }, { "func": { "id": "parseJsonTypeArray", "description": "Parses a string of JSON data at `key` and coerces it to type array corresponding to `typeDescription`.", "declaration": "function parseJsonTypeArray(string calldata json, string calldata key, string calldata typeDescription) external pure returns (bytes memory);", "visibility": "external", "mutability": "pure", "signature": "parseJsonTypeArray(string,string,string)", "selector": "0x0175d535", "selectorBytes": [ 1, 117, 213, 53 ] }, "group": "json", "status": "stable", "safety": "safe" }, { "func": { "id": "parseJsonType_0", "description": "Parses a string of JSON data and coerces it to type corresponding to `typeDescription`.", "declaration": "function parseJsonType(string calldata json, string calldata typeDescription) external pure returns (bytes memory);", "visibility": "external", "mutability": "pure", "signature": "parseJsonType(string,string)", "selector": "0xa9da313b", "selectorBytes": [ 169, 218, 49, 59 ] }, "group": "json", "status": "stable", "safety": "safe" }, { "func": { "id": "parseJsonType_1", "description": "Parses a string of JSON data at `key` and coerces it to type corresponding to `typeDescription`.", "declaration": "function parseJsonType(string calldata json, string calldata key, string calldata typeDescription) external pure returns (bytes memory);", "visibility": "external", "mutability": "pure", "signature": "parseJsonType(string,string,string)", "selector": "0xe3f5ae33", "selectorBytes": [ 227, 245, 174, 51 ] }, "group": "json", "status": "stable", "safety": "safe" }, { "func": { "id": "parseJsonUint", "description": "Parses a string of JSON data at `key` and coerces it to `uint256`.", "declaration": "function parseJsonUint(string calldata json, string calldata key) external pure returns (uint256);", "visibility": "external", "mutability": "pure", "signature": "parseJsonUint(string,string)", "selector": "0xaddde2b6", "selectorBytes": [ 173, 221, 226, 182 ] }, "group": "json", "status": "stable", "safety": "safe" }, { "func": { "id": "parseJsonUintArray", "description": "Parses a string of JSON data at `key` and coerces it to `uint256[]`.", "declaration": "function parseJsonUintArray(string calldata json, string calldata key) external pure returns (uint256[] memory);", "visibility": "external", "mutability": "pure", "signature": "parseJsonUintArray(string,string)", "selector": "0x522074ab", "selectorBytes": [ 82, 32, 116, 171 ] }, "group": "json", "status": "stable", "safety": "safe" }, { "func": { "id": "parseJson_0", "description": "ABI-encodes a JSON object.", "declaration": "function parseJson(string calldata json) external pure returns (bytes memory abiEncodedData);", "visibility": "external", "mutability": "pure", "signature": "parseJson(string)", "selector": "0x6a82600a", "selectorBytes": [ 106, 130, 96, 10 ] }, "group": "json", "status": "stable", "safety": "safe" }, { "func": { "id": "parseJson_1", "description": "ABI-encodes a JSON object at `key`.", "declaration": "function parseJson(string calldata json, string calldata key) external pure returns (bytes memory abiEncodedData);", "visibility": "external", "mutability": "pure", "signature": "parseJson(string,string)", "selector": "0x85940ef1", "selectorBytes": [ 133, 148, 14, 241 ] }, "group": "json", "status": "stable", "safety": "safe" }, { "func": { "id": "parseTomlAddress", "description": "Parses a string of TOML data at `key` and coerces it to `address`.", "declaration": "function parseTomlAddress(string calldata toml, string calldata key) external pure returns (address);", "visibility": "external", "mutability": "pure", "signature": "parseTomlAddress(string,string)", "selector": "0x65e7c844", "selectorBytes": [ 101, 231, 200, 68 ] }, "group": "toml", "status": "stable", "safety": "safe" }, { "func": { "id": "parseTomlAddressArray", "description": "Parses a string of TOML data at `key` and coerces it to `address[]`.", "declaration": "function parseTomlAddressArray(string calldata toml, string calldata key) external pure returns (address[] memory);", "visibility": "external", "mutability": "pure", "signature": "parseTomlAddressArray(string,string)", "selector": "0x65c428e7", "selectorBytes": [ 101, 196, 40, 231 ] }, "group": "toml", "status": "stable", "safety": "safe" }, { "func": { "id": "parseTomlBool", "description": "Parses a string of TOML data at `key` and coerces it to `bool`.", "declaration": "function parseTomlBool(string calldata toml, string calldata key) external pure returns (bool);", "visibility": "external", "mutability": "pure", "signature": "parseTomlBool(string,string)", "selector": "0xd30dced6", "selectorBytes": [ 211, 13, 206, 214 ] }, "group": "toml", "status": "stable", "safety": "safe" }, { "func": { "id": "parseTomlBoolArray", "description": "Parses a string of TOML data at `key` and coerces it to `bool[]`.", "declaration": "function parseTomlBoolArray(string calldata toml, string calldata key) external pure returns (bool[] memory);", "visibility": "external", "mutability": "pure", "signature": "parseTomlBoolArray(string,string)", "selector": "0x127cfe9a", "selectorBytes": [ 18, 124, 254, 154 ] }, "group": "toml", "status": "stable", "safety": "safe" }, { "func": { "id": "parseTomlBytes", "description": "Parses a string of TOML data at `key` and coerces it to `bytes`.", "declaration": "function parseTomlBytes(string calldata toml, string calldata key) external pure returns (bytes memory);", "visibility": "external", "mutability": "pure", "signature": "parseTomlBytes(string,string)", "selector": "0xd77bfdb9", "selectorBytes": [ 215, 123, 253, 185 ] }, "group": "toml", "status": "stable", "safety": "safe" }, { "func": { "id": "parseTomlBytes32", "description": "Parses a string of TOML data at `key` and coerces it to `bytes32`.", "declaration": "function parseTomlBytes32(string calldata toml, string calldata key) external pure returns (bytes32);", "visibility": "external", "mutability": "pure", "signature": "parseTomlBytes32(string,string)", "selector": "0x8e214810", "selectorBytes": [ 142, 33, 72, 16 ] }, "group": "toml", "status": "stable", "safety": "safe" }, { "func": { "id": "parseTomlBytes32Array", "description": "Parses a string of TOML data at `key` and coerces it to `bytes32[]`.", "declaration": "function parseTomlBytes32Array(string calldata toml, string calldata key) external pure returns (bytes32[] memory);", "visibility": "external", "mutability": "pure", "signature": "parseTomlBytes32Array(string,string)", "selector": "0x3e716f81", "selectorBytes": [ 62, 113, 111, 129 ] }, "group": "toml", "status": "stable", "safety": "safe" }, { "func": { "id": "parseTomlBytesArray", "description": "Parses a string of TOML data at `key` and coerces it to `bytes[]`.", "declaration": "function parseTomlBytesArray(string calldata toml, string calldata key) external pure returns (bytes[] memory);", "visibility": "external", "mutability": "pure", "signature": "parseTomlBytesArray(string,string)", "selector": "0xb197c247", "selectorBytes": [ 177, 151, 194, 71 ] }, "group": "toml", "status": "stable", "safety": "safe" }, { "func": { "id": "parseTomlInt", "description": "Parses a string of TOML data at `key` and coerces it to `int256`.", "declaration": "function parseTomlInt(string calldata toml, string calldata key) external pure returns (int256);", "visibility": "external", "mutability": "pure", "signature": "parseTomlInt(string,string)", "selector": "0xc1350739", "selectorBytes": [ 193, 53, 7, 57 ] }, "group": "toml", "status": "stable", "safety": "safe" }, { "func": { "id": "parseTomlIntArray", "description": "Parses a string of TOML data at `key` and coerces it to `int256[]`.", "declaration": "function parseTomlIntArray(string calldata toml, string calldata key) external pure returns (int256[] memory);", "visibility": "external", "mutability": "pure", "signature": "parseTomlIntArray(string,string)", "selector": "0xd3522ae6", "selectorBytes": [ 211, 82, 42, 230 ] }, "group": "toml", "status": "stable", "safety": "safe" }, { "func": { "id": "parseTomlKeys", "description": "Returns an array of all the keys in a TOML table.", "declaration": "function parseTomlKeys(string calldata toml, string calldata key) external pure returns (string[] memory keys);", "visibility": "external", "mutability": "pure", "signature": "parseTomlKeys(string,string)", "selector": "0x812a44b2", "selectorBytes": [ 129, 42, 68, 178 ] }, "group": "toml", "status": "stable", "safety": "safe" }, { "func": { "id": "parseTomlString", "description": "Parses a string of TOML data at `key` and coerces it to `string`.", "declaration": "function parseTomlString(string calldata toml, string calldata key) external pure returns (string memory);", "visibility": "external", "mutability": "pure", "signature": "parseTomlString(string,string)", "selector": "0x8bb8dd43", "selectorBytes": [ 139, 184, 221, 67 ] }, "group": "toml", "status": "stable", "safety": "safe" }, { "func": { "id": "parseTomlStringArray", "description": "Parses a string of TOML data at `key` and coerces it to `string[]`.", "declaration": "function parseTomlStringArray(string calldata toml, string calldata key) external pure returns (string[] memory);", "visibility": "external", "mutability": "pure", "signature": "parseTomlStringArray(string,string)", "selector": "0x9f629281", "selectorBytes": [ 159, 98, 146, 129 ] }, "group": "toml", "status": "stable", "safety": "safe" }, { "func": { "id": "parseTomlTypeArray", "description": "Parses a string of TOML data at `key` and coerces it to type array corresponding to `typeDescription`.", "declaration": "function parseTomlTypeArray(string calldata toml, string calldata key, string calldata typeDescription) external pure returns (bytes memory);", "visibility": "external", "mutability": "pure", "signature": "parseTomlTypeArray(string,string,string)", "selector": "0x49be3743", "selectorBytes": [ 73, 190, 55, 67 ] }, "group": "toml", "status": "stable", "safety": "safe" }, { "func": { "id": "parseTomlType_0", "description": "Parses a string of TOML data and coerces it to type corresponding to `typeDescription`.", "declaration": "function parseTomlType(string calldata toml, string calldata typeDescription) external pure returns (bytes memory);", "visibility": "external", "mutability": "pure", "signature": "parseTomlType(string,string)", "selector": "0x47fa5e11", "selectorBytes": [ 71, 250, 94, 17 ] }, "group": "toml", "status": "stable", "safety": "safe" }, { "func": { "id": "parseTomlType_1", "description": "Parses a string of TOML data at `key` and coerces it to type corresponding to `typeDescription`.", "declaration": "function parseTomlType(string calldata toml, string calldata key, string calldata typeDescription) external pure returns (bytes memory);", "visibility": "external", "mutability": "pure", "signature": "parseTomlType(string,string,string)", "selector": "0xf9fa5cdb", "selectorBytes": [ 249, 250, 92, 219 ] }, "group": "toml", "status": "stable", "safety": "safe" }, { "func": { "id": "parseTomlUint", "description": "Parses a string of TOML data at `key` and coerces it to `uint256`.", "declaration": "function parseTomlUint(string calldata toml, string calldata key) external pure returns (uint256);", "visibility": "external", "mutability": "pure", "signature": "parseTomlUint(string,string)", "selector": "0xcc7b0487", "selectorBytes": [ 204, 123, 4, 135 ] }, "group": "toml", "status": "stable", "safety": "safe" }, { "func": { "id": "parseTomlUintArray", "description": "Parses a string of TOML data at `key` and coerces it to `uint256[]`.", "declaration": "function parseTomlUintArray(string calldata toml, string calldata key) external pure returns (uint256[] memory);", "visibility": "external", "mutability": "pure", "signature": "parseTomlUintArray(string,string)", "selector": "0xb5df27c8", "selectorBytes": [ 181, 223, 39, 200 ] }, "group": "toml", "status": "stable", "safety": "safe" }, { "func": { "id": "parseToml_0", "description": "ABI-encodes a TOML table.", "declaration": "function parseToml(string calldata toml) external pure returns (bytes memory abiEncodedData);", "visibility": "external", "mutability": "pure", "signature": "parseToml(string)", "selector": "0x592151f0", "selectorBytes": [ 89, 33, 81, 240 ] }, "group": "toml", "status": "stable", "safety": "safe" }, { "func": { "id": "parseToml_1", "description": "ABI-encodes a TOML table at `key`.", "declaration": "function parseToml(string calldata toml, string calldata key) external pure returns (bytes memory abiEncodedData);", "visibility": "external", "mutability": "pure", "signature": "parseToml(string,string)", "selector": "0x37736e08", "selectorBytes": [ 55, 115, 110, 8 ] }, "group": "toml", "status": "stable", "safety": "safe" }, { "func": { "id": "parseUint", "description": "Parses the given `string` into a `uint256`.", "declaration": "function parseUint(string calldata stringifiedValue) external pure returns (uint256 parsedValue);", "visibility": "external", "mutability": "pure", "signature": "parseUint(string)", "selector": "0xfa91454d", "selectorBytes": [ 250, 145, 69, 77 ] }, "group": "string", "status": "stable", "safety": "safe" }, { "func": { "id": "pauseGasMetering", "description": "Pauses gas metering (i.e. gas usage is not counted). Noop if already paused.", "declaration": "function pauseGasMetering() external;", "visibility": "external", "mutability": "", "signature": "pauseGasMetering()", "selector": "0xd1a5b36f", "selectorBytes": [ 209, 165, 179, 111 ] }, "group": "evm", "status": "stable", "safety": "safe" }, { "func": { "id": "pauseTracing", "description": "Pauses collection of call traces. Useful in cases when you want to skip tracing of\ncomplex calls which are not useful for debugging.", "declaration": "function pauseTracing() external view;", "visibility": "external", "mutability": "view", "signature": "pauseTracing()", "selector": "0xc94d1f90", "selectorBytes": [ 201, 77, 31, 144 ] }, "group": "utilities", "status": "stable", "safety": "safe" }, { "func": { "id": "prank_0", "description": "Sets the *next* call's `msg.sender` to be the input address.", "declaration": "function prank(address msgSender) external;", "visibility": "external", "mutability": "", "signature": "prank(address)", "selector": "0xca669fa7", "selectorBytes": [ 202, 102, 159, 167 ] }, "group": "evm", "status": "stable", "safety": "unsafe" }, { "func": { "id": "prank_1", "description": "Sets the *next* call's `msg.sender` to be the input address, and the `tx.origin` to be the second input.", "declaration": "function prank(address msgSender, address txOrigin) external;", "visibility": "external", "mutability": "", "signature": "prank(address,address)", "selector": "0x47e50cce", "selectorBytes": [ 71, 229, 12, 206 ] }, "group": "evm", "status": "stable", "safety": "unsafe" }, { "func": { "id": "prank_2", "description": "Sets the *next* delegate call's `msg.sender` to be the input address.", "declaration": "function prank(address msgSender, bool delegateCall) external;", "visibility": "external", "mutability": "", "signature": "prank(address,bool)", "selector": "0xa7f8bf5c", "selectorBytes": [ 167, 248, 191, 92 ] }, "group": "evm", "status": "stable", "safety": "unsafe" }, { "func": { "id": "prank_3", "description": "Sets the *next* delegate call's `msg.sender` to be the input address, and the `tx.origin` to be the second input.", "declaration": "function prank(address msgSender, address txOrigin, bool delegateCall) external;", "visibility": "external", "mutability": "", "signature": "prank(address,address,bool)", "selector": "0x7d73d042", "selectorBytes": [ 125, 115, 208, 66 ] }, "group": "evm", "status": "stable", "safety": "unsafe" }, { "func": { "id": "prevrandao_0", "description": "Sets `block.prevrandao`.\nNot available on EVM versions before Paris. Use `difficulty` instead.\nIf used on unsupported EVM versions it will revert.", "declaration": "function prevrandao(bytes32 newPrevrandao) external;", "visibility": "external", "mutability": "", "signature": "prevrandao(bytes32)", "selector": "0x3b925549", "selectorBytes": [ 59, 146, 85, 73 ] }, "group": "evm", "status": "stable", "safety": "unsafe" }, { "func": { "id": "prevrandao_1", "description": "Sets `block.prevrandao`.\nNot available on EVM versions before Paris. Use `difficulty` instead.\nIf used on unsupported EVM versions it will revert.", "declaration": "function prevrandao(uint256 newPrevrandao) external;", "visibility": "external", "mutability": "", "signature": "prevrandao(uint256)", "selector": "0x9cb1c0d4", "selectorBytes": [ 156, 177, 192, 212 ] }, "group": "evm", "status": "stable", "safety": "unsafe" }, { "func": { "id": "projectRoot", "description": "Get the path of the current project root.", "declaration": "function projectRoot() external view returns (string memory path);", "visibility": "external", "mutability": "view", "signature": "projectRoot()", "selector": "0xd930a0e6", "selectorBytes": [ 217, 48, 160, 230 ] }, "group": "filesystem", "status": "stable", "safety": "safe" }, { "func": { "id": "prompt", "description": "Prompts the user for a string value in the terminal.", "declaration": "function prompt(string calldata promptText) external returns (string memory input);", "visibility": "external", "mutability": "", "signature": "prompt(string)", "selector": "0x47eaf474", "selectorBytes": [ 71, 234, 244, 116 ] }, "group": "filesystem", "status": "stable", "safety": "safe" }, { "func": { "id": "promptAddress", "description": "Prompts the user for an address in the terminal.", "declaration": "function promptAddress(string calldata promptText) external returns (address);", "visibility": "external", "mutability": "", "signature": "promptAddress(string)", "selector": "0x62ee05f4", "selectorBytes": [ 98, 238, 5, 244 ] }, "group": "filesystem", "status": "stable", "safety": "safe" }, { "func": { "id": "promptSecret", "description": "Prompts the user for a hidden string value in the terminal.", "declaration": "function promptSecret(string calldata promptText) external returns (string memory input);", "visibility": "external", "mutability": "", "signature": "promptSecret(string)", "selector": "0x1e279d41", "selectorBytes": [ 30, 39, 157, 65 ] }, "group": "filesystem", "status": "stable", "safety": "safe" }, { "func": { "id": "promptSecretUint", "description": "Prompts the user for hidden uint256 in the terminal (usually pk).", "declaration": "function promptSecretUint(string calldata promptText) external returns (uint256);", "visibility": "external", "mutability": "", "signature": "promptSecretUint(string)", "selector": "0x69ca02b7", "selectorBytes": [ 105, 202, 2, 183 ] }, "group": "filesystem", "status": "stable", "safety": "safe" }, { "func": { "id": "promptUint", "description": "Prompts the user for uint256 in the terminal.", "declaration": "function promptUint(string calldata promptText) external returns (uint256);", "visibility": "external", "mutability": "", "signature": "promptUint(string)", "selector": "0x652fd489", "selectorBytes": [ 101, 47, 212, 137 ] }, "group": "filesystem", "status": "stable", "safety": "safe" }, { "func": { "id": "publicKeyEd25519", "description": "Derives the Ed25519 public key from a private key.", "declaration": "function publicKeyEd25519(bytes32 privateKey) external pure returns (bytes32 publicKey);", "visibility": "external", "mutability": "pure", "signature": "publicKeyEd25519(bytes32)", "selector": "0x27f44236", "selectorBytes": [ 39, 244, 66, 54 ] }, "group": "crypto", "status": "stable", "safety": "safe" }, { "func": { "id": "publicKeyP256", "description": "Derives secp256r1 public key from the provided `privateKey`.", "declaration": "function publicKeyP256(uint256 privateKey) external pure returns (uint256 publicKeyX, uint256 publicKeyY);", "visibility": "external", "mutability": "pure", "signature": "publicKeyP256(uint256)", "selector": "0xc453949e", "selectorBytes": [ 196, 83, 148, 158 ] }, "group": "crypto", "status": "stable", "safety": "safe" }, { "func": { "id": "randomAddress", "description": "Returns a random `address`.", "declaration": "function randomAddress() external view returns (address);", "visibility": "external", "mutability": "view", "signature": "randomAddress()", "selector": "0xd5bee9f5", "selectorBytes": [ 213, 190, 233, 245 ] }, "group": "utilities", "status": "stable", "safety": "safe" }, { "func": { "id": "randomBool", "description": "Returns a random `bool`.", "declaration": "function randomBool() external view returns (bool);", "visibility": "external", "mutability": "view", "signature": "randomBool()", "selector": "0xcdc126bd", "selectorBytes": [ 205, 193, 38, 189 ] }, "group": "utilities", "status": "stable", "safety": "safe" }, { "func": { "id": "randomBytes", "description": "Returns a random byte array value of the given length.", "declaration": "function randomBytes(uint256 len) external view returns (bytes memory);", "visibility": "external", "mutability": "view", "signature": "randomBytes(uint256)", "selector": "0x6c5d32a9", "selectorBytes": [ 108, 93, 50, 169 ] }, "group": "utilities", "status": "stable", "safety": "safe" }, { "func": { "id": "randomBytes4", "description": "Returns a random fixed-size byte array of length 4.", "declaration": "function randomBytes4() external view returns (bytes4);", "visibility": "external", "mutability": "view", "signature": "randomBytes4()", "selector": "0x9b7cd579", "selectorBytes": [ 155, 124, 213, 121 ] }, "group": "utilities", "status": "stable", "safety": "safe" }, { "func": { "id": "randomBytes8", "description": "Returns a random fixed-size byte array of length 8.", "declaration": "function randomBytes8() external view returns (bytes8);", "visibility": "external", "mutability": "view", "signature": "randomBytes8()", "selector": "0x0497b0a5", "selectorBytes": [ 4, 151, 176, 165 ] }, "group": "utilities", "status": "stable", "safety": "safe" }, { "func": { "id": "randomInt_0", "description": "Returns a random `int256` value.", "declaration": "function randomInt() external view returns (int256);", "visibility": "external", "mutability": "view", "signature": "randomInt()", "selector": "0x111f1202", "selectorBytes": [ 17, 31, 18, 2 ] }, "group": "utilities", "status": "stable", "safety": "safe" }, { "func": { "id": "randomInt_1", "description": "Returns a random `int256` value of given bits.", "declaration": "function randomInt(uint256 bits) external view returns (int256);", "visibility": "external", "mutability": "view", "signature": "randomInt(uint256)", "selector": "0x12845966", "selectorBytes": [ 18, 132, 89, 102 ] }, "group": "utilities", "status": "stable", "safety": "safe" }, { "func": { "id": "randomUint_0", "description": "Returns a random uint256 value.", "declaration": "function randomUint() external view returns (uint256);", "visibility": "external", "mutability": "view", "signature": "randomUint()", "selector": "0x25124730", "selectorBytes": [ 37, 18, 71, 48 ] }, "group": "utilities", "status": "stable", "safety": "safe" }, { "func": { "id": "randomUint_1", "description": "Returns random uint256 value between the provided range (=min..=max).", "declaration": "function randomUint(uint256 min, uint256 max) external view returns (uint256);", "visibility": "external", "mutability": "view", "signature": "randomUint(uint256,uint256)", "selector": "0xd61b051b", "selectorBytes": [ 214, 27, 5, 27 ] }, "group": "utilities", "status": "stable", "safety": "safe" }, { "func": { "id": "randomUint_2", "description": "Returns a random `uint256` value of given bits.", "declaration": "function randomUint(uint256 bits) external view returns (uint256);", "visibility": "external", "mutability": "view", "signature": "randomUint(uint256)", "selector": "0xcf81e69c", "selectorBytes": [ 207, 129, 230, 156 ] }, "group": "utilities", "status": "stable", "safety": "safe" }, { "func": { "id": "readCallers", "description": "Reads the current `msg.sender` and `tx.origin` from state and reports if there is any active caller modification.", "declaration": "function readCallers() external view returns (CallerMode callerMode, address msgSender, address txOrigin);", "visibility": "external", "mutability": "view", "signature": "readCallers()", "selector": "0x4ad0bac9", "selectorBytes": [ 74, 208, 186, 201 ] }, "group": "evm", "status": "stable", "safety": "unsafe" }, { "func": { "id": "readDir_0", "description": "Reads the directory at the given path recursively, up to `maxDepth`.\n`maxDepth` defaults to 1, meaning only the direct children of the given directory will be returned.\nFollows symbolic links if `followLinks` is true.", "declaration": "function readDir(string calldata path) external view returns (DirEntry[] memory entries);", "visibility": "external", "mutability": "view", "signature": "readDir(string)", "selector": "0xc4bc59e0", "selectorBytes": [ 196, 188, 89, 224 ] }, "group": "filesystem", "status": "stable", "safety": "safe" }, { "func": { "id": "readDir_1", "description": "See `readDir(string)`.", "declaration": "function readDir(string calldata path, uint64 maxDepth) external view returns (DirEntry[] memory entries);", "visibility": "external", "mutability": "view", "signature": "readDir(string,uint64)", "selector": "0x1497876c", "selectorBytes": [ 20, 151, 135, 108 ] }, "group": "filesystem", "status": "stable", "safety": "safe" }, { "func": { "id": "readDir_2", "description": "See `readDir(string)`.", "declaration": "function readDir(string calldata path, uint64 maxDepth, bool followLinks) external view returns (DirEntry[] memory entries);", "visibility": "external", "mutability": "view", "signature": "readDir(string,uint64,bool)", "selector": "0x8102d70d", "selectorBytes": [ 129, 2, 215, 13 ] }, "group": "filesystem", "status": "stable", "safety": "safe" }, { "func": { "id": "readFile", "description": "Reads the entire content of file to string. `path` is relative to the project root.", "declaration": "function readFile(string calldata path) external view returns (string memory data);", "visibility": "external", "mutability": "view", "signature": "readFile(string)", "selector": "0x60f9bb11", "selectorBytes": [ 96, 249, 187, 17 ] }, "group": "filesystem", "status": "stable", "safety": "safe" }, { "func": { "id": "readFileBinary", "description": "Reads the entire content of file as binary. `path` is relative to the project root.", "declaration": "function readFileBinary(string calldata path) external view returns (bytes memory data);", "visibility": "external", "mutability": "view", "signature": "readFileBinary(string)", "selector": "0x16ed7bc4", "selectorBytes": [ 22, 237, 123, 196 ] }, "group": "filesystem", "status": "stable", "safety": "safe" }, { "func": { "id": "readLine", "description": "Reads next line of file to string.", "declaration": "function readLine(string calldata path) external view returns (string memory line);", "visibility": "external", "mutability": "view", "signature": "readLine(string)", "selector": "0x70f55728", "selectorBytes": [ 112, 245, 87, 40 ] }, "group": "filesystem", "status": "stable", "safety": "safe" }, { "func": { "id": "readLink", "description": "Reads a symbolic link, returning the path that the link points to.\nThis cheatcode will revert in the following situations, but is not limited to just these cases:\n- `path` is not a symbolic link.\n- `path` does not exist.", "declaration": "function readLink(string calldata linkPath) external view returns (string memory targetPath);", "visibility": "external", "mutability": "view", "signature": "readLink(string)", "selector": "0x9f5684a2", "selectorBytes": [ 159, 86, 132, 162 ] }, "group": "filesystem", "status": "stable", "safety": "safe" }, { "func": { "id": "record", "description": "Records all storage reads and writes. Use `accesses` to get the recorded data.\nSubsequent calls to `record` will clear the previous data.", "declaration": "function record() external;", "visibility": "external", "mutability": "", "signature": "record()", "selector": "0x266cf109", "selectorBytes": [ 38, 108, 241, 9 ] }, "group": "evm", "status": "stable", "safety": "safe" }, { "func": { "id": "recordLogs", "description": "Record all the transaction logs.", "declaration": "function recordLogs() external;", "visibility": "external", "mutability": "", "signature": "recordLogs()", "selector": "0x41af2f52", "selectorBytes": [ 65, 175, 47, 82 ] }, "group": "evm", "status": "stable", "safety": "safe" }, { "func": { "id": "rememberKey", "description": "Adds a private key to the local forge wallet and returns the address.", "declaration": "function rememberKey(uint256 privateKey) external returns (address keyAddr);", "visibility": "external", "mutability": "", "signature": "rememberKey(uint256)", "selector": "0x22100064", "selectorBytes": [ 34, 16, 0, 100 ] }, "group": "crypto", "status": "stable", "safety": "safe" }, { "func": { "id": "rememberKeys_0", "description": "Derive a set number of wallets from a mnemonic at the derivation path `m/44'/60'/0'/0/{0..count}`.\nThe respective private keys are saved to the local forge wallet for later use and their addresses are returned.", "declaration": "function rememberKeys(string calldata mnemonic, string calldata derivationPath, uint32 count) external returns (address[] memory keyAddrs);", "visibility": "external", "mutability": "", "signature": "rememberKeys(string,string,uint32)", "selector": "0x97cb9189", "selectorBytes": [ 151, 203, 145, 137 ] }, "group": "crypto", "status": "stable", "safety": "safe" }, { "func": { "id": "rememberKeys_1", "description": "Derive a set number of wallets from a mnemonic in the specified language at the derivation path `m/44'/60'/0'/0/{0..count}`.\nThe respective private keys are saved to the local forge wallet for later use and their addresses are returned.", "declaration": "function rememberKeys(string calldata mnemonic, string calldata derivationPath, string calldata language, uint32 count) external returns (address[] memory keyAddrs);", "visibility": "external", "mutability": "", "signature": "rememberKeys(string,string,string,uint32)", "selector": "0xf8d58eaf", "selectorBytes": [ 248, 213, 142, 175 ] }, "group": "crypto", "status": "stable", "safety": "safe" }, { "func": { "id": "removeDir", "description": "Removes a directory at the provided path.\nThis cheatcode will revert in the following situations, but is not limited to just these cases:\n- `path` doesn't exist.\n- `path` isn't a directory.\n- User lacks permissions to modify `path`.\n- The directory is not empty and `recursive` is false.\n`path` is relative to the project root.", "declaration": "function removeDir(string calldata path, bool recursive) external;", "visibility": "external", "mutability": "", "signature": "removeDir(string,bool)", "selector": "0x45c62011", "selectorBytes": [ 69, 198, 32, 17 ] }, "group": "filesystem", "status": "stable", "safety": "safe" }, { "func": { "id": "removeFile", "description": "Removes a file from the filesystem.\nThis cheatcode will revert in the following situations, but is not limited to just these cases:\n- `path` points to a directory.\n- The file doesn't exist.\n- The user lacks permissions to remove the file.\n`path` is relative to the project root.", "declaration": "function removeFile(string calldata path) external;", "visibility": "external", "mutability": "", "signature": "removeFile(string)", "selector": "0xf1afe04d", "selectorBytes": [ 241, 175, 224, 77 ] }, "group": "filesystem", "status": "stable", "safety": "safe" }, { "func": { "id": "replace", "description": "Replaces occurrences of `from` in the given `string` with `to`.", "declaration": "function replace(string calldata input, string calldata from, string calldata to) external pure returns (string memory output);", "visibility": "external", "mutability": "pure", "signature": "replace(string,string,string)", "selector": "0xe00ad03e", "selectorBytes": [ 224, 10, 208, 62 ] }, "group": "string", "status": "stable", "safety": "safe" }, { "func": { "id": "resetGasMetering", "description": "Reset gas metering (i.e. gas usage is set to gas limit).", "declaration": "function resetGasMetering() external;", "visibility": "external", "mutability": "", "signature": "resetGasMetering()", "selector": "0xbe367dd3", "selectorBytes": [ 190, 54, 125, 211 ] }, "group": "evm", "status": "stable", "safety": "safe" }, { "func": { "id": "resetNonce", "description": "Resets the nonce of an account to 0 for EOAs and 1 for contract accounts.", "declaration": "function resetNonce(address account) external;", "visibility": "external", "mutability": "", "signature": "resetNonce(address)", "selector": "0x1c72346d", "selectorBytes": [ 28, 114, 52, 109 ] }, "group": "evm", "status": "stable", "safety": "unsafe" }, { "func": { "id": "resolveEnv", "description": "Resolves the env variable placeholders of a given input string.", "declaration": "function resolveEnv(string calldata input) external returns (string memory);", "visibility": "external", "mutability": "", "signature": "resolveEnv(string)", "selector": "0xddd2128d", "selectorBytes": [ 221, 210, 18, 141 ] }, "group": "environment", "status": "stable", "safety": "safe" }, { "func": { "id": "resumeGasMetering", "description": "Resumes gas metering (i.e. gas usage is counted again). Noop if already on.", "declaration": "function resumeGasMetering() external;", "visibility": "external", "mutability": "", "signature": "resumeGasMetering()", "selector": "0x2bcd50e0", "selectorBytes": [ 43, 205, 80, 224 ] }, "group": "evm", "status": "stable", "safety": "safe" }, { "func": { "id": "resumeTracing", "description": "Unpauses collection of call traces.", "declaration": "function resumeTracing() external view;", "visibility": "external", "mutability": "view", "signature": "resumeTracing()", "selector": "0x72a09ccb", "selectorBytes": [ 114, 160, 156, 203 ] }, "group": "utilities", "status": "stable", "safety": "safe" }, { "func": { "id": "revertTo", "description": "`revertTo` is being deprecated in favor of `revertToState`. It will be removed in future versions.", "declaration": "function revertTo(uint256 snapshotId) external returns (bool success);", "visibility": "external", "mutability": "", "signature": "revertTo(uint256)", "selector": "0x44d7f0a4", "selectorBytes": [ 68, 215, 240, 164 ] }, "group": "evm", "status": { "deprecated": "replaced by `revertToState`" }, "safety": "unsafe" }, { "func": { "id": "revertToAndDelete", "description": "`revertToAndDelete` is being deprecated in favor of `revertToStateAndDelete`. It will be removed in future versions.", "declaration": "function revertToAndDelete(uint256 snapshotId) external returns (bool success);", "visibility": "external", "mutability": "", "signature": "revertToAndDelete(uint256)", "selector": "0x03e0aca9", "selectorBytes": [ 3, 224, 172, 169 ] }, "group": "evm", "status": { "deprecated": "replaced by `revertToStateAndDelete`" }, "safety": "unsafe" }, { "func": { "id": "revertToState", "description": "Revert the state of the EVM to a previous snapshot\nTakes the snapshot ID to revert to.\nReturns `true` if the snapshot was successfully reverted.\nReturns `false` if the snapshot does not exist.\n**Note:** This does not automatically delete the snapshot. To delete the snapshot use `deleteStateSnapshot`.", "declaration": "function revertToState(uint256 snapshotId) external returns (bool success);", "visibility": "external", "mutability": "", "signature": "revertToState(uint256)", "selector": "0xc2527405", "selectorBytes": [ 194, 82, 116, 5 ] }, "group": "evm", "status": "stable", "safety": "unsafe" }, { "func": { "id": "revertToStateAndDelete", "description": "Revert the state of the EVM to a previous snapshot and automatically deletes the snapshots\nTakes the snapshot ID to revert to.\nReturns `true` if the snapshot was successfully reverted and deleted.\nReturns `false` if the snapshot does not exist.", "declaration": "function revertToStateAndDelete(uint256 snapshotId) external returns (bool success);", "visibility": "external", "mutability": "", "signature": "revertToStateAndDelete(uint256)", "selector": "0x3a1985dc", "selectorBytes": [ 58, 25, 133, 220 ] }, "group": "evm", "status": "stable", "safety": "unsafe" }, { "func": { "id": "revokePersistent_0", "description": "Revokes persistent status from the address, previously added via `makePersistent`.", "declaration": "function revokePersistent(address account) external;", "visibility": "external", "mutability": "", "signature": "revokePersistent(address)", "selector": "0x997a0222", "selectorBytes": [ 153, 122, 2, 34 ] }, "group": "evm", "status": "stable", "safety": "unsafe" }, { "func": { "id": "revokePersistent_1", "description": "See `revokePersistent(address)`.", "declaration": "function revokePersistent(address[] calldata accounts) external;", "visibility": "external", "mutability": "", "signature": "revokePersistent(address[])", "selector": "0x3ce969e6", "selectorBytes": [ 60, 233, 105, 230 ] }, "group": "evm", "status": "stable", "safety": "unsafe" }, { "func": { "id": "roll", "description": "Sets `block.height`.", "declaration": "function roll(uint256 newHeight) external;", "visibility": "external", "mutability": "", "signature": "roll(uint256)", "selector": "0x1f7b4f30", "selectorBytes": [ 31, 123, 79, 48 ] }, "group": "evm", "status": "stable", "safety": "unsafe" }, { "func": { "id": "rollFork_0", "description": "Updates the currently active fork to given block number\nThis is similar to `roll` but for the currently active fork.", "declaration": "function rollFork(uint256 blockNumber) external;", "visibility": "external", "mutability": "", "signature": "rollFork(uint256)", "selector": "0xd9bbf3a1", "selectorBytes": [ 217, 187, 243, 161 ] }, "group": "evm", "status": "stable", "safety": "unsafe" }, { "func": { "id": "rollFork_1", "description": "Updates the currently active fork to given transaction. This will `rollFork` with the number\nof the block the transaction was mined in and replays all transaction mined before it in the block.", "declaration": "function rollFork(bytes32 txHash) external;", "visibility": "external", "mutability": "", "signature": "rollFork(bytes32)", "selector": "0x0f29772b", "selectorBytes": [ 15, 41, 119, 43 ] }, "group": "evm", "status": "stable", "safety": "unsafe" }, { "func": { "id": "rollFork_2", "description": "Updates the given fork to given block number.", "declaration": "function rollFork(uint256 forkId, uint256 blockNumber) external;", "visibility": "external", "mutability": "", "signature": "rollFork(uint256,uint256)", "selector": "0xd74c83a4", "selectorBytes": [ 215, 76, 131, 164 ] }, "group": "evm", "status": "stable", "safety": "unsafe" }, { "func": { "id": "rollFork_3", "description": "Updates the given fork to block number of the given transaction and replays all transaction mined before it in the block.", "declaration": "function rollFork(uint256 forkId, bytes32 txHash) external;", "visibility": "external", "mutability": "", "signature": "rollFork(uint256,bytes32)", "selector": "0xf2830f7b", "selectorBytes": [ 242, 131, 15, 123 ] }, "group": "evm", "status": "stable", "safety": "unsafe" }, { "func": { "id": "rpcUrl", "description": "Returns the RPC url for the given alias.", "declaration": "function rpcUrl(string calldata rpcAlias) external view returns (string memory json);", "visibility": "external", "mutability": "view", "signature": "rpcUrl(string)", "selector": "0x975a6ce9", "selectorBytes": [ 151, 90, 108, 233 ] }, "group": "testing", "status": "stable", "safety": "safe" }, { "func": { "id": "rpcUrlStructs", "description": "Returns all rpc urls and their aliases as structs.", "declaration": "function rpcUrlStructs() external view returns (Rpc[] memory urls);", "visibility": "external", "mutability": "view", "signature": "rpcUrlStructs()", "selector": "0x9d2ad72a", "selectorBytes": [ 157, 42, 215, 42 ] }, "group": "testing", "status": "stable", "safety": "safe" }, { "func": { "id": "rpcUrls", "description": "Returns all rpc urls and their aliases `[alias, url][]`.", "declaration": "function rpcUrls() external view returns (string[2][] memory urls);", "visibility": "external", "mutability": "view", "signature": "rpcUrls()", "selector": "0xa85a8418", "selectorBytes": [ 168, 90, 132, 24 ] }, "group": "testing", "status": "stable", "safety": "safe" }, { "func": { "id": "rpc_0", "description": "Performs an Ethereum JSON-RPC request to the current fork URL.", "declaration": "function rpc(string calldata method, string calldata params) external returns (bytes memory data);", "visibility": "external", "mutability": "", "signature": "rpc(string,string)", "selector": "0x1206c8a8", "selectorBytes": [ 18, 6, 200, 168 ] }, "group": "evm", "status": "stable", "safety": "safe" }, { "func": { "id": "rpc_1", "description": "Performs an Ethereum JSON-RPC request to the given endpoint.", "declaration": "function rpc(string calldata urlOrAlias, string calldata method, string calldata params) external returns (bytes memory data);", "visibility": "external", "mutability": "", "signature": "rpc(string,string,string)", "selector": "0x0199a220", "selectorBytes": [ 1, 153, 162, 32 ] }, "group": "evm", "status": "stable", "safety": "safe" }, { "func": { "id": "selectFork", "description": "Takes a fork identifier created by `createFork` and sets the corresponding forked state as active.", "declaration": "function selectFork(uint256 forkId) external;", "visibility": "external", "mutability": "", "signature": "selectFork(uint256)", "selector": "0x9ebf6827", "selectorBytes": [ 158, 191, 104, 39 ] }, "group": "evm", "status": "stable", "safety": "unsafe" }, { "func": { "id": "serializeAddress_0", "description": "See `serializeJson`.", "declaration": "function serializeAddress(string calldata objectKey, string calldata valueKey, address value) external returns (string memory json);", "visibility": "external", "mutability": "", "signature": "serializeAddress(string,string,address)", "selector": "0x972c6062", "selectorBytes": [ 151, 44, 96, 98 ] }, "group": "json", "status": "stable", "safety": "safe" }, { "func": { "id": "serializeAddress_1", "description": "See `serializeJson`.", "declaration": "function serializeAddress(string calldata objectKey, string calldata valueKey, address[] calldata values) external returns (string memory json);", "visibility": "external", "mutability": "", "signature": "serializeAddress(string,string,address[])", "selector": "0x1e356e1a", "selectorBytes": [ 30, 53, 110, 26 ] }, "group": "json", "status": "stable", "safety": "safe" }, { "func": { "id": "serializeBool_0", "description": "See `serializeJson`.", "declaration": "function serializeBool(string calldata objectKey, string calldata valueKey, bool value) external returns (string memory json);", "visibility": "external", "mutability": "", "signature": "serializeBool(string,string,bool)", "selector": "0xac22e971", "selectorBytes": [ 172, 34, 233, 113 ] }, "group": "json", "status": "stable", "safety": "safe" }, { "func": { "id": "serializeBool_1", "description": "See `serializeJson`.", "declaration": "function serializeBool(string calldata objectKey, string calldata valueKey, bool[] calldata values) external returns (string memory json);", "visibility": "external", "mutability": "", "signature": "serializeBool(string,string,bool[])", "selector": "0x92925aa1", "selectorBytes": [ 146, 146, 90, 161 ] }, "group": "json", "status": "stable", "safety": "safe" }, { "func": { "id": "serializeBytes32_0", "description": "See `serializeJson`.", "declaration": "function serializeBytes32(string calldata objectKey, string calldata valueKey, bytes32 value) external returns (string memory json);", "visibility": "external", "mutability": "", "signature": "serializeBytes32(string,string,bytes32)", "selector": "0x2d812b44", "selectorBytes": [ 45, 129, 43, 68 ] }, "group": "json", "status": "stable", "safety": "safe" }, { "func": { "id": "serializeBytes32_1", "description": "See `serializeJson`.", "declaration": "function serializeBytes32(string calldata objectKey, string calldata valueKey, bytes32[] calldata values) external returns (string memory json);", "visibility": "external", "mutability": "", "signature": "serializeBytes32(string,string,bytes32[])", "selector": "0x201e43e2", "selectorBytes": [ 32, 30, 67, 226 ] }, "group": "json", "status": "stable", "safety": "safe" }, { "func": { "id": "serializeBytes_0", "description": "See `serializeJson`.", "declaration": "function serializeBytes(string calldata objectKey, string calldata valueKey, bytes calldata value) external returns (string memory json);", "visibility": "external", "mutability": "", "signature": "serializeBytes(string,string,bytes)", "selector": "0xf21d52c7", "selectorBytes": [ 242, 29, 82, 199 ] }, "group": "json", "status": "stable", "safety": "safe" }, { "func": { "id": "serializeBytes_1", "description": "See `serializeJson`.", "declaration": "function serializeBytes(string calldata objectKey, string calldata valueKey, bytes[] calldata values) external returns (string memory json);", "visibility": "external", "mutability": "", "signature": "serializeBytes(string,string,bytes[])", "selector": "0x9884b232", "selectorBytes": [ 152, 132, 178, 50 ] }, "group": "json", "status": "stable", "safety": "safe" }, { "func": { "id": "serializeInt_0", "description": "See `serializeJson`.", "declaration": "function serializeInt(string calldata objectKey, string calldata valueKey, int256 value) external returns (string memory json);", "visibility": "external", "mutability": "", "signature": "serializeInt(string,string,int256)", "selector": "0x3f33db60", "selectorBytes": [ 63, 51, 219, 96 ] }, "group": "json", "status": "stable", "safety": "safe" }, { "func": { "id": "serializeInt_1", "description": "See `serializeJson`.", "declaration": "function serializeInt(string calldata objectKey, string calldata valueKey, int256[] calldata values) external returns (string memory json);", "visibility": "external", "mutability": "", "signature": "serializeInt(string,string,int256[])", "selector": "0x7676e127", "selectorBytes": [ 118, 118, 225, 39 ] }, "group": "json", "status": "stable", "safety": "safe" }, { "func": { "id": "serializeJson", "description": "Serializes a key and value to a JSON object stored in-memory that can be later written to a file.\nReturns the stringified version of the specific JSON file up to that moment.", "declaration": "function serializeJson(string calldata objectKey, string calldata value) external returns (string memory json);", "visibility": "external", "mutability": "", "signature": "serializeJson(string,string)", "selector": "0x9b3358b0", "selectorBytes": [ 155, 51, 88, 176 ] }, "group": "json", "status": "stable", "safety": "safe" }, { "func": { "id": "serializeJsonType_0", "description": "See `serializeJson`.", "declaration": "function serializeJsonType(string calldata typeDescription, bytes calldata value) external pure returns (string memory json);", "visibility": "external", "mutability": "pure", "signature": "serializeJsonType(string,bytes)", "selector": "0x6d4f96a6", "selectorBytes": [ 109, 79, 150, 166 ] }, "group": "json", "status": "stable", "safety": "safe" }, { "func": { "id": "serializeJsonType_1", "description": "See `serializeJson`.", "declaration": "function serializeJsonType(string calldata objectKey, string calldata valueKey, string calldata typeDescription, bytes calldata value) external returns (string memory json);", "visibility": "external", "mutability": "", "signature": "serializeJsonType(string,string,string,bytes)", "selector": "0x6f93bccb", "selectorBytes": [ 111, 147, 188, 203 ] }, "group": "json", "status": "stable", "safety": "safe" }, { "func": { "id": "serializeString_0", "description": "See `serializeJson`.", "declaration": "function serializeString(string calldata objectKey, string calldata valueKey, string calldata value) external returns (string memory json);", "visibility": "external", "mutability": "", "signature": "serializeString(string,string,string)", "selector": "0x88da6d35", "selectorBytes": [ 136, 218, 109, 53 ] }, "group": "json", "status": "stable", "safety": "safe" }, { "func": { "id": "serializeString_1", "description": "See `serializeJson`.", "declaration": "function serializeString(string calldata objectKey, string calldata valueKey, string[] calldata values) external returns (string memory json);", "visibility": "external", "mutability": "", "signature": "serializeString(string,string,string[])", "selector": "0x561cd6f3", "selectorBytes": [ 86, 28, 214, 243 ] }, "group": "json", "status": "stable", "safety": "safe" }, { "func": { "id": "serializeUintToHex", "description": "See `serializeJson`.", "declaration": "function serializeUintToHex(string calldata objectKey, string calldata valueKey, uint256 value) external returns (string memory json);", "visibility": "external", "mutability": "", "signature": "serializeUintToHex(string,string,uint256)", "selector": "0xae5a2ae8", "selectorBytes": [ 174, 90, 42, 232 ] }, "group": "json", "status": "stable", "safety": "safe" }, { "func": { "id": "serializeUint_0", "description": "See `serializeJson`.", "declaration": "function serializeUint(string calldata objectKey, string calldata valueKey, uint256 value) external returns (string memory json);", "visibility": "external", "mutability": "", "signature": "serializeUint(string,string,uint256)", "selector": "0x129e9002", "selectorBytes": [ 18, 158, 144, 2 ] }, "group": "json", "status": "stable", "safety": "safe" }, { "func": { "id": "serializeUint_1", "description": "See `serializeJson`.", "declaration": "function serializeUint(string calldata objectKey, string calldata valueKey, uint256[] calldata values) external returns (string memory json);", "visibility": "external", "mutability": "", "signature": "serializeUint(string,string,uint256[])", "selector": "0xfee9a469", "selectorBytes": [ 254, 233, 164, 105 ] }, "group": "json", "status": "stable", "safety": "safe" }, { "func": { "id": "setArbitraryStorage_0", "description": "Utility cheatcode to set arbitrary storage for given target address.", "declaration": "function setArbitraryStorage(address target) external;", "visibility": "external", "mutability": "", "signature": "setArbitraryStorage(address)", "selector": "0xe1631837", "selectorBytes": [ 225, 99, 24, 55 ] }, "group": "utilities", "status": "stable", "safety": "safe" }, { "func": { "id": "setArbitraryStorage_1", "description": "Utility cheatcode to set arbitrary storage for given target address and overwrite\nany storage slots that have been previously set.", "declaration": "function setArbitraryStorage(address target, bool overwrite) external;", "visibility": "external", "mutability": "", "signature": "setArbitraryStorage(address,bool)", "selector": "0xd3ec2a0b", "selectorBytes": [ 211, 236, 42, 11 ] }, "group": "utilities", "status": "stable", "safety": "safe" }, { "func": { "id": "setBlockhash", "description": "Set blockhash for the current block.\nIt only sets the blockhash for blocks where `block.number - 256 <= number < block.number`.", "declaration": "function setBlockhash(uint256 blockNumber, bytes32 blockHash) external;", "visibility": "external", "mutability": "", "signature": "setBlockhash(uint256,bytes32)", "selector": "0x5314b54a", "selectorBytes": [ 83, 20, 181, 74 ] }, "group": "evm", "status": "stable", "safety": "unsafe" }, { "func": { "id": "setEnv", "description": "Sets environment variables.", "declaration": "function setEnv(string calldata name, string calldata value) external;", "visibility": "external", "mutability": "", "signature": "setEnv(string,string)", "selector": "0x3d5923ee", "selectorBytes": [ 61, 89, 35, 238 ] }, "group": "environment", "status": "stable", "safety": "safe" }, { "func": { "id": "setEvmVersion", "description": "Set the exact test or script execution evm version, e.g. `berlin`, `cancun`.\n**Note:** The execution evm version is not the same as the compilation one.", "declaration": "function setEvmVersion(string calldata evm) external;", "visibility": "external", "mutability": "", "signature": "setEvmVersion(string)", "selector": "0x43179f5a", "selectorBytes": [ 67, 23, 159, 90 ] }, "group": "evm", "status": "stable", "safety": "safe" }, { "func": { "id": "setNonce", "description": "Sets the nonce of an account. Must be higher than the current nonce of the account.", "declaration": "function setNonce(address account, uint64 newNonce) external;", "visibility": "external", "mutability": "", "signature": "setNonce(address,uint64)", "selector": "0xf8e18b57", "selectorBytes": [ 248, 225, 139, 87 ] }, "group": "evm", "status": "stable", "safety": "unsafe" }, { "func": { "id": "setNonceUnsafe", "description": "Sets the nonce of an account to an arbitrary value.", "declaration": "function setNonceUnsafe(address account, uint64 newNonce) external;", "visibility": "external", "mutability": "", "signature": "setNonceUnsafe(address,uint64)", "selector": "0x9b67b21c", "selectorBytes": [ 155, 103, 178, 28 ] }, "group": "evm", "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", "description": "Randomly shuffles an array.", "declaration": "function shuffle(uint256[] calldata array) external returns (uint256[] memory);", "visibility": "external", "mutability": "", "signature": "shuffle(uint256[])", "selector": "0x54f1469c", "selectorBytes": [ 84, 241, 70, 156 ] }, "group": "utilities", "status": "stable", "safety": "safe" }, { "func": { "id": "signAndAttachDelegation_0", "description": "Sign an EIP-7702 authorization and designate the next call as an EIP-7702 transaction", "declaration": "function signAndAttachDelegation(address implementation, uint256 privateKey) external returns (SignedDelegation memory signedDelegation);", "visibility": "external", "mutability": "", "signature": "signAndAttachDelegation(address,uint256)", "selector": "0xc7fa7288", "selectorBytes": [ 199, 250, 114, 136 ] }, "group": "scripting", "status": "stable", "safety": "safe" }, { "func": { "id": "signAndAttachDelegation_1", "description": "Sign an EIP-7702 authorization and designate the next call as an EIP-7702 transaction for specific nonce", "declaration": "function signAndAttachDelegation(address implementation, uint256 privateKey, uint64 nonce) external returns (SignedDelegation memory signedDelegation);", "visibility": "external", "mutability": "", "signature": "signAndAttachDelegation(address,uint256,uint64)", "selector": "0xcde3e5be", "selectorBytes": [ 205, 227, 229, 190 ] }, "group": "scripting", "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", "description": "Signs data with a `Wallet`.\nReturns a compact signature (`r`, `vs`) as per EIP-2098, where `vs` encodes both the\nsignature's `s` value, and the recovery id `v` in a single bytes32.\nThis format reduces the signature size from 65 to 64 bytes.", "declaration": "function signCompact(Wallet calldata wallet, bytes32 digest) external pure returns (bytes32 r, bytes32 vs);", "visibility": "external", "mutability": "pure", "signature": "signCompact((address,uint256,uint256,uint256),bytes32)", "selector": "0x3d0e292f", "selectorBytes": [ 61, 14, 41, 47 ] }, "group": "crypto", "status": "stable", "safety": "safe" }, { "func": { "id": "signCompact_1", "description": "Signs `digest` with `privateKey` using the secp256k1 curve.\nReturns a compact signature (`r`, `vs`) as per EIP-2098, where `vs` encodes both the\nsignature's `s` value, and the recovery id `v` in a single bytes32.\nThis format reduces the signature size from 65 to 64 bytes.", "declaration": "function signCompact(uint256 privateKey, bytes32 digest) external pure returns (bytes32 r, bytes32 vs);", "visibility": "external", "mutability": "pure", "signature": "signCompact(uint256,bytes32)", "selector": "0xcc2a781f", "selectorBytes": [ 204, 42, 120, 31 ] }, "group": "crypto", "status": "stable", "safety": "safe" }, { "func": { "id": "signCompact_2", "description": "Signs `digest` with signer provided to script using the secp256k1 curve.\nReturns a compact signature (`r`, `vs`) as per EIP-2098, where `vs` encodes both the\nsignature's `s` value, and the recovery id `v` in a single bytes32.\nThis format reduces the signature size from 65 to 64 bytes.\nIf `--sender` is provided, the signer with provided address is used, otherwise,\nif exactly one signer is provided to the script, that signer is used.\nRaises error if signer passed through `--sender` does not match any unlocked signers or\nif `--sender` is not provided and not exactly one signer is passed to the script.", "declaration": "function signCompact(bytes32 digest) external pure returns (bytes32 r, bytes32 vs);", "visibility": "external", "mutability": "pure", "signature": "signCompact(bytes32)", "selector": "0xa282dc4b", "selectorBytes": [ 162, 130, 220, 75 ] }, "group": "crypto", "status": "stable", "safety": "safe" }, { "func": { "id": "signCompact_3", "description": "Signs `digest` with signer provided to script using the secp256k1 curve.\nReturns a compact signature (`r`, `vs`) as per EIP-2098, where `vs` encodes both the\nsignature's `s` value, and the recovery id `v` in a single bytes32.\nThis format reduces the signature size from 65 to 64 bytes.\nRaises error if none of the signers passed into the script have provided address.", "declaration": "function signCompact(address signer, bytes32 digest) external pure returns (bytes32 r, bytes32 vs);", "visibility": "external", "mutability": "pure", "signature": "signCompact(address,bytes32)", "selector": "0x8e2f97bf", "selectorBytes": [ 142, 47, 151, 191 ] }, "group": "crypto", "status": "stable", "safety": "safe" }, { "func": { "id": "signDelegation_0", "description": "Sign an EIP-7702 authorization for delegation", "declaration": "function signDelegation(address implementation, uint256 privateKey) external returns (SignedDelegation memory signedDelegation);", "visibility": "external", "mutability": "", "signature": "signDelegation(address,uint256)", "selector": "0x5b593c7b", "selectorBytes": [ 91, 89, 60, 123 ] }, "group": "scripting", "status": "stable", "safety": "safe" }, { "func": { "id": "signDelegation_1", "description": "Sign an EIP-7702 authorization for delegation for specific nonce", "declaration": "function signDelegation(address implementation, uint256 privateKey, uint64 nonce) external returns (SignedDelegation memory signedDelegation);", "visibility": "external", "mutability": "", "signature": "signDelegation(address,uint256,uint64)", "selector": "0xceba2ec3", "selectorBytes": [ 206, 186, 46, 195 ] }, "group": "scripting", "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": "signEd25519", "description": "Signs a message with namespace using Ed25519.\nThe signature covers namespace || message for domain separation.\nReturns a 64-byte Ed25519 signature.", "declaration": "function signEd25519(bytes calldata namespace, bytes calldata message, bytes32 privateKey) external pure returns (bytes memory signature);", "visibility": "external", "mutability": "pure", "signature": "signEd25519(bytes,bytes,bytes32)", "selector": "0xef609c65", "selectorBytes": [ 239, 96, 156, 101 ] }, "group": "crypto", "status": "stable", "safety": "safe" }, { "func": { "id": "signP256", "description": "Signs `digest` with `privateKey` using the secp256r1 curve.", "declaration": "function signP256(uint256 privateKey, bytes32 digest) external pure returns (bytes32 r, bytes32 s);", "visibility": "external", "mutability": "pure", "signature": "signP256(uint256,bytes32)", "selector": "0x83211b40", "selectorBytes": [ 131, 33, 27, 64 ] }, "group": "crypto", "status": "stable", "safety": "safe" }, { "func": { "id": "signWithNonceUnsafe", "description": "Signs `digest` with `privateKey` on the secp256k1 curve, using the given `nonce`\nas the raw ephemeral k value in ECDSA (instead of deriving it deterministically).", "declaration": "function signWithNonceUnsafe(uint256 privateKey, bytes32 digest, uint256 nonce) external pure returns (uint8 v, bytes32 r, bytes32 s);", "visibility": "external", "mutability": "pure", "signature": "signWithNonceUnsafe(uint256,bytes32,uint256)", "selector": "0x2012783a", "selectorBytes": [ 32, 18, 120, 58 ] }, "group": "crypto", "status": "stable", "safety": "safe" }, { "func": { "id": "sign_0", "description": "Signs data with a `Wallet`.", "declaration": "function sign(Wallet calldata wallet, bytes32 digest) external pure returns (uint8 v, bytes32 r, bytes32 s);", "visibility": "external", "mutability": "pure", "signature": "sign((address,uint256,uint256,uint256),bytes32)", "selector": "0xb25c5a25", "selectorBytes": [ 178, 92, 90, 37 ] }, "group": "crypto", "status": "stable", "safety": "safe" }, { "func": { "id": "sign_1", "description": "Signs `digest` with `privateKey` using the secp256k1 curve.", "declaration": "function sign(uint256 privateKey, bytes32 digest) external pure returns (uint8 v, bytes32 r, bytes32 s);", "visibility": "external", "mutability": "pure", "signature": "sign(uint256,bytes32)", "selector": "0xe341eaa4", "selectorBytes": [ 227, 65, 234, 164 ] }, "group": "crypto", "status": "stable", "safety": "safe" }, { "func": { "id": "sign_2", "description": "Signs `digest` with signer provided to script using the secp256k1 curve.\nIf `--sender` is provided, the signer with provided address is used, otherwise,\nif exactly one signer is provided to the script, that signer is used.\nRaises error if signer passed through `--sender` does not match any unlocked signers or\nif `--sender` is not provided and not exactly one signer is passed to the script.", "declaration": "function sign(bytes32 digest) external pure returns (uint8 v, bytes32 r, bytes32 s);", "visibility": "external", "mutability": "pure", "signature": "sign(bytes32)", "selector": "0x799cd333", "selectorBytes": [ 121, 156, 211, 51 ] }, "group": "crypto", "status": "stable", "safety": "safe" }, { "func": { "id": "sign_3", "description": "Signs `digest` with signer provided to script using the secp256k1 curve.\nRaises error if none of the signers passed into the script have provided address.", "declaration": "function sign(address signer, bytes32 digest) external pure returns (uint8 v, bytes32 r, bytes32 s);", "visibility": "external", "mutability": "pure", "signature": "sign(address,bytes32)", "selector": "0x8c1aa205", "selectorBytes": [ 140, 26, 162, 5 ] }, "group": "crypto", "status": "stable", "safety": "safe" }, { "func": { "id": "skip_0", "description": "Marks a test as skipped. Must be called at the top level of a test.", "declaration": "function skip(bool skipTest) external;", "visibility": "external", "mutability": "", "signature": "skip(bool)", "selector": "0xdd82d13e", "selectorBytes": [ 221, 130, 209, 62 ] }, "group": "testing", "status": "stable", "safety": "unsafe" }, { "func": { "id": "skip_1", "description": "Marks a test as skipped with a reason. Must be called at the top level of a test.", "declaration": "function skip(bool skipTest, string calldata reason) external;", "visibility": "external", "mutability": "", "signature": "skip(bool,string)", "selector": "0xc42a80a7", "selectorBytes": [ 196, 42, 128, 167 ] }, "group": "testing", "status": "stable", "safety": "unsafe" }, { "func": { "id": "sleep", "description": "Suspends execution of the main thread for `duration` milliseconds.", "declaration": "function sleep(uint256 duration) external;", "visibility": "external", "mutability": "", "signature": "sleep(uint256)", "selector": "0xfa9d8713", "selectorBytes": [ 250, 157, 135, 19 ] }, "group": "testing", "status": "stable", "safety": "safe" }, { "func": { "id": "snapshot", "description": "`snapshot` is being deprecated in favor of `snapshotState`. It will be removed in future versions.", "declaration": "function snapshot() external returns (uint256 snapshotId);", "visibility": "external", "mutability": "", "signature": "snapshot()", "selector": "0x9711715a", "selectorBytes": [ 151, 17, 113, 90 ] }, "group": "evm", "status": { "deprecated": "replaced by `snapshotState`" }, "safety": "unsafe" }, { "func": { "id": "snapshotGasLastCall_0", "description": "Snapshot capture the gas usage of the last call by name from the callee perspective.", "declaration": "function snapshotGasLastCall(string calldata name) external returns (uint256 gasUsed);", "visibility": "external", "mutability": "", "signature": "snapshotGasLastCall(string)", "selector": "0xdd9fca12", "selectorBytes": [ 221, 159, 202, 18 ] }, "group": "evm", "status": "stable", "safety": "unsafe" }, { "func": { "id": "snapshotGasLastCall_1", "description": "Snapshot capture the gas usage of the last call by name in a group from the callee perspective.", "declaration": "function snapshotGasLastCall(string calldata group, string calldata name) external returns (uint256 gasUsed);", "visibility": "external", "mutability": "", "signature": "snapshotGasLastCall(string,string)", "selector": "0x200c6772", "selectorBytes": [ 32, 12, 103, 114 ] }, "group": "evm", "status": "stable", "safety": "unsafe" }, { "func": { "id": "snapshotState", "description": "Snapshot the current state of the evm.\nReturns the ID of the snapshot that was created.\nTo revert a snapshot use `revertToState`.", "declaration": "function snapshotState() external returns (uint256 snapshotId);", "visibility": "external", "mutability": "", "signature": "snapshotState()", "selector": "0x9cd23835", "selectorBytes": [ 156, 210, 56, 53 ] }, "group": "evm", "status": "stable", "safety": "unsafe" }, { "func": { "id": "snapshotValue_0", "description": "Snapshot capture an arbitrary numerical value by name.\nThe group name is derived from the contract name.", "declaration": "function snapshotValue(string calldata name, uint256 value) external;", "visibility": "external", "mutability": "", "signature": "snapshotValue(string,uint256)", "selector": "0x51db805a", "selectorBytes": [ 81, 219, 128, 90 ] }, "group": "evm", "status": "stable", "safety": "unsafe" }, { "func": { "id": "snapshotValue_1", "description": "Snapshot capture an arbitrary numerical value by name in a group.", "declaration": "function snapshotValue(string calldata group, string calldata name, uint256 value) external;", "visibility": "external", "mutability": "", "signature": "snapshotValue(string,string,uint256)", "selector": "0x6d2b27d8", "selectorBytes": [ 109, 43, 39, 216 ] }, "group": "evm", "status": "stable", "safety": "unsafe" }, { "func": { "id": "sort", "description": "Sorts an array in ascending order.", "declaration": "function sort(uint256[] calldata array) external returns (uint256[] memory);", "visibility": "external", "mutability": "", "signature": "sort(uint256[])", "selector": "0x9ec8b026", "selectorBytes": [ 158, 200, 176, 38 ] }, "group": "utilities", "status": "stable", "safety": "safe" }, { "func": { "id": "split", "description": "Splits the given `string` into an array of strings divided by the `delimiter`.", "declaration": "function split(string calldata input, string calldata delimiter) external pure returns (string[] memory outputs);", "visibility": "external", "mutability": "pure", "signature": "split(string,string)", "selector": "0x8bb75533", "selectorBytes": [ 139, 183, 85, 51 ] }, "group": "string", "status": "stable", "safety": "safe" }, { "func": { "id": "startBroadcast_0", "description": "Has all subsequent calls (at this call depth only) create transactions that can later be signed and sent onchain.\nBroadcasting address is determined by checking the following in order:\n1. If `--sender` argument was provided, that address is used.\n2. If exactly one signer (e.g. private key, hw wallet, keystore) is set when `forge broadcast` is invoked, that signer is used.\n3. Otherwise, default foundry sender (1804c8AB1F12E6bbf3894d4083f33e07309d1f38) is used.", "declaration": "function startBroadcast() external;", "visibility": "external", "mutability": "", "signature": "startBroadcast()", "selector": "0x7fb5297f", "selectorBytes": [ 127, 181, 41, 127 ] }, "group": "scripting", "status": "stable", "safety": "safe" }, { "func": { "id": "startBroadcast_1", "description": "Has all subsequent calls (at this call depth only) create transactions with the address\nprovided that can later be signed and sent onchain.", "declaration": "function startBroadcast(address signer) external;", "visibility": "external", "mutability": "", "signature": "startBroadcast(address)", "selector": "0x7fec2a8d", "selectorBytes": [ 127, 236, 42, 141 ] }, "group": "scripting", "status": "stable", "safety": "safe" }, { "func": { "id": "startBroadcast_2", "description": "Has all subsequent calls (at this call depth only) create transactions with the private key\nprovided that can later be signed and sent onchain.", "declaration": "function startBroadcast(uint256 privateKey) external;", "visibility": "external", "mutability": "", "signature": "startBroadcast(uint256)", "selector": "0xce817d47", "selectorBytes": [ 206, 129, 125, 71 ] }, "group": "scripting", "status": "stable", "safety": "safe" }, { "func": { "id": "startDebugTraceRecording", "description": "Records the debug trace during the run.", "declaration": "function startDebugTraceRecording() external;", "visibility": "external", "mutability": "", "signature": "startDebugTraceRecording()", "selector": "0x419c8832", "selectorBytes": [ 65, 156, 136, 50 ] }, "group": "evm", "status": "stable", "safety": "safe" }, { "func": { "id": "startMappingRecording", "description": "Starts recording all map SSTOREs for later retrieval.", "declaration": "function startMappingRecording() external;", "visibility": "external", "mutability": "", "signature": "startMappingRecording()", "selector": "0x3e9705c0", "selectorBytes": [ 62, 151, 5, 192 ] }, "group": "evm", "status": "stable", "safety": "safe" }, { "func": { "id": "startPrank_0", "description": "Sets all subsequent calls' `msg.sender` to be the input address until `stopPrank` is called.", "declaration": "function startPrank(address msgSender) external;", "visibility": "external", "mutability": "", "signature": "startPrank(address)", "selector": "0x06447d56", "selectorBytes": [ 6, 68, 125, 86 ] }, "group": "evm", "status": "stable", "safety": "unsafe" }, { "func": { "id": "startPrank_1", "description": "Sets all subsequent calls' `msg.sender` to be the input address until `stopPrank` is called, and the `tx.origin` to be the second input.", "declaration": "function startPrank(address msgSender, address txOrigin) external;", "visibility": "external", "mutability": "", "signature": "startPrank(address,address)", "selector": "0x45b56078", "selectorBytes": [ 69, 181, 96, 120 ] }, "group": "evm", "status": "stable", "safety": "unsafe" }, { "func": { "id": "startPrank_2", "description": "Sets all subsequent delegate calls' `msg.sender` to be the input address until `stopPrank` is called.", "declaration": "function startPrank(address msgSender, bool delegateCall) external;", "visibility": "external", "mutability": "", "signature": "startPrank(address,bool)", "selector": "0x1cc0b435", "selectorBytes": [ 28, 192, 180, 53 ] }, "group": "evm", "status": "stable", "safety": "unsafe" }, { "func": { "id": "startPrank_3", "description": "Sets all subsequent delegate calls' `msg.sender` to be the input address until `stopPrank` is called, and the `tx.origin` to be the second input.", "declaration": "function startPrank(address msgSender, address txOrigin, bool delegateCall) external;", "visibility": "external", "mutability": "", "signature": "startPrank(address,address,bool)", "selector": "0x4eb859b5", "selectorBytes": [ 78, 184, 89, 181 ] }, "group": "evm", "status": "stable", "safety": "unsafe" }, { "func": { "id": "startSnapshotGas_0", "description": "Start a snapshot capture of the current gas usage by name.\nThe group name is derived from the contract name.", "declaration": "function startSnapshotGas(string calldata name) external;", "visibility": "external", "mutability": "", "signature": "startSnapshotGas(string)", "selector": "0x3cad9d7b", "selectorBytes": [ 60, 173, 157, 123 ] }, "group": "evm", "status": "stable", "safety": "unsafe" }, { "func": { "id": "startSnapshotGas_1", "description": "Start a snapshot capture of the current gas usage by name in a group.", "declaration": "function startSnapshotGas(string calldata group, string calldata name) external;", "visibility": "external", "mutability": "", "signature": "startSnapshotGas(string,string)", "selector": "0x6cd0cc53", "selectorBytes": [ 108, 208, 204, 83 ] }, "group": "evm", "status": "stable", "safety": "unsafe" }, { "func": { "id": "startStateDiffRecording", "description": "Record all account accesses as part of CREATE, CALL or SELFDESTRUCT opcodes in order,\nalong with the context of the calls", "declaration": "function startStateDiffRecording() external;", "visibility": "external", "mutability": "", "signature": "startStateDiffRecording()", "selector": "0xcf22e3c9", "selectorBytes": [ 207, 34, 227, 201 ] }, "group": "evm", "status": "stable", "safety": "safe" }, { "func": { "id": "stopAndReturnDebugTraceRecording", "description": "Stop debug trace recording and returns the recorded debug trace.", "declaration": "function stopAndReturnDebugTraceRecording() external returns (DebugStep[] memory step);", "visibility": "external", "mutability": "", "signature": "stopAndReturnDebugTraceRecording()", "selector": "0xced398a2", "selectorBytes": [ 206, 211, 152, 162 ] }, "group": "evm", "status": "stable", "safety": "safe" }, { "func": { "id": "stopAndReturnStateDiff", "description": "Returns an ordered array of all account accesses from a `vm.startStateDiffRecording` session.", "declaration": "function stopAndReturnStateDiff() external returns (AccountAccess[] memory accountAccesses);", "visibility": "external", "mutability": "", "signature": "stopAndReturnStateDiff()", "selector": "0xaa5cf90e", "selectorBytes": [ 170, 92, 249, 14 ] }, "group": "evm", "status": "stable", "safety": "safe" }, { "func": { "id": "stopBroadcast", "description": "Stops collecting onchain transactions.", "declaration": "function stopBroadcast() external;", "visibility": "external", "mutability": "", "signature": "stopBroadcast()", "selector": "0x76eadd36", "selectorBytes": [ 118, 234, 221, 54 ] }, "group": "scripting", "status": "stable", "safety": "safe" }, { "func": { "id": "stopExpectSafeMemory", "description": "Stops all safe memory expectation in the current subcontext.", "declaration": "function stopExpectSafeMemory() external;", "visibility": "external", "mutability": "", "signature": "stopExpectSafeMemory()", "selector": "0x0956441b", "selectorBytes": [ 9, 86, 68, 27 ] }, "group": "testing", "status": "stable", "safety": "unsafe" }, { "func": { "id": "stopMappingRecording", "description": "Stops recording all map SSTOREs for later retrieval and clears the recorded data.", "declaration": "function stopMappingRecording() external;", "visibility": "external", "mutability": "", "signature": "stopMappingRecording()", "selector": "0x0d4aae9b", "selectorBytes": [ 13, 74, 174, 155 ] }, "group": "evm", "status": "stable", "safety": "safe" }, { "func": { "id": "stopPrank", "description": "Resets subsequent calls' `msg.sender` to be `address(this)`.", "declaration": "function stopPrank() external;", "visibility": "external", "mutability": "", "signature": "stopPrank()", "selector": "0x90c5013b", "selectorBytes": [ 144, 197, 1, 59 ] }, "group": "evm", "status": "stable", "safety": "unsafe" }, { "func": { "id": "stopRecord", "description": "Stops recording storage reads and writes.", "declaration": "function stopRecord() external;", "visibility": "external", "mutability": "", "signature": "stopRecord()", "selector": "0x996be76d", "selectorBytes": [ 153, 107, 231, 109 ] }, "group": "evm", "status": "stable", "safety": "safe" }, { "func": { "id": "stopSnapshotGas_0", "description": "Stop the snapshot capture of the current gas by latest snapshot name, capturing the gas used since the start.", "declaration": "function stopSnapshotGas() external returns (uint256 gasUsed);", "visibility": "external", "mutability": "", "signature": "stopSnapshotGas()", "selector": "0xf6402eda", "selectorBytes": [ 246, 64, 46, 218 ] }, "group": "evm", "status": "stable", "safety": "unsafe" }, { "func": { "id": "stopSnapshotGas_1", "description": "Stop the snapshot capture of the current gas usage by name, capturing the gas used since the start.\nThe group name is derived from the contract name.", "declaration": "function stopSnapshotGas(string calldata name) external returns (uint256 gasUsed);", "visibility": "external", "mutability": "", "signature": "stopSnapshotGas(string)", "selector": "0x773b2805", "selectorBytes": [ 119, 59, 40, 5 ] }, "group": "evm", "status": "stable", "safety": "unsafe" }, { "func": { "id": "stopSnapshotGas_2", "description": "Stop the snapshot capture of the current gas usage by name in a group, capturing the gas used since the start.", "declaration": "function stopSnapshotGas(string calldata group, string calldata name) external returns (uint256 gasUsed);", "visibility": "external", "mutability": "", "signature": "stopSnapshotGas(string,string)", "selector": "0x0c9db707", "selectorBytes": [ 12, 157, 183, 7 ] }, "group": "evm", "status": "stable", "safety": "unsafe" }, { "func": { "id": "store", "description": "Stores a value to an address' storage slot.", "declaration": "function store(address target, bytes32 slot, bytes32 value) external;", "visibility": "external", "mutability": "", "signature": "store(address,bytes32,bytes32)", "selector": "0x70ca10bb", "selectorBytes": [ 112, 202, 16, 187 ] }, "group": "evm", "status": "stable", "safety": "unsafe" }, { "func": { "id": "toBase64URL_0", "description": "Encodes a `bytes` value to a base64url string.", "declaration": "function toBase64URL(bytes calldata data) external pure returns (string memory);", "visibility": "external", "mutability": "pure", "signature": "toBase64URL(bytes)", "selector": "0xc8bd0e4a", "selectorBytes": [ 200, 189, 14, 74 ] }, "group": "utilities", "status": "stable", "safety": "safe" }, { "func": { "id": "toBase64URL_1", "description": "Encodes a `string` value to a base64url string.", "declaration": "function toBase64URL(string calldata data) external pure returns (string memory);", "visibility": "external", "mutability": "pure", "signature": "toBase64URL(string)", "selector": "0xae3165b3", "selectorBytes": [ 174, 49, 101, 179 ] }, "group": "utilities", "status": "stable", "safety": "safe" }, { "func": { "id": "toBase64_0", "description": "Encodes a `bytes` value to a base64 string.", "declaration": "function toBase64(bytes calldata data) external pure returns (string memory);", "visibility": "external", "mutability": "pure", "signature": "toBase64(bytes)", "selector": "0xa5cbfe65", "selectorBytes": [ 165, 203, 254, 101 ] }, "group": "utilities", "status": "stable", "safety": "safe" }, { "func": { "id": "toBase64_1", "description": "Encodes a `string` value to a base64 string.", "declaration": "function toBase64(string calldata data) external pure returns (string memory);", "visibility": "external", "mutability": "pure", "signature": "toBase64(string)", "selector": "0x3f8be2c8", "selectorBytes": [ 63, 139, 226, 200 ] }, "group": "utilities", "status": "stable", "safety": "safe" }, { "func": { "id": "toLowercase", "description": "Converts the given `string` value to Lowercase.", "declaration": "function toLowercase(string calldata input) external pure returns (string memory output);", "visibility": "external", "mutability": "pure", "signature": "toLowercase(string)", "selector": "0x50bb0884", "selectorBytes": [ 80, 187, 8, 132 ] }, "group": "string", "status": "stable", "safety": "safe" }, { "func": { "id": "toRlp", "description": "RLP encodes a list of bytes into an RLP payload.", "declaration": "function toRlp(bytes[] calldata data) external pure returns (bytes memory);", "visibility": "external", "mutability": "pure", "signature": "toRlp(bytes[])", "selector": "0xa7ed3885", "selectorBytes": [ 167, 237, 56, 133 ] }, "group": "utilities", "status": "stable", "safety": "safe" }, { "func": { "id": "toString_0", "description": "Converts the given value to a `string`.", "declaration": "function toString(address value) external pure returns (string memory stringifiedValue);", "visibility": "external", "mutability": "pure", "signature": "toString(address)", "selector": "0x56ca623e", "selectorBytes": [ 86, 202, 98, 62 ] }, "group": "string", "status": "stable", "safety": "safe" }, { "func": { "id": "toString_1", "description": "Converts the given value to a `string`.", "declaration": "function toString(bytes calldata value) external pure returns (string memory stringifiedValue);", "visibility": "external", "mutability": "pure", "signature": "toString(bytes)", "selector": "0x71aad10d", "selectorBytes": [ 113, 170, 209, 13 ] }, "group": "string", "status": "stable", "safety": "safe" }, { "func": { "id": "toString_2", "description": "Converts the given value to a `string`.", "declaration": "function toString(bytes32 value) external pure returns (string memory stringifiedValue);", "visibility": "external", "mutability": "pure", "signature": "toString(bytes32)", "selector": "0xb11a19e8", "selectorBytes": [ 177, 26, 25, 232 ] }, "group": "string", "status": "stable", "safety": "safe" }, { "func": { "id": "toString_3", "description": "Converts the given value to a `string`.", "declaration": "function toString(bool value) external pure returns (string memory stringifiedValue);", "visibility": "external", "mutability": "pure", "signature": "toString(bool)", "selector": "0x71dce7da", "selectorBytes": [ 113, 220, 231, 218 ] }, "group": "string", "status": "stable", "safety": "safe" }, { "func": { "id": "toString_4", "description": "Converts the given value to a `string`.", "declaration": "function toString(uint256 value) external pure returns (string memory stringifiedValue);", "visibility": "external", "mutability": "pure", "signature": "toString(uint256)", "selector": "0x6900a3ae", "selectorBytes": [ 105, 0, 163, 174 ] }, "group": "string", "status": "stable", "safety": "safe" }, { "func": { "id": "toString_5", "description": "Converts the given value to a `string`.", "declaration": "function toString(int256 value) external pure returns (string memory stringifiedValue);", "visibility": "external", "mutability": "pure", "signature": "toString(int256)", "selector": "0xa322c40e", "selectorBytes": [ 163, 34, 196, 14 ] }, "group": "string", "status": "stable", "safety": "safe" }, { "func": { "id": "toUppercase", "description": "Converts the given `string` value to Uppercase.", "declaration": "function toUppercase(string calldata input) external pure returns (string memory output);", "visibility": "external", "mutability": "pure", "signature": "toUppercase(string)", "selector": "0x074ae3d7", "selectorBytes": [ 7, 74, 227, 215 ] }, "group": "string", "status": "stable", "safety": "safe" }, { "func": { "id": "transact_0", "description": "Fetches the given transaction from the active fork and executes it on the current state.", "declaration": "function transact(bytes32 txHash) external;", "visibility": "external", "mutability": "", "signature": "transact(bytes32)", "selector": "0xbe646da1", "selectorBytes": [ 190, 100, 109, 161 ] }, "group": "evm", "status": "stable", "safety": "unsafe" }, { "func": { "id": "transact_1", "description": "Fetches the given transaction from the given fork and executes it on the current state.", "declaration": "function transact(uint256 forkId, bytes32 txHash) external;", "visibility": "external", "mutability": "", "signature": "transact(uint256,bytes32)", "selector": "0x4d8abc4b", "selectorBytes": [ 77, 138, 188, 75 ] }, "group": "evm", "status": "stable", "safety": "unsafe" }, { "func": { "id": "trim", "description": "Trims leading and trailing whitespace from the given `string` value.", "declaration": "function trim(string calldata input) external pure returns (string memory output);", "visibility": "external", "mutability": "pure", "signature": "trim(string)", "selector": "0xb2dad155", "selectorBytes": [ 178, 218, 209, 85 ] }, "group": "string", "status": "stable", "safety": "safe" }, { "func": { "id": "tryFfi", "description": "Performs a foreign function call via terminal and returns the exit code, stdout, and stderr.", "declaration": "function tryFfi(string[] calldata commandInput) external returns (FfiResult memory result);", "visibility": "external", "mutability": "", "signature": "tryFfi(string[])", "selector": "0xf45c1ce7", "selectorBytes": [ 244, 92, 28, 231 ] }, "group": "filesystem", "status": "stable", "safety": "safe" }, { "func": { "id": "txGasPrice", "description": "Sets `tx.gasprice`.", "declaration": "function txGasPrice(uint256 newGasPrice) external;", "visibility": "external", "mutability": "", "signature": "txGasPrice(uint256)", "selector": "0x48f50c0f", "selectorBytes": [ 72, 245, 12, 15 ] }, "group": "evm", "status": "stable", "safety": "unsafe" }, { "func": { "id": "unixTime", "description": "Returns the time since unix epoch in milliseconds.", "declaration": "function unixTime() external view returns (uint256 milliseconds);", "visibility": "external", "mutability": "view", "signature": "unixTime()", "selector": "0x625387dc", "selectorBytes": [ 98, 83, 135, 220 ] }, "group": "filesystem", "status": "stable", "safety": "safe" }, { "func": { "id": "verifyEd25519", "description": "Verifies an Ed25519 signature over namespace || message.\nReturns true if signature is valid, false otherwise.", "declaration": "function verifyEd25519(bytes calldata signature, bytes calldata namespace, bytes calldata message, bytes32 publicKey) external pure returns (bool valid);", "visibility": "external", "mutability": "pure", "signature": "verifyEd25519(bytes,bytes,bytes,bytes32)", "selector": "0xd08c2888", "selectorBytes": [ 208, 140, 40, 136 ] }, "group": "crypto", "status": "stable", "safety": "safe" }, { "func": { "id": "warmSlot", "description": "Utility cheatcode to mark specific storage slot as warm, simulating a prior read.", "declaration": "function warmSlot(address target, bytes32 slot) external;", "visibility": "external", "mutability": "", "signature": "warmSlot(address,bytes32)", "selector": "0xb23184cf", "selectorBytes": [ 178, 49, 132, 207 ] }, "group": "evm", "status": "stable", "safety": "unsafe" }, { "func": { "id": "warp", "description": "Sets `block.timestamp`.", "declaration": "function warp(uint256 newTimestamp) external;", "visibility": "external", "mutability": "", "signature": "warp(uint256)", "selector": "0xe5d6bf02", "selectorBytes": [ 229, 214, 191, 2 ] }, "group": "evm", "status": "stable", "safety": "unsafe" }, { "func": { "id": "writeFile", "description": "Writes data to file, creating a file if it does not exist, and entirely replacing its contents if it does.\n`path` is relative to the project root.", "declaration": "function writeFile(string calldata path, string calldata data) external;", "visibility": "external", "mutability": "", "signature": "writeFile(string,string)", "selector": "0x897e0a97", "selectorBytes": [ 137, 126, 10, 151 ] }, "group": "filesystem", "status": "stable", "safety": "safe" }, { "func": { "id": "writeFileBinary", "description": "Writes binary data to a file, creating a file if it does not exist, and entirely replacing its contents if it does.\n`path` is relative to the project root.", "declaration": "function writeFileBinary(string calldata path, bytes calldata data) external;", "visibility": "external", "mutability": "", "signature": "writeFileBinary(string,bytes)", "selector": "0x1f21fc80", "selectorBytes": [ 31, 33, 252, 128 ] }, "group": "filesystem", "status": "stable", "safety": "safe" }, { "func": { "id": "writeJson_0", "description": "Write a serialized JSON object to a file. If the file exists, it will be overwritten.", "declaration": "function writeJson(string calldata json, string calldata path) external;", "visibility": "external", "mutability": "", "signature": "writeJson(string,string)", "selector": "0xe23cd19f", "selectorBytes": [ 226, 60, 209, 159 ] }, "group": "json", "status": "stable", "safety": "safe" }, { "func": { "id": "writeJson_1", "description": "Write a serialized JSON object to an **existing** JSON file, replacing a value with key = \nThis is useful to replace a specific value of a JSON file, without having to parse the entire thing.\nThis cheatcode will create new keys if they didn't previously exist.", "declaration": "function writeJson(string calldata json, string calldata path, string calldata valueKey) external;", "visibility": "external", "mutability": "", "signature": "writeJson(string,string,string)", "selector": "0x35d6ad46", "selectorBytes": [ 53, 214, 173, 70 ] }, "group": "json", "status": "stable", "safety": "safe" }, { "func": { "id": "writeLine", "description": "Writes line to file, creating a file if it does not exist.\n`path` is relative to the project root.", "declaration": "function writeLine(string calldata path, string calldata data) external;", "visibility": "external", "mutability": "", "signature": "writeLine(string,string)", "selector": "0x619d897f", "selectorBytes": [ 97, 157, 137, 127 ] }, "group": "filesystem", "status": "stable", "safety": "safe" }, { "func": { "id": "writeToml_0", "description": "Takes serialized JSON, converts to TOML and write a serialized TOML to a file.", "declaration": "function writeToml(string calldata json, string calldata path) external;", "visibility": "external", "mutability": "", "signature": "writeToml(string,string)", "selector": "0xc0865ba7", "selectorBytes": [ 192, 134, 91, 167 ] }, "group": "toml", "status": "stable", "safety": "safe" }, { "func": { "id": "writeToml_1", "description": "Takes serialized JSON, converts to TOML and write a serialized TOML table to an **existing** TOML file, replacing a value with key = \nThis is useful to replace a specific value of a TOML file, without having to parse the entire thing.\nThis cheatcode will create new keys if they didn't previously exist.", "declaration": "function writeToml(string calldata json, string calldata path, string calldata valueKey) external;", "visibility": "external", "mutability": "", "signature": "writeToml(string,string,string)", "selector": "0x51ac6a33", "selectorBytes": [ 81, 172, 106, 51 ] }, "group": "toml", "status": "stable", "safety": "safe" } ] } ================================================ FILE: crates/cheatcodes/assets/cheatcodes.schema.json ================================================ { "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "Cheatcodes", "description": "Foundry cheatcodes. Learn more: ", "type": "object", "properties": { "cheatcodes": { "description": "All the cheatcodes.", "type": "array", "items": { "$ref": "#/$defs/Cheatcode" } }, "enums": { "description": "Cheatcode enums.", "type": "array", "items": { "$ref": "#/$defs/Enum" } }, "errors": { "description": "Cheatcode errors.", "type": "array", "items": { "$ref": "#/$defs/Error" } }, "events": { "description": "Cheatcode events.", "type": "array", "items": { "$ref": "#/$defs/Event" } }, "structs": { "description": "Cheatcode structs.", "type": "array", "items": { "$ref": "#/$defs/Struct" } } }, "required": [ "errors", "events", "enums", "structs", "cheatcodes" ], "$defs": { "Cheatcode": { "description": "Specification of a single cheatcode. Extends [`Function`] with additional metadata.", "type": "object", "properties": { "func": { "description": "The Solidity function declaration.", "$ref": "#/$defs/Function" }, "group": { "description": "The group that the cheatcode belongs to.", "$ref": "#/$defs/Group" }, "safety": { "description": "Whether the cheatcode is safe to use inside of scripts. E.g. it does not change state in an\nunexpected way.", "$ref": "#/$defs/Safety" }, "status": { "description": "The current status of the cheatcode. E.g. whether it is stable or experimental, etc.", "$ref": "#/$defs/Status" } }, "additionalProperties": false, "required": [ "func", "group", "status", "safety" ] }, "Enum": { "description": "A Solidity enumeration.", "type": "object", "properties": { "description": { "description": "The description of the enum.\nThis is a markdown string derived from the NatSpec documentation.", "type": "string" }, "name": { "description": "The name of the enum.", "type": "string" }, "variants": { "description": "The variants of the enum.", "type": "array", "items": { "$ref": "#/$defs/EnumVariant" } } }, "required": [ "name", "description", "variants" ] }, "EnumVariant": { "description": "A variant of an [`Enum`].", "type": "object", "properties": { "description": { "description": "The description of the variant.\nThis is a markdown string derived from the NatSpec documentation.", "type": "string" }, "name": { "description": "The name of the variant.", "type": "string" } }, "required": [ "name", "description" ] }, "Error": { "description": "A Solidity custom error.", "type": "object", "properties": { "declaration": { "description": "The Solidity error declaration, including full type, parameter names, etc.", "type": "string" }, "description": { "description": "The description of the error.\nThis is a markdown string derived from the NatSpec documentation.", "type": "string" }, "name": { "description": "The name of the error.", "type": "string" } }, "required": [ "name", "description", "declaration" ] }, "Event": { "description": "A Solidity event.", "type": "object", "properties": { "declaration": { "description": "The Solidity event declaration, including full type, parameter names, etc.", "type": "string" }, "description": { "description": "The description of the event.\nThis is a markdown string derived from the NatSpec documentation.", "type": "string" }, "name": { "description": "The name of the event.", "type": "string" } }, "required": [ "name", "description", "declaration" ] }, "Function": { "description": "Solidity function.", "type": "object", "properties": { "declaration": { "description": "The Solidity function declaration, including full type and parameter names, visibility,\netc.", "type": "string" }, "description": { "description": "The description of the function.\nThis is a markdown string derived from the NatSpec documentation.", "type": "string" }, "id": { "description": "The function's unique identifier. This is the function name, optionally appended with an\nindex if it is overloaded.", "type": "string" }, "mutability": { "description": "The Solidity function state mutability attribute.", "$ref": "#/$defs/Mutability" }, "selector": { "description": "The hex-encoded, \"0x\"-prefixed 4-byte function selector,\nwhich is the Keccak-256 hash of `signature`.", "type": "string" }, "selectorBytes": { "description": "The 4-byte function selector as a byte array.", "type": "array", "items": { "type": "integer", "format": "uint8", "maximum": 255, "minimum": 0 }, "maxItems": 4, "minItems": 4 }, "signature": { "description": "The standard function signature used to calculate `selector`.\nSee the [Solidity docs] for more information.\n\n[Solidity docs]: https://docs.soliditylang.org/en/latest/abi-spec.html#function-selector", "type": "string" }, "visibility": { "description": "The Solidity function visibility attribute. This is currently always `external`, but this\nmay change in the future.", "$ref": "#/$defs/Visibility" } }, "required": [ "id", "description", "declaration", "visibility", "mutability", "signature", "selector", "selectorBytes" ] }, "Group": { "description": "Cheatcode groups.\nInitially derived and modified from inline comments in [`forge-std`'s `Vm.sol`][vmsol].\n\n[vmsol]: https://github.com/foundry-rs/forge-std/blob/dcb0d52bc4399d37a6545848e3b8f9d03c77b98d/src/Vm.sol", "oneOf": [ { "description": "Cheatcodes that read from, or write to the current EVM execution state.\n\nExamples: any of the `record` cheatcodes, `chainId`, `coinbase`.\n\nSafety: ambiguous, depends on whether the cheatcode is read-only or not.", "type": "string", "const": "evm" }, { "description": "Cheatcodes that interact with how a test is run.\n\nExamples: `assume`, `skip`, `expectRevert`.\n\nSafety: ambiguous, depends on whether the cheatcode is read-only or not.", "type": "string", "const": "testing" }, { "description": "Cheatcodes that interact with how a script is run.\n\nExamples: `broadcast`, `startBroadcast`, `stopBroadcast`.\n\nSafety: safe.", "type": "string", "const": "scripting" }, { "description": "Cheatcodes that interact with the OS or filesystem.\n\nExamples: `ffi`, `projectRoot`, `writeFile`.\n\nSafety: safe.", "type": "string", "const": "filesystem" }, { "description": "Cheatcodes that interact with the program's environment variables.\n\nExamples: `setEnv`, `envBool`, `envOr`.\n\nSafety: safe.", "type": "string", "const": "environment" }, { "description": "Utility cheatcodes that deal with string parsing and manipulation.\n\nExamples: `toString`. `parseBytes`.\n\nSafety: safe.", "type": "string", "const": "string" }, { "description": "Utility cheatcodes that deal with parsing values from and converting values to JSON.\n\nExamples: `serializeJson`, `parseJsonUint`, `writeJson`.\n\nSafety: safe.", "type": "string", "const": "json" }, { "description": "Utility cheatcodes that deal with parsing values from and converting values to TOML.\n\nExamples: `parseToml`, `writeToml`.\n\nSafety: safe.", "type": "string", "const": "toml" }, { "description": "Cryptography-related cheatcodes.\n\nExamples: `sign*`.\n\nSafety: safe.", "type": "string", "const": "crypto" }, { "description": "Generic, uncategorized utilities.\n\nExamples: `toString`, `parse*`, `serialize*`.\n\nSafety: safe.", "type": "string", "const": "utilities" } ] }, "Mutability": { "description": "Solidity function state mutability attribute. See the [Solidity docs] for more information.\n\n[Solidity docs]: https://docs.soliditylang.org/en/latest/contracts.html#state-mutability", "oneOf": [ { "description": "Disallows modification or access of state.", "type": "string", "const": "pure" }, { "description": "Disallows modification of state.", "type": "string", "const": "view" }, { "description": "Allows modification of state.", "type": "string", "const": "" } ] }, "Safety": { "description": "Cheatcode safety.", "oneOf": [ { "description": "The cheatcode is not safe to use in scripts.", "type": "string", "const": "unsafe" }, { "description": "The cheatcode is safe to use in scripts.", "type": "string", "const": "safe" } ] }, "Status": { "description": "The status of a cheatcode.", "oneOf": [ { "description": "The cheatcode and its API is currently stable.", "type": "string", "const": "stable" }, { "description": "The cheatcode is unstable, meaning it may contain bugs and may break its API on any\nrelease.\n\nUse of experimental cheatcodes will result in a warning.", "type": "string", "const": "experimental" }, { "description": "The cheatcode has been deprecated, meaning it will be removed in a future release.\n\nContains the optional reason for deprecation.\n\nUse of deprecated cheatcodes is discouraged and will result in a warning.", "type": "object", "properties": { "deprecated": { "type": [ "string", "null" ] } }, "additionalProperties": false, "required": [ "deprecated" ] }, { "description": "The cheatcode has been removed and is no longer available for use.\n\nUse of removed cheatcodes will result in a hard error.", "type": "string", "const": "removed" }, { "description": "The cheatcode is only used internally for foundry testing and may be changed or removed at\nany time.\n\nUse of internal cheatcodes is discouraged and will result in a warning.", "type": "string", "const": "internal" } ] }, "Struct": { "description": "A Solidity struct.", "type": "object", "properties": { "description": { "description": "The description of the struct.\nThis is a markdown string derived from the NatSpec documentation.", "type": "string" }, "fields": { "description": "The fields of the struct.", "type": "array", "items": { "$ref": "#/$defs/StructField" } }, "name": { "description": "The name of the struct.", "type": "string" } }, "required": [ "name", "description", "fields" ] }, "StructField": { "description": "A [`Struct`] field.", "type": "object", "properties": { "description": { "description": "The description of the field.\nThis is a markdown string derived from the NatSpec documentation.", "type": "string" }, "name": { "description": "The name of the field.", "type": "string" }, "ty": { "description": "The type of the field.", "type": "string" } }, "required": [ "name", "ty", "description" ] }, "Visibility": { "description": "Solidity function visibility attribute. See the [Solidity docs] for more information.\n\n[Solidity docs]: https://docs.soliditylang.org/en/latest/contracts.html#function-visibility", "oneOf": [ { "description": "The function is only visible externally.", "type": "string", "const": "external" }, { "description": "Visible externally and internally.", "type": "string", "const": "public" }, { "description": "Only visible internally.", "type": "string", "const": "internal" }, { "description": "Only visible in the current contract", "type": "string", "const": "private" } ] } } } ================================================ FILE: crates/cheatcodes/spec/Cargo.toml ================================================ [package] name = "foundry-cheatcodes-spec" description = "Foundry cheatcodes specification" version.workspace = true edition.workspace = true rust-version.workspace = true authors.workspace = true license.workspace = true homepage.workspace = true repository.workspace = true exclude.workspace = true [lints] workspace = true [dependencies] foundry-macros.workspace = true alloy-sol-types = { workspace = true, features = ["json"] } serde.workspace = true # schema schemars = { version = "1.1", optional = true } [dev-dependencies] serde_json.workspace = true [features] schema = ["dep:schemars"] ================================================ FILE: crates/cheatcodes/spec/src/cheatcode.rs ================================================ use super::Function; use alloy_sol_types::SolCall; use serde::{Deserialize, Serialize}; /// Cheatcode definition trait. Implemented by all [`Vm`](crate::Vm) functions. pub trait CheatcodeDef: std::fmt::Debug + Clone + SolCall { /// The static cheatcode definition. const CHEATCODE: &'static Cheatcode<'static>; } /// Specification of a single cheatcode. Extends [`Function`] with additional metadata. #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] #[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] #[serde(deny_unknown_fields, rename_all = "camelCase")] #[non_exhaustive] pub struct Cheatcode<'a> { // Automatically-generated fields. /// The Solidity function declaration. #[serde(borrow)] pub func: Function<'a>, // Manually-specified fields. /// The group that the cheatcode belongs to. pub group: Group, /// The current status of the cheatcode. E.g. whether it is stable or experimental, etc. pub status: Status<'a>, /// Whether the cheatcode is safe to use inside of scripts. E.g. it does not change state in an /// unexpected way. pub safety: Safety, } /// The status of a cheatcode. #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] #[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] #[serde(rename_all = "camelCase")] #[non_exhaustive] pub enum Status<'a> { /// The cheatcode and its API is currently stable. Stable, /// The cheatcode is unstable, meaning it may contain bugs and may break its API on any /// release. /// /// Use of experimental cheatcodes will result in a warning. Experimental, /// The cheatcode has been deprecated, meaning it will be removed in a future release. /// /// Contains the optional reason for deprecation. /// /// Use of deprecated cheatcodes is discouraged and will result in a warning. Deprecated(Option<&'a str>), /// The cheatcode has been removed and is no longer available for use. /// /// Use of removed cheatcodes will result in a hard error. Removed, /// The cheatcode is only used internally for foundry testing and may be changed or removed at /// any time. /// /// Use of internal cheatcodes is discouraged and will result in a warning. Internal, } /// Cheatcode groups. /// Initially derived and modified from inline comments in [`forge-std`'s `Vm.sol`][vmsol]. /// /// [vmsol]: https://github.com/foundry-rs/forge-std/blob/dcb0d52bc4399d37a6545848e3b8f9d03c77b98d/src/Vm.sol #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] #[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] #[serde(rename_all = "camelCase")] #[non_exhaustive] pub enum Group { /// Cheatcodes that read from, or write to the current EVM execution state. /// /// Examples: any of the `record` cheatcodes, `chainId`, `coinbase`. /// /// Safety: ambiguous, depends on whether the cheatcode is read-only or not. Evm, /// Cheatcodes that interact with how a test is run. /// /// Examples: `assume`, `skip`, `expectRevert`. /// /// Safety: ambiguous, depends on whether the cheatcode is read-only or not. Testing, /// Cheatcodes that interact with how a script is run. /// /// Examples: `broadcast`, `startBroadcast`, `stopBroadcast`. /// /// Safety: safe. Scripting, /// Cheatcodes that interact with the OS or filesystem. /// /// Examples: `ffi`, `projectRoot`, `writeFile`. /// /// Safety: safe. Filesystem, /// Cheatcodes that interact with the program's environment variables. /// /// Examples: `setEnv`, `envBool`, `envOr`. /// /// Safety: safe. Environment, /// Utility cheatcodes that deal with string parsing and manipulation. /// /// Examples: `toString`. `parseBytes`. /// /// Safety: safe. String, /// Utility cheatcodes that deal with parsing values from and converting values to JSON. /// /// Examples: `serializeJson`, `parseJsonUint`, `writeJson`. /// /// Safety: safe. Json, /// Utility cheatcodes that deal with parsing values from and converting values to TOML. /// /// Examples: `parseToml`, `writeToml`. /// /// Safety: safe. Toml, /// Cryptography-related cheatcodes. /// /// Examples: `sign*`. /// /// Safety: safe. Crypto, /// Generic, uncategorized utilities. /// /// Examples: `toString`, `parse*`, `serialize*`. /// /// Safety: safe. Utilities, } impl Group { /// Returns the safety of this cheatcode group. /// /// Some groups are inherently safe or unsafe, while others are ambiguous and will return /// `None`. pub const fn safety(self) -> Option { match self { Self::Evm | Self::Testing => None, Self::Scripting | Self::Filesystem | Self::Environment | Self::String | Self::Json | Self::Toml | Self::Crypto | Self::Utilities => Some(Safety::Safe), } } /// Returns this value as a string. pub const fn as_str(self) -> &'static str { match self { Self::Evm => "evm", Self::Testing => "testing", Self::Scripting => "scripting", Self::Filesystem => "filesystem", Self::Environment => "environment", Self::String => "string", Self::Json => "json", Self::Toml => "toml", Self::Crypto => "crypto", Self::Utilities => "utilities", } } } // TODO: Find a better name for this /// Cheatcode safety. #[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] #[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] #[serde(rename_all = "camelCase")] #[non_exhaustive] pub enum Safety { /// The cheatcode is not safe to use in scripts. Unsafe, /// The cheatcode is safe to use in scripts. #[default] Safe, } impl Safety { /// Returns this value as a string. pub const fn as_str(self) -> &'static str { match self { Self::Safe => "safe", Self::Unsafe => "unsafe", } } /// Returns whether this value is safe. pub const fn is_safe(self) -> bool { matches!(self, Self::Safe) } } ================================================ FILE: crates/cheatcodes/spec/src/function.rs ================================================ use serde::{Deserialize, Serialize}; use std::fmt; /// Solidity function. #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] #[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] #[serde(rename_all = "camelCase")] #[non_exhaustive] pub struct Function<'a> { /// The function's unique identifier. This is the function name, optionally appended with an /// index if it is overloaded. pub id: &'a str, /// The description of the function. /// This is a markdown string derived from the NatSpec documentation. pub description: &'a str, /// The Solidity function declaration, including full type and parameter names, visibility, /// etc. pub declaration: &'a str, /// The Solidity function visibility attribute. This is currently always `external`, but this /// may change in the future. pub visibility: Visibility, /// The Solidity function state mutability attribute. pub mutability: Mutability, /// The standard function signature used to calculate `selector`. /// See the [Solidity docs] for more information. /// /// [Solidity docs]: https://docs.soliditylang.org/en/latest/abi-spec.html#function-selector pub signature: &'a str, /// The hex-encoded, "0x"-prefixed 4-byte function selector, /// which is the Keccak-256 hash of `signature`. pub selector: &'a str, /// The 4-byte function selector as a byte array. pub selector_bytes: [u8; 4], } impl fmt::Display for Function<'_> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.write_str(self.declaration) } } /// Solidity function visibility attribute. See the [Solidity docs] for more information. /// /// [Solidity docs]: https://docs.soliditylang.org/en/latest/contracts.html#function-visibility #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] #[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] #[serde(rename_all = "camelCase")] pub enum Visibility { /// The function is only visible externally. External, /// Visible externally and internally. Public, /// Only visible internally. Internal, /// Only visible in the current contract Private, } impl fmt::Display for Visibility { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.write_str(self.as_str()) } } impl Visibility { /// Returns the string representation of the visibility. pub const fn as_str(self) -> &'static str { match self { Self::External => "external", Self::Public => "public", Self::Internal => "internal", Self::Private => "private", } } } /// Solidity function state mutability attribute. See the [Solidity docs] for more information. /// /// [Solidity docs]: https://docs.soliditylang.org/en/latest/contracts.html#state-mutability #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] #[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] #[serde(rename_all = "camelCase")] pub enum Mutability { /// Disallows modification or access of state. Pure, /// Disallows modification of state. View, /// Allows modification of state. #[serde(rename = "")] None, } impl fmt::Display for Mutability { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.write_str(self.as_str()) } } impl Mutability { /// Returns the string representation of the mutability. pub const fn as_str(self) -> &'static str { match self { Self::Pure => "pure", Self::View => "view", Self::None => "", } } } ================================================ FILE: crates/cheatcodes/spec/src/items.rs ================================================ use serde::{Deserialize, Serialize}; use std::{borrow::Cow, fmt}; /// A Solidity custom error. #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] #[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] #[serde(rename_all = "camelCase")] pub struct Error<'a> { /// The name of the error. pub name: &'a str, /// The description of the error. /// This is a markdown string derived from the NatSpec documentation. pub description: &'a str, /// The Solidity error declaration, including full type, parameter names, etc. pub declaration: &'a str, } impl fmt::Display for Error<'_> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.write_str(self.declaration) } } /// A Solidity event. #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] #[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] #[serde(rename_all = "camelCase")] pub struct Event<'a> { /// The name of the event. pub name: &'a str, /// The description of the event. /// This is a markdown string derived from the NatSpec documentation. pub description: &'a str, /// The Solidity event declaration, including full type, parameter names, etc. pub declaration: &'a str, } impl fmt::Display for Event<'_> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.write_str(self.declaration) } } /// A Solidity enumeration. #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] #[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] #[serde(rename_all = "camelCase")] pub struct Enum<'a> { /// The name of the enum. pub name: &'a str, /// The description of the enum. /// This is a markdown string derived from the NatSpec documentation. pub description: &'a str, /// The variants of the enum. #[serde(borrow)] pub variants: Cow<'a, [EnumVariant<'a>]>, } impl fmt::Display for Enum<'_> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "enum {} {{ ", self.name)?; for (i, variant) in self.variants.iter().enumerate() { if i > 0 { f.write_str(", ")?; } f.write_str(variant.name)?; } f.write_str(" }") } } /// A variant of an [`Enum`]. #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] #[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] #[serde(rename_all = "camelCase")] pub struct EnumVariant<'a> { /// The name of the variant. pub name: &'a str, /// The description of the variant. /// This is a markdown string derived from the NatSpec documentation. pub description: &'a str, } /// A Solidity struct. #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] #[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] #[serde(rename_all = "camelCase")] pub struct Struct<'a> { /// The name of the struct. pub name: &'a str, /// The description of the struct. /// This is a markdown string derived from the NatSpec documentation. pub description: &'a str, /// The fields of the struct. #[serde(borrow)] pub fields: Cow<'a, [StructField<'a>]>, } impl fmt::Display for Struct<'_> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "struct {} {{ ", self.name)?; for field in self.fields.iter() { write!(f, "{} {}; ", field.ty, field.name)?; } f.write_str("}") } } /// A [`Struct`] field. #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] #[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] #[serde(rename_all = "camelCase")] pub struct StructField<'a> { /// The name of the field. pub name: &'a str, /// The type of the field. pub ty: &'a str, /// The description of the field. /// This is a markdown string derived from the NatSpec documentation. pub description: &'a str, } ================================================ FILE: crates/cheatcodes/spec/src/lib.rs ================================================ //! Cheatcode specification for Foundry. #![cfg_attr(not(test), warn(unused_crate_dependencies))] #![cfg_attr(docsrs, feature(doc_cfg))] use serde::{Deserialize, Serialize}; use std::{borrow::Cow, fmt}; mod cheatcode; pub use cheatcode::{Cheatcode, CheatcodeDef, Group, Safety, Status}; mod function; pub use function::{Function, Mutability, Visibility}; mod items; pub use items::{Enum, EnumVariant, Error, Event, Struct, StructField}; mod vm; pub use vm::Vm; // The `cheatcodes.json` schema. /// Foundry cheatcodes. Learn more: #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] #[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] #[serde(rename_all = "camelCase")] pub struct Cheatcodes<'a> { /// Cheatcode errors. #[serde(borrow)] pub errors: Cow<'a, [Error<'a>]>, /// Cheatcode events. #[serde(borrow)] pub events: Cow<'a, [Event<'a>]>, /// Cheatcode enums. #[serde(borrow)] pub enums: Cow<'a, [Enum<'a>]>, /// Cheatcode structs. #[serde(borrow)] pub structs: Cow<'a, [Struct<'a>]>, /// All the cheatcodes. #[serde(borrow)] pub cheatcodes: Cow<'a, [Cheatcode<'a>]>, } impl fmt::Display for Cheatcodes<'_> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { for error in self.errors.iter() { writeln!(f, "{error}")?; } for event in self.events.iter() { writeln!(f, "{event}")?; } for enumm in self.enums.iter() { writeln!(f, "{enumm}")?; } for strukt in self.structs.iter() { writeln!(f, "{strukt}")?; } for cheatcode in self.cheatcodes.iter() { writeln!(f, "{}", cheatcode.func)?; } Ok(()) } } impl Default for Cheatcodes<'static> { fn default() -> Self { Self::new() } } impl Cheatcodes<'static> { /// Returns the default cheatcodes. pub fn new() -> Self { Self { // unfortunately technology has not yet advanced to the point where we can get all // items of a certain type in a module, so we have to hardcode them here structs: Cow::Owned(vec![ Vm::Log::STRUCT.clone(), Vm::Rpc::STRUCT.clone(), Vm::EthGetLogs::STRUCT.clone(), Vm::DirEntry::STRUCT.clone(), Vm::FsMetadata::STRUCT.clone(), Vm::Wallet::STRUCT.clone(), Vm::FfiResult::STRUCT.clone(), Vm::ChainInfo::STRUCT.clone(), Vm::Chain::STRUCT.clone(), Vm::AccountAccess::STRUCT.clone(), Vm::StorageAccess::STRUCT.clone(), Vm::Gas::STRUCT.clone(), Vm::DebugStep::STRUCT.clone(), Vm::BroadcastTxSummary::STRUCT.clone(), Vm::SignedDelegation::STRUCT.clone(), Vm::PotentialRevert::STRUCT.clone(), Vm::AccessListItem::STRUCT.clone(), ]), enums: Cow::Owned(vec![ Vm::CallerMode::ENUM.clone(), Vm::AccountAccessKind::ENUM.clone(), Vm::ForgeContext::ENUM.clone(), Vm::BroadcastTxType::ENUM.clone(), ]), errors: Vm::VM_ERRORS.iter().copied().cloned().collect(), events: Cow::Borrowed(&[]), // events: Vm::VM_EVENTS.iter().copied().cloned().collect(), cheatcodes: Vm::CHEATCODES.iter().copied().cloned().collect(), } } } #[cfg(test)] #[expect(clippy::disallowed_macros)] mod tests { use super::*; use std::{fs, path::Path}; const JSON_PATH: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/../assets/cheatcodes.json"); #[cfg(feature = "schema")] const SCHEMA_PATH: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/../assets/cheatcodes.schema.json"); const IFACE_PATH: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/../../../testdata/utils/Vm.sol"); /// Generates the `cheatcodes.json` file contents. fn json_cheatcodes() -> String { serde_json::to_string_pretty(&Cheatcodes::new()).unwrap() } /// Generates the [cheatcodes](json_cheatcodes) JSON schema. #[cfg(feature = "schema")] fn json_schema() -> String { serde_json::to_string_pretty(&schemars::schema_for!(Cheatcodes<'_>)).unwrap() } fn sol_iface() -> String { let mut cheats = Cheatcodes::new(); cheats.errors = Default::default(); // Skip errors to allow <0.8.4. let cheats = cheats.to_string().trim().replace('\n', "\n "); format!( "\ // Automatically generated from `foundry-cheatcodes` Vm definitions. Do not modify manually. // This interface is just for internal testing purposes. Use `forge-std` instead. // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity >=0.6.2 <0.9.0; pragma experimental ABIEncoderV2; interface Vm {{ {cheats} }} " ) } #[test] fn spec_up_to_date() { ensure_file_contents(Path::new(JSON_PATH), &json_cheatcodes()); } #[test] #[cfg(feature = "schema")] fn schema_up_to_date() { ensure_file_contents(Path::new(SCHEMA_PATH), &json_schema()); } #[test] fn iface_up_to_date() { ensure_file_contents(Path::new(IFACE_PATH), &sol_iface()); } /// Checks that the `file` has the specified `contents`. If that is not the /// case, updates the file and then fails the test. fn ensure_file_contents(file: &Path, contents: &str) { if let Ok(old_contents) = fs::read_to_string(file) && normalize_newlines(&old_contents) == normalize_newlines(contents) { // File is already up to date. return; } eprintln!("\n\x1b[31;1merror\x1b[0m: {} was not up-to-date, updating\n", file.display()); if std::env::var("CI").is_ok() { eprintln!(" NOTE: run `cargo cheats` locally and commit the updated files\n"); } if let Some(parent) = file.parent() { let _ = fs::create_dir_all(parent); } fs::write(file, contents).unwrap(); panic!("some file was not up to date and has been updated, simply re-run the tests"); } fn normalize_newlines(s: &str) -> String { s.replace("\r\n", "\n") } } ================================================ FILE: crates/cheatcodes/spec/src/vm.rs ================================================ // We don't document function parameters individually so we can't enable `missing_docs` for this // module. Instead, we emit custom diagnostics in `#[derive(Cheatcode)]`. #![allow(missing_docs)] use super::*; use crate::Vm::ForgeContext; use alloy_sol_types::sol; use foundry_macros::Cheatcode; sol! { // Cheatcodes are marked as view/pure/none using the following rules: // 0. A call's observable behaviour includes its return value, logs, reverts and state writes, // 1. If you can influence a later call's observable behaviour, you're neither `view` nor `pure` // (you are modifying some state be it the EVM, interpreter, filesystem, etc), // 2. Otherwise if you can be influenced by an earlier call, or if reading some state, you're `view`, // 3. Otherwise you're `pure`. /// Foundry cheatcodes interface. #[derive(Debug, Cheatcode)] // Keep this list small to avoid unnecessary bloat. #[sol(abi)] interface Vm { // ======== Types ======== /// Error thrown by cheatcodes. error CheatcodeError(string message); /// A modification applied to either `msg.sender` or `tx.origin`. Returned by `readCallers`. enum CallerMode { /// No caller modification is currently active. None, /// A one time broadcast triggered by a `vm.broadcast()` call is currently active. Broadcast, /// A recurrent broadcast triggered by a `vm.startBroadcast()` call is currently active. RecurrentBroadcast, /// A one time prank triggered by a `vm.prank()` call is currently active. Prank, /// A recurrent prank triggered by a `vm.startPrank()` call is currently active. RecurrentPrank, } /// The kind of account access that occurred. enum AccountAccessKind { /// The account was called. Call, /// The account was called via delegatecall. DelegateCall, /// The account was called via callcode. CallCode, /// The account was called via staticcall. StaticCall, /// The account was created. Create, /// The account was selfdestructed. SelfDestruct, /// Synthetic access indicating the current context has resumed after a previous sub-context (AccountAccess). Resume, /// The account's balance was read. Balance, /// The account's codesize was read. Extcodesize, /// The account's codehash was read. Extcodehash, /// The account's code was copied. Extcodecopy, } /// Forge execution contexts. enum ForgeContext { /// Test group execution context (test, coverage or snapshot). TestGroup, /// `forge test` execution context. Test, /// `forge coverage` execution context. Coverage, /// `forge snapshot` execution context. Snapshot, /// Script group execution context (dry run, broadcast or resume). ScriptGroup, /// `forge script` execution context. ScriptDryRun, /// `forge script --broadcast` execution context. ScriptBroadcast, /// `forge script --resume` execution context. ScriptResume, /// Unknown `forge` execution context. Unknown, } /// An Ethereum log. Returned by `getRecordedLogs`. struct Log { /// The topics of the log, including the signature, if any. bytes32[] topics; /// The raw data of the log. bytes data; /// The address of the log's emitter. address emitter; } /// Gas used. Returned by `lastCallGas`. struct Gas { /// The gas limit of the call. uint64 gasLimit; /// The total gas used. uint64 gasTotalUsed; /// DEPRECATED: The amount of gas used for memory expansion. Ref: uint64 gasMemoryUsed; /// The amount of gas refunded. int64 gasRefunded; /// The amount of gas remaining. uint64 gasRemaining; } /// An RPC URL and its alias. Returned by `rpcUrlStructs`. struct Rpc { /// The alias of the RPC URL. string key; /// The RPC URL. string url; } /// An RPC log object. Returned by `eth_getLogs`. struct EthGetLogs { /// The address of the log's emitter. address emitter; /// The topics of the log, including the signature, if any. bytes32[] topics; /// The raw data of the log. bytes data; /// The block hash. bytes32 blockHash; /// The block number. uint64 blockNumber; /// The transaction hash. bytes32 transactionHash; /// The transaction index in the block. uint64 transactionIndex; /// The log index. uint256 logIndex; /// Whether the log was removed. bool removed; } /// A single entry in a directory listing. Returned by `readDir`. struct DirEntry { /// The error message, if any. string errorMessage; /// The path of the entry. string path; /// The depth of the entry. uint64 depth; /// Whether the entry is a directory. bool isDir; /// Whether the entry is a symlink. bool isSymlink; } /// Metadata information about a file. /// /// This structure is returned from the `fsMetadata` function and represents known /// metadata about a file such as its permissions, size, modification /// times, etc. struct FsMetadata { /// True if this metadata is for a directory. bool isDir; /// True if this metadata is for a symlink. bool isSymlink; /// The size of the file, in bytes, this metadata is for. uint256 length; /// True if this metadata is for a readonly (unwritable) file. bool readOnly; /// The last modification time listed in this metadata. uint256 modified; /// The last access time of this metadata. uint256 accessed; /// The creation time listed in this metadata. uint256 created; } /// A wallet with a public and private key. struct Wallet { /// The wallet's address. address addr; /// The wallet's public key `X`. uint256 publicKeyX; /// The wallet's public key `Y`. uint256 publicKeyY; /// The wallet's private key. uint256 privateKey; } /// The result of a `tryFfi` call. struct FfiResult { /// The exit code of the call. int32 exitCode; /// The optionally hex-decoded `stdout` data. bytes stdout; /// The `stderr` data. bytes stderr; } /// Information on the chain and fork. struct ChainInfo { /// The fork identifier. Set to zero if no fork is active. uint256 forkId; /// The chain ID of the current fork. uint256 chainId; } /// Information about a blockchain. struct Chain { /// The chain name. string name; /// The chain's Chain ID. uint256 chainId; /// The chain's alias. (i.e. what gets specified in `foundry.toml`). string chainAlias; /// A default RPC endpoint for this chain. string rpcUrl; } /// The storage accessed during an `AccountAccess`. struct StorageAccess { /// The account whose storage was accessed. address account; /// The slot that was accessed. bytes32 slot; /// If the access was a write. bool isWrite; /// The previous value of the slot. bytes32 previousValue; /// The new value of the slot. bytes32 newValue; /// If the access was reverted. bool reverted; } /// An EIP-2930 access list item. struct AccessListItem { /// The address to be added in access list. address target; /// The storage keys to be added in access list. bytes32[] storageKeys; } /// The result of a `stopAndReturnStateDiff` call. struct AccountAccess { /// The chain and fork the access occurred. ChainInfo chainInfo; /// The kind of account access that determines what the account is. /// If kind is Call, DelegateCall, StaticCall or CallCode, then the account is the callee. /// If kind is Create, then the account is the newly created account. /// If kind is SelfDestruct, then the account is the selfdestruct recipient. /// If kind is a Resume, then account represents a account context that has resumed. AccountAccessKind kind; /// The account that was accessed. /// It's either the account created, callee or a selfdestruct recipient for CREATE, CALL or SELFDESTRUCT. address account; /// What accessed the account. address accessor; /// If the account was initialized or empty prior to the access. /// An account is considered initialized if it has code, a /// non-zero nonce, or a non-zero balance. bool initialized; /// The previous balance of the accessed account. uint256 oldBalance; /// The potential new balance of the accessed account. /// That is, all balance changes are recorded here, even if reverts occurred. uint256 newBalance; /// Code of the account deployed by CREATE. bytes deployedCode; /// Value passed along with the account access uint256 value; /// Input data provided to the CREATE or CALL bytes data; /// If this access reverted in either the current or parent context. bool reverted; /// An ordered list of storage accesses made during an account access operation. StorageAccess[] storageAccesses; /// Call depth traversed during the recording of state differences uint64 depth; /// The previous nonce of the accessed account. uint64 oldNonce; /// The new nonce of the accessed account. uint64 newNonce; } /// The result of the `stopDebugTraceRecording` call struct DebugStep { /// The stack before executing the step of the run. /// stack\[0\] represents the top of the stack. /// and only stack data relevant to the opcode execution is contained. uint256[] stack; /// The memory input data before executing the step of the run. /// only input data relevant to the opcode execution is contained. /// /// e.g. for MLOAD, it will have memory\[offset:offset+32\] copied here. /// the offset value can be get by the stack data. bytes memoryInput; /// The opcode that was accessed. uint8 opcode; /// The call depth of the step. uint64 depth; /// Whether the call end up with out of gas error. bool isOutOfGas; /// The contract address where the opcode is running address contractAddr; } /// The transaction type (`txType`) of the broadcast. enum BroadcastTxType { /// Represents a CALL broadcast tx. Call, /// Represents a CREATE broadcast tx. Create, /// Represents a CREATE2 broadcast tx. Create2 } /// Represents a transaction's broadcast details. struct BroadcastTxSummary { /// The hash of the transaction that was broadcasted bytes32 txHash; /// Represent the type of transaction among CALL, CREATE, CREATE2 BroadcastTxType txType; /// The address of the contract that was called or created. /// This is address of the contract that is created if the txType is CREATE or CREATE2. address contractAddress; /// The block number the transaction landed in. uint64 blockNumber; /// Status of the transaction, retrieved from the transaction receipt. bool success; } /// Holds a signed EIP-7702 authorization for an authority account to delegate to an implementation. struct SignedDelegation { /// The y-parity of the recovered secp256k1 signature (0 or 1). uint8 v; /// First 32 bytes of the signature. bytes32 r; /// Second 32 bytes of the signature. bytes32 s; /// The current nonce of the authority account at signing time. /// Used to ensure signature can't be replayed after account nonce changes. uint64 nonce; /// Address of the contract implementation that will be delegated to. /// Gets encoded into delegation code: 0xef0100 || implementation. address implementation; } /// Represents a "potential" revert reason from a single subsequent call when using `vm.assumeNoReverts`. /// Reverts that match will result in a FOUNDRY::ASSUME rejection, whereas unmatched reverts will be surfaced /// as normal. struct PotentialRevert { /// The allowed origin of the revert opcode; address(0) allows reverts from any address address reverter; /// When true, only matches on the beginning of the revert data, otherwise, matches on entire revert data bool partialMatch; /// The data to use to match encountered reverts bytes revertData; } // ======== EVM ======== /// Gets the address for a given private key. #[cheatcode(group = Evm, safety = Safe)] function addr(uint256 privateKey) external pure returns (address keyAddr); /// Dump a genesis JSON file's `allocs` to disk. #[cheatcode(group = Evm, safety = Unsafe)] function dumpState(string calldata pathToStateJson) external; /// Gets the nonce of an account. #[cheatcode(group = Evm, safety = Safe)] function getNonce(address account) external view returns (uint64 nonce); /// Get the nonce of a `Wallet`. #[cheatcode(group = Evm, safety = Safe)] function getNonce(Wallet calldata wallet) external view returns (uint64 nonce); /// Loads a storage slot from an address. #[cheatcode(group = Evm, safety = Safe)] function load(address target, bytes32 slot) external view returns (bytes32 data); /// Load a genesis JSON file's `allocs` into the in-memory EVM state. #[cheatcode(group = Evm, safety = Unsafe)] function loadAllocs(string calldata pathToAllocsJson) external; // -------- Record Debug Traces -------- /// Records the debug trace during the run. #[cheatcode(group = Evm, safety = Safe)] function startDebugTraceRecording() external; /// Stop debug trace recording and returns the recorded debug trace. #[cheatcode(group = Evm, safety = Safe)] function stopAndReturnDebugTraceRecording() external returns (DebugStep[] memory step); /// Clones a source account code, state, balance and nonce to a target account and updates in-memory EVM state. #[cheatcode(group = Evm, safety = Unsafe)] function cloneAccount(address source, address target) external; // -------- Record Storage -------- /// Records all storage reads and writes. Use `accesses` to get the recorded data. /// Subsequent calls to `record` will clear the previous data. #[cheatcode(group = Evm, safety = Safe)] function record() external; /// Stops recording storage reads and writes. #[cheatcode(group = Evm, safety = Safe)] function stopRecord() external; /// Gets all accessed reads and write slot from a `vm.record` session, for a given address. #[cheatcode(group = Evm, safety = Safe)] function accesses(address target) external view returns (bytes32[] memory readSlots, bytes32[] memory writeSlots); /// Record all account accesses as part of CREATE, CALL or SELFDESTRUCT opcodes in order, /// along with the context of the calls #[cheatcode(group = Evm, safety = Safe)] function startStateDiffRecording() external; /// Returns an ordered array of all account accesses from a `vm.startStateDiffRecording` session. #[cheatcode(group = Evm, safety = Safe)] function stopAndReturnStateDiff() external returns (AccountAccess[] memory accountAccesses); /// Returns state diffs from current `vm.startStateDiffRecording` session. #[cheatcode(group = Evm, safety = Safe)] function getStateDiff() external view returns (string memory diff); /// Returns state diffs from current `vm.startStateDiffRecording` session, in json format. #[cheatcode(group = Evm, safety = Safe)] function getStateDiffJson() external view returns (string memory diff); /// Returns an array of storage slots occupied by the specified variable. #[cheatcode(group = Evm, safety = Safe)] function getStorageSlots(address target, string calldata variableName) external view returns (uint256[] memory slots); /// Returns an array of `StorageAccess` from current `vm.stateStateDiffRecording` session #[cheatcode(group = Evm, safety = Safe)] function getStorageAccesses() external view returns (StorageAccess[] memory storageAccesses); // -------- Recording Map Writes -------- /// Starts recording all map SSTOREs for later retrieval. #[cheatcode(group = Evm, safety = Safe)] function startMappingRecording() external; /// Stops recording all map SSTOREs for later retrieval and clears the recorded data. #[cheatcode(group = Evm, safety = Safe)] function stopMappingRecording() external; /// Gets the number of elements in the mapping at the given slot, for a given address. #[cheatcode(group = Evm, safety = Safe)] function getMappingLength(address target, bytes32 mappingSlot) external view returns (uint256 length); /// Gets the elements at index idx of the mapping at the given slot, for a given address. The /// index must be less than the length of the mapping (i.e. the number of keys in the mapping). #[cheatcode(group = Evm, safety = Safe)] function getMappingSlotAt(address target, bytes32 mappingSlot, uint256 idx) external view returns (bytes32 value); /// Gets the map key and parent of a mapping at a given slot, for a given address. #[cheatcode(group = Evm, safety = Safe)] function getMappingKeyAndParentOf(address target, bytes32 elementSlot) external view returns (bool found, bytes32 key, bytes32 parent); // -------- Block and Transaction Properties -------- /// Gets the current `block.chainid` of the currently selected environment. /// You should use this instead of `block.chainid` if you use `vm.selectFork` or `vm.createSelectFork`, as `block.chainid` could be assumed /// to be constant across a transaction, and as a result will get optimized out by the compiler. /// See https://github.com/foundry-rs/foundry/issues/6180 #[cheatcode(group = Evm, safety = Safe)] function getChainId() external view returns (uint256 blockChainId); /// Sets `block.chainid`. #[cheatcode(group = Evm, safety = Unsafe)] function chainId(uint256 newChainId) external; /// Sets `block.coinbase`. #[cheatcode(group = Evm, safety = Unsafe)] function coinbase(address newCoinbase) external; /// Sets `block.difficulty`. /// Not available on EVM versions from Paris onwards. Use `prevrandao` instead. /// Reverts if used on unsupported EVM versions. #[cheatcode(group = Evm, safety = Unsafe)] function difficulty(uint256 newDifficulty) external; /// Sets `block.basefee`. #[cheatcode(group = Evm, safety = Unsafe)] function fee(uint256 newBasefee) external; /// Sets `block.prevrandao`. /// Not available on EVM versions before Paris. Use `difficulty` instead. /// If used on unsupported EVM versions it will revert. #[cheatcode(group = Evm, safety = Unsafe)] function prevrandao(bytes32 newPrevrandao) external; /// Sets `block.prevrandao`. /// Not available on EVM versions before Paris. Use `difficulty` instead. /// If used on unsupported EVM versions it will revert. #[cheatcode(group = Evm, safety = Unsafe)] function prevrandao(uint256 newPrevrandao) external; /// Sets the blobhashes in the transaction. /// Not available on EVM versions before Cancun. /// If used on unsupported EVM versions it will revert. #[cheatcode(group = Evm, safety = Unsafe)] function blobhashes(bytes32[] calldata hashes) external; /// Gets the blockhashes from the current transaction. /// Not available on EVM versions before Cancun. /// If used on unsupported EVM versions it will revert. #[cheatcode(group = Evm, safety = Unsafe)] function getBlobhashes() external view returns (bytes32[] memory hashes); /// Sets `block.height`. #[cheatcode(group = Evm, safety = Unsafe)] function roll(uint256 newHeight) external; /// Gets the current `block.number`. /// You should use this instead of `block.number` if you use `vm.roll`, as `block.number` is assumed to be constant across a transaction, /// and as a result will get optimized out by the compiler. /// See https://github.com/foundry-rs/foundry/issues/6180 #[cheatcode(group = Evm, safety = Safe)] function getBlockNumber() external view returns (uint256 height); /// Sets `tx.gasprice`. #[cheatcode(group = Evm, safety = Unsafe)] function txGasPrice(uint256 newGasPrice) external; /// Sets `block.timestamp`. #[cheatcode(group = Evm, safety = Unsafe)] function warp(uint256 newTimestamp) external; /// Gets the current `block.timestamp`. /// You should use this instead of `block.timestamp` if you use `vm.warp`, as `block.timestamp` is assumed to be constant across a transaction, /// and as a result will get optimized out by the compiler. /// See https://github.com/foundry-rs/foundry/issues/6180 #[cheatcode(group = Evm, safety = Safe)] function getBlockTimestamp() external view returns (uint256 timestamp); /// Gets the RLP encoded block header for a given block number. /// Returns the block header in the same format as `cast block --raw`. #[cheatcode(group = Evm, safety = Safe)] function getRawBlockHeader(uint256 blockNumber) external view returns (bytes memory rlpHeader); /// Sets `block.blobbasefee` #[cheatcode(group = Evm, safety = Unsafe)] function blobBaseFee(uint256 newBlobBaseFee) external; /// Gets the current `block.blobbasefee`. /// You should use this instead of `block.blobbasefee` if you use `vm.blobBaseFee`, as `block.blobbasefee` is assumed to be constant across a transaction, /// and as a result will get optimized out by the compiler. /// See https://github.com/foundry-rs/foundry/issues/6180 #[cheatcode(group = Evm, safety = Safe)] function getBlobBaseFee() external view returns (uint256 blobBaseFee); /// Set blockhash for the current block. /// It only sets the blockhash for blocks where `block.number - 256 <= number < block.number`. #[cheatcode(group = Evm, safety = Unsafe)] function setBlockhash(uint256 blockNumber, bytes32 blockHash) external; /// Executes an RLP-encoded signed transaction with full EVM semantics (like `--isolate` mode). /// The transaction is decoded from EIP-2718 format (type byte prefix + RLP payload) or legacy RLP. /// Returns the execution output bytes. /// /// This cheatcode is not allowed in `forge script` contexts. #[cheatcode(group = Evm, safety = Unsafe)] function executeTransaction(bytes calldata rawTx) external returns (bytes memory); // -------- Account State -------- /// Sets an address' balance. #[cheatcode(group = Evm, safety = Unsafe)] function deal(address account, uint256 newBalance) external; /// Sets an address' code. #[cheatcode(group = Evm, safety = Unsafe)] function etch(address target, bytes calldata newRuntimeBytecode) external; /// Resets the nonce of an account to 0 for EOAs and 1 for contract accounts. #[cheatcode(group = Evm, safety = Unsafe)] function resetNonce(address account) external; /// Sets the nonce of an account. Must be higher than the current nonce of the account. #[cheatcode(group = Evm, safety = Unsafe)] function setNonce(address account, uint64 newNonce) external; /// Sets the nonce of an account to an arbitrary value. #[cheatcode(group = Evm, safety = Unsafe)] function setNonceUnsafe(address account, uint64 newNonce) external; /// Stores a value to an address' storage slot. #[cheatcode(group = Evm, safety = Unsafe)] function store(address target, bytes32 slot, bytes32 value) external; /// Marks the slots of an account and the account address as cold. #[cheatcode(group = Evm, safety = Unsafe)] function cool(address target) external; /// Utility cheatcode to set an EIP-2930 access list for all subsequent transactions. #[cheatcode(group = Evm, safety = Unsafe)] function accessList(AccessListItem[] calldata access) external; /// Utility cheatcode to remove any EIP-2930 access list set by `accessList` cheatcode. #[cheatcode(group = Evm, safety = Unsafe)] function noAccessList() external; /// Utility cheatcode to mark specific storage slot as warm, simulating a prior read. #[cheatcode(group = Evm, safety = Unsafe)] function warmSlot(address target, bytes32 slot) external; /// Utility cheatcode to mark specific storage slot as cold, simulating no prior read. #[cheatcode(group = Evm, safety = Unsafe)] function coolSlot(address target, bytes32 slot) external; /// Returns the test or script execution evm version. /// /// **Note:** The execution evm version is not the same as the compilation one. #[cheatcode(group = Evm, safety = Safe)] function getEvmVersion() external pure returns (string memory evm); /// Set the exact test or script execution evm version, e.g. `berlin`, `cancun`. /// /// **Note:** The execution evm version is not the same as the compilation one. #[cheatcode(group = Evm, safety = Safe)] function setEvmVersion(string calldata evm) external; // -------- Call Manipulation -------- // --- Mocks --- /// Clears all mocked calls. #[cheatcode(group = Evm, safety = Unsafe)] function clearMockedCalls() external; /// Mocks a call to an address, returning specified data. /// Calldata can either be strict or a partial match, e.g. if you only /// pass a Solidity selector to the expected calldata, then the entire Solidity /// function will be mocked. #[cheatcode(group = Evm, safety = Unsafe)] function mockCall(address callee, bytes calldata data, bytes calldata returnData) external; /// Mocks a call to an address with a specific `msg.value`, returning specified data. /// Calldata match takes precedence over `msg.value` in case of ambiguity. #[cheatcode(group = Evm, safety = Unsafe)] function mockCall(address callee, uint256 msgValue, bytes calldata data, bytes calldata returnData) external; /// Mocks a call to an address, returning specified data. /// Calldata can either be strict or a partial match, e.g. if you only /// pass a Solidity selector to the expected calldata, then the entire Solidity /// function will be mocked. /// /// Overload to pass the function selector directly `token.approve.selector` instead of `abi.encodeWithSelector(token.approve.selector)`. #[cheatcode(group = Evm, safety = Unsafe)] function mockCall(address callee, bytes4 data, bytes calldata returnData) external; /// Mocks a call to an address with a specific `msg.value`, returning specified data. /// Calldata match takes precedence over `msg.value` in case of ambiguity. /// /// Overload to pass the function selector directly `token.approve.selector` instead of `abi.encodeWithSelector(token.approve.selector)`. #[cheatcode(group = Evm, safety = Unsafe)] function mockCall(address callee, uint256 msgValue, bytes4 data, bytes calldata returnData) external; /// Mocks multiple calls to an address, returning specified data for each call. #[cheatcode(group = Evm, safety = Unsafe)] function mockCalls(address callee, bytes calldata data, bytes[] calldata returnData) external; /// Mocks multiple calls to an address with a specific `msg.value`, returning specified data for each call. #[cheatcode(group = Evm, safety = Unsafe)] function mockCalls(address callee, uint256 msgValue, bytes calldata data, bytes[] calldata returnData) external; /// Reverts a call to an address with specified revert data. #[cheatcode(group = Evm, safety = Unsafe)] function mockCallRevert(address callee, bytes calldata data, bytes calldata revertData) external; /// Reverts a call to an address with a specific `msg.value`, with specified revert data. #[cheatcode(group = Evm, safety = Unsafe)] function mockCallRevert(address callee, uint256 msgValue, bytes calldata data, bytes calldata revertData) external; /// Reverts a call to an address with specified revert data. /// /// Overload to pass the function selector directly `token.approve.selector` instead of `abi.encodeWithSelector(token.approve.selector)`. #[cheatcode(group = Evm, safety = Unsafe)] function mockCallRevert(address callee, bytes4 data, bytes calldata revertData) external; /// Reverts a call to an address with a specific `msg.value`, with specified revert data. /// /// Overload to pass the function selector directly `token.approve.selector` instead of `abi.encodeWithSelector(token.approve.selector)`. #[cheatcode(group = Evm, safety = Unsafe)] function mockCallRevert(address callee, uint256 msgValue, bytes4 data, bytes calldata revertData) external; /// Whenever a call is made to `callee` with calldata `data`, this cheatcode instead calls /// `target` with the same calldata. This functionality is similar to a delegate call made to /// `target` contract from `callee`. /// Can be used to substitute a call to a function with another implementation that captures /// the primary logic of the original function but is easier to reason about. /// If calldata is not a strict match then partial match by selector is attempted. #[cheatcode(group = Evm, safety = Unsafe)] function mockFunction(address callee, address target, bytes calldata data) external; // --- Impersonation (pranks) --- /// Sets the *next* call's `msg.sender` to be the input address. #[cheatcode(group = Evm, safety = Unsafe)] function prank(address msgSender) external; /// Sets all subsequent calls' `msg.sender` to be the input address until `stopPrank` is called. #[cheatcode(group = Evm, safety = Unsafe)] function startPrank(address msgSender) external; /// Sets the *next* call's `msg.sender` to be the input address, and the `tx.origin` to be the second input. #[cheatcode(group = Evm, safety = Unsafe)] function prank(address msgSender, address txOrigin) external; /// Sets all subsequent calls' `msg.sender` to be the input address until `stopPrank` is called, and the `tx.origin` to be the second input. #[cheatcode(group = Evm, safety = Unsafe)] function startPrank(address msgSender, address txOrigin) external; /// Sets the *next* delegate call's `msg.sender` to be the input address. #[cheatcode(group = Evm, safety = Unsafe)] function prank(address msgSender, bool delegateCall) external; /// Sets all subsequent delegate calls' `msg.sender` to be the input address until `stopPrank` is called. #[cheatcode(group = Evm, safety = Unsafe)] function startPrank(address msgSender, bool delegateCall) external; /// Sets the *next* delegate call's `msg.sender` to be the input address, and the `tx.origin` to be the second input. #[cheatcode(group = Evm, safety = Unsafe)] function prank(address msgSender, address txOrigin, bool delegateCall) external; /// Sets all subsequent delegate calls' `msg.sender` to be the input address until `stopPrank` is called, and the `tx.origin` to be the second input. #[cheatcode(group = Evm, safety = Unsafe)] function startPrank(address msgSender, address txOrigin, bool delegateCall) external; /// Resets subsequent calls' `msg.sender` to be `address(this)`. #[cheatcode(group = Evm, safety = Unsafe)] function stopPrank() external; /// Reads the current `msg.sender` and `tx.origin` from state and reports if there is any active caller modification. #[cheatcode(group = Evm, safety = Unsafe)] function readCallers() external view returns (CallerMode callerMode, address msgSender, address txOrigin); // ----- Arbitrary Snapshots ----- /// Snapshot capture an arbitrary numerical value by name. /// The group name is derived from the contract name. #[cheatcode(group = Evm, safety = Unsafe)] function snapshotValue(string calldata name, uint256 value) external; /// Snapshot capture an arbitrary numerical value by name in a group. #[cheatcode(group = Evm, safety = Unsafe)] function snapshotValue(string calldata group, string calldata name, uint256 value) external; // -------- Gas Snapshots -------- /// Snapshot capture the gas usage of the last call by name from the callee perspective. #[cheatcode(group = Evm, safety = Unsafe)] function snapshotGasLastCall(string calldata name) external returns (uint256 gasUsed); /// Snapshot capture the gas usage of the last call by name in a group from the callee perspective. #[cheatcode(group = Evm, safety = Unsafe)] function snapshotGasLastCall(string calldata group, string calldata name) external returns (uint256 gasUsed); /// Start a snapshot capture of the current gas usage by name. /// The group name is derived from the contract name. #[cheatcode(group = Evm, safety = Unsafe)] function startSnapshotGas(string calldata name) external; /// Start a snapshot capture of the current gas usage by name in a group. #[cheatcode(group = Evm, safety = Unsafe)] function startSnapshotGas(string calldata group, string calldata name) external; /// Stop the snapshot capture of the current gas by latest snapshot name, capturing the gas used since the start. #[cheatcode(group = Evm, safety = Unsafe)] function stopSnapshotGas() external returns (uint256 gasUsed); /// Stop the snapshot capture of the current gas usage by name, capturing the gas used since the start. /// The group name is derived from the contract name. #[cheatcode(group = Evm, safety = Unsafe)] function stopSnapshotGas(string calldata name) external returns (uint256 gasUsed); /// Stop the snapshot capture of the current gas usage by name in a group, capturing the gas used since the start. #[cheatcode(group = Evm, safety = Unsafe)] function stopSnapshotGas(string calldata group, string calldata name) external returns (uint256 gasUsed); // -------- State Snapshots -------- /// `snapshot` is being deprecated in favor of `snapshotState`. It will be removed in future versions. #[cheatcode(group = Evm, safety = Unsafe, status = Deprecated(Some("replaced by `snapshotState`")))] function snapshot() external returns (uint256 snapshotId); /// Snapshot the current state of the evm. /// Returns the ID of the snapshot that was created. /// To revert a snapshot use `revertToState`. #[cheatcode(group = Evm, safety = Unsafe)] function snapshotState() external returns (uint256 snapshotId); /// `revertTo` is being deprecated in favor of `revertToState`. It will be removed in future versions. #[cheatcode(group = Evm, safety = Unsafe, status = Deprecated(Some("replaced by `revertToState`")))] function revertTo(uint256 snapshotId) external returns (bool success); /// Revert the state of the EVM to a previous snapshot /// Takes the snapshot ID to revert to. /// /// Returns `true` if the snapshot was successfully reverted. /// Returns `false` if the snapshot does not exist. /// /// **Note:** This does not automatically delete the snapshot. To delete the snapshot use `deleteStateSnapshot`. #[cheatcode(group = Evm, safety = Unsafe)] function revertToState(uint256 snapshotId) external returns (bool success); /// `revertToAndDelete` is being deprecated in favor of `revertToStateAndDelete`. It will be removed in future versions. #[cheatcode(group = Evm, safety = Unsafe, status = Deprecated(Some("replaced by `revertToStateAndDelete`")))] function revertToAndDelete(uint256 snapshotId) external returns (bool success); /// Revert the state of the EVM to a previous snapshot and automatically deletes the snapshots /// Takes the snapshot ID to revert to. /// /// Returns `true` if the snapshot was successfully reverted and deleted. /// Returns `false` if the snapshot does not exist. #[cheatcode(group = Evm, safety = Unsafe)] function revertToStateAndDelete(uint256 snapshotId) external returns (bool success); /// `deleteSnapshot` is being deprecated in favor of `deleteStateSnapshot`. It will be removed in future versions. #[cheatcode(group = Evm, safety = Unsafe, status = Deprecated(Some("replaced by `deleteStateSnapshot`")))] function deleteSnapshot(uint256 snapshotId) external returns (bool success); /// Removes the snapshot with the given ID created by `snapshot`. /// Takes the snapshot ID to delete. /// /// Returns `true` if the snapshot was successfully deleted. /// Returns `false` if the snapshot does not exist. #[cheatcode(group = Evm, safety = Unsafe)] function deleteStateSnapshot(uint256 snapshotId) external returns (bool success); /// `deleteSnapshots` is being deprecated in favor of `deleteStateSnapshots`. It will be removed in future versions. #[cheatcode(group = Evm, safety = Unsafe, status = Deprecated(Some("replaced by `deleteStateSnapshots`")))] function deleteSnapshots() external; /// Removes _all_ snapshots previously created by `snapshot`. #[cheatcode(group = Evm, safety = Unsafe)] function deleteStateSnapshots() external; // -------- Forking -------- // --- Creation and Selection --- /// Returns the identifier of the currently active fork. Reverts if no fork is currently active. #[cheatcode(group = Evm, safety = Unsafe)] function activeFork() external view returns (uint256 forkId); /// Creates a new fork with the given endpoint and the _latest_ block and returns the identifier of the fork. #[cheatcode(group = Evm, safety = Unsafe)] function createFork(string calldata urlOrAlias) external returns (uint256 forkId); /// Creates a new fork with the given endpoint and block and returns the identifier of the fork. #[cheatcode(group = Evm, safety = Unsafe)] function createFork(string calldata urlOrAlias, uint256 blockNumber) external returns (uint256 forkId); /// Creates a new fork with the given endpoint and at the block the given transaction was mined in, /// replays all transaction mined in the block before the transaction, and returns the identifier of the fork. #[cheatcode(group = Evm, safety = Unsafe)] function createFork(string calldata urlOrAlias, bytes32 txHash) external returns (uint256 forkId); /// Creates and also selects a new fork with the given endpoint and the latest block and returns the identifier of the fork. #[cheatcode(group = Evm, safety = Unsafe)] function createSelectFork(string calldata urlOrAlias) external returns (uint256 forkId); /// Creates and also selects a new fork with the given endpoint and block and returns the identifier of the fork. #[cheatcode(group = Evm, safety = Unsafe)] function createSelectFork(string calldata urlOrAlias, uint256 blockNumber) external returns (uint256 forkId); /// Creates and also selects new fork with the given endpoint and at the block the given transaction was mined in, /// replays all transaction mined in the block before the transaction, returns the identifier of the fork. #[cheatcode(group = Evm, safety = Unsafe)] function createSelectFork(string calldata urlOrAlias, bytes32 txHash) external returns (uint256 forkId); /// Updates the currently active fork to given block number /// This is similar to `roll` but for the currently active fork. #[cheatcode(group = Evm, safety = Unsafe)] function rollFork(uint256 blockNumber) external; /// Updates the currently active fork to given transaction. This will `rollFork` with the number /// of the block the transaction was mined in and replays all transaction mined before it in the block. #[cheatcode(group = Evm, safety = Unsafe)] function rollFork(bytes32 txHash) external; /// Updates the given fork to given block number. #[cheatcode(group = Evm, safety = Unsafe)] function rollFork(uint256 forkId, uint256 blockNumber) external; /// Updates the given fork to block number of the given transaction and replays all transaction mined before it in the block. #[cheatcode(group = Evm, safety = Unsafe)] function rollFork(uint256 forkId, bytes32 txHash) external; /// Takes a fork identifier created by `createFork` and sets the corresponding forked state as active. #[cheatcode(group = Evm, safety = Unsafe)] function selectFork(uint256 forkId) external; /// Fetches the given transaction from the active fork and executes it on the current state. #[cheatcode(group = Evm, safety = Unsafe)] function transact(bytes32 txHash) external; /// Fetches the given transaction from the given fork and executes it on the current state. #[cheatcode(group = Evm, safety = Unsafe)] function transact(uint256 forkId, bytes32 txHash) external; /// Performs an Ethereum JSON-RPC request to the current fork URL. #[cheatcode(group = Evm, safety = Safe)] function rpc(string calldata method, string calldata params) external returns (bytes memory data); /// Performs an Ethereum JSON-RPC request to the given endpoint. #[cheatcode(group = Evm, safety = Safe)] function rpc(string calldata urlOrAlias, string calldata method, string calldata params) external returns (bytes memory data); /// Gets all the logs according to specified filter. #[cheatcode(group = Evm, safety = Safe)] function eth_getLogs(uint256 fromBlock, uint256 toBlock, address target, bytes32[] calldata topics) external view returns (EthGetLogs[] memory logs); // --- Behavior --- /// In forking mode, explicitly grant the given address cheatcode access. #[cheatcode(group = Evm, safety = Unsafe)] function allowCheatcodes(address account) external; /// Marks that the account(s) should use persistent storage across fork swaps in a multifork setup /// Meaning, changes made to the state of this account will be kept when switching forks. #[cheatcode(group = Evm, safety = Unsafe)] function makePersistent(address account) external; /// See `makePersistent(address)`. #[cheatcode(group = Evm, safety = Unsafe)] function makePersistent(address account0, address account1) external; /// See `makePersistent(address)`. #[cheatcode(group = Evm, safety = Unsafe)] function makePersistent(address account0, address account1, address account2) external; /// See `makePersistent(address)`. #[cheatcode(group = Evm, safety = Unsafe)] function makePersistent(address[] calldata accounts) external; /// Revokes persistent status from the address, previously added via `makePersistent`. #[cheatcode(group = Evm, safety = Unsafe)] function revokePersistent(address account) external; /// See `revokePersistent(address)`. #[cheatcode(group = Evm, safety = Unsafe)] function revokePersistent(address[] calldata accounts) external; /// Returns true if the account is marked as persistent. #[cheatcode(group = Evm, safety = Unsafe)] function isPersistent(address account) external view returns (bool persistent); // -------- Record Logs -------- /// Record all the transaction logs. #[cheatcode(group = Evm, safety = Safe)] function recordLogs() external; /// Gets all the recorded logs. #[cheatcode(group = Evm, safety = Safe)] function getRecordedLogs() external view returns (Log[] memory logs); /// Gets all the recorded logs, in JSON format. #[cheatcode(group = Evm, safety = Safe)] function getRecordedLogsJson() external view returns (string memory logsJson); // -------- Gas Metering -------- // It's recommend to use the `noGasMetering` modifier included with forge-std, instead of // using these functions directly. /// Pauses gas metering (i.e. gas usage is not counted). Noop if already paused. #[cheatcode(group = Evm, safety = Safe)] function pauseGasMetering() external; /// Resumes gas metering (i.e. gas usage is counted again). Noop if already on. #[cheatcode(group = Evm, safety = Safe)] function resumeGasMetering() external; /// Reset gas metering (i.e. gas usage is set to gas limit). #[cheatcode(group = Evm, safety = Safe)] function resetGasMetering() external; // -------- Gas Measurement -------- /// Gets the gas used in the last call from the callee perspective. #[cheatcode(group = Evm, safety = Safe)] function lastCallGas() external view returns (Gas memory gas); // ======== Test Assertions and Utilities ======== /// If the condition is false, discard this run's fuzz inputs and generate new ones. #[cheatcode(group = Testing, safety = Safe)] function assume(bool condition) external pure; /// Discard this run's fuzz inputs and generate new ones if next call reverted. #[cheatcode(group = Testing, safety = Safe)] function assumeNoRevert() external pure; /// Discard this run's fuzz inputs and generate new ones if next call reverts with the potential revert parameters. #[cheatcode(group = Testing, safety = Safe)] function assumeNoRevert(PotentialRevert calldata potentialRevert) external pure; /// Discard this run's fuzz inputs and generate new ones if next call reverts with the any of the potential revert parameters. #[cheatcode(group = Testing, safety = Safe)] function assumeNoRevert(PotentialRevert[] calldata potentialReverts) external pure; /// Writes a breakpoint to jump to in the debugger. #[cheatcode(group = Testing, safety = Safe)] function breakpoint(string calldata char) external pure; /// Writes a conditional breakpoint to jump to in the debugger. #[cheatcode(group = Testing, safety = Safe)] function breakpoint(string calldata char, bool value) external pure; /// Returns the Foundry version. /// Format: -+.. /// Sample output: 0.3.0-nightly+3cb96bde9b.1737036656.debug /// Note: Build timestamps may vary slightly across platforms due to separate CI jobs. /// For reliable version comparisons, use UNIX format (e.g., >= 1700000000) /// to compare timestamps while ignoring minor time differences. #[cheatcode(group = Testing, safety = Safe)] function getFoundryVersion() external view returns (string memory version); /// Returns the RPC url for the given alias. #[cheatcode(group = Testing, safety = Safe)] function rpcUrl(string calldata rpcAlias) external view returns (string memory json); /// Returns all rpc urls and their aliases `[alias, url][]`. #[cheatcode(group = Testing, safety = Safe)] function rpcUrls() external view returns (string[2][] memory urls); /// Returns all rpc urls and their aliases as structs. #[cheatcode(group = Testing, safety = Safe)] function rpcUrlStructs() external view returns (Rpc[] memory urls); /// Returns a Chain struct for specific alias #[cheatcode(group = Testing, safety = Safe)] function getChain(string calldata chainAlias) external view returns (Chain memory chain); /// Returns a Chain struct for specific chainId #[cheatcode(group = Testing, safety = Safe)] function getChain(uint256 chainId) external view returns (Chain memory chain); /// Suspends execution of the main thread for `duration` milliseconds. #[cheatcode(group = Testing, safety = Safe)] function sleep(uint256 duration) external; /// Expects a call to an address with the specified calldata. /// Calldata can either be a strict or a partial match. #[cheatcode(group = Testing, safety = Unsafe)] function expectCall(address callee, bytes calldata data) external; /// Expects given number of calls to an address with the specified calldata. #[cheatcode(group = Testing, safety = Unsafe)] function expectCall(address callee, bytes calldata data, uint64 count) external; /// Expects a call to an address with the specified `msg.value` and calldata. #[cheatcode(group = Testing, safety = Unsafe)] function expectCall(address callee, uint256 msgValue, bytes calldata data) external; /// Expects given number of calls to an address with the specified `msg.value` and calldata. #[cheatcode(group = Testing, safety = Unsafe)] function expectCall(address callee, uint256 msgValue, bytes calldata data, uint64 count) external; /// Expect a call to an address with the specified `msg.value`, gas, and calldata. #[cheatcode(group = Testing, safety = Unsafe)] function expectCall(address callee, uint256 msgValue, uint64 gas, bytes calldata data) external; /// Expects given number of calls to an address with the specified `msg.value`, gas, and calldata. #[cheatcode(group = Testing, safety = Unsafe)] function expectCall(address callee, uint256 msgValue, uint64 gas, bytes calldata data, uint64 count) external; /// Expect a call to an address with the specified `msg.value` and calldata, and a *minimum* amount of gas. #[cheatcode(group = Testing, safety = Unsafe)] function expectCallMinGas(address callee, uint256 msgValue, uint64 minGas, bytes calldata data) external; /// Expect given number of calls to an address with the specified `msg.value` and calldata, and a *minimum* amount of gas. #[cheatcode(group = Testing, safety = Unsafe)] function expectCallMinGas(address callee, uint256 msgValue, uint64 minGas, bytes calldata data, uint64 count) external; /// Prepare an expected log with (bool checkTopic1, bool checkTopic2, bool checkTopic3, bool checkData.). /// Call this function, then emit an event, then call a function. Internally after the call, we check if /// logs were emitted in the expected order with the expected topics and data (as specified by the booleans). #[cheatcode(group = Testing, safety = Unsafe)] function expectEmit(bool checkTopic1, bool checkTopic2, bool checkTopic3, bool checkData) external; /// Same as the previous method, but also checks supplied address against emitting contract. #[cheatcode(group = Testing, safety = Unsafe)] function expectEmit(bool checkTopic1, bool checkTopic2, bool checkTopic3, bool checkData, address emitter) external; /// Prepare an expected log with all topic and data checks enabled. /// Call this function, then emit an event, then call a function. Internally after the call, we check if /// logs were emitted in the expected order with the expected topics and data. #[cheatcode(group = Testing, safety = Unsafe)] function expectEmit() external; /// Same as the previous method, but also checks supplied address against emitting contract. #[cheatcode(group = Testing, safety = Unsafe)] function expectEmit(address emitter) external; /// Expect a given number of logs with the provided topics. #[cheatcode(group = Testing, safety = Unsafe)] function expectEmit(bool checkTopic1, bool checkTopic2, bool checkTopic3, bool checkData, uint64 count) external; /// Expect a given number of logs from a specific emitter with the provided topics. #[cheatcode(group = Testing, safety = Unsafe)] function expectEmit(bool checkTopic1, bool checkTopic2, bool checkTopic3, bool checkData, address emitter, uint64 count) external; /// Expect a given number of logs with all topic and data checks enabled. #[cheatcode(group = Testing, safety = Unsafe)] function expectEmit(uint64 count) external; /// Expect a given number of logs from a specific emitter with all topic and data checks enabled. #[cheatcode(group = Testing, safety = Unsafe)] function expectEmit(address emitter, uint64 count) external; /// Prepare an expected anonymous log with (bool checkTopic1, bool checkTopic2, bool checkTopic3, bool checkData.). /// Call this function, then emit an anonymous event, then call a function. Internally after the call, we check if /// logs were emitted in the expected order with the expected topics and data (as specified by the booleans). #[cheatcode(group = Testing, safety = Unsafe)] function expectEmitAnonymous(bool checkTopic0, bool checkTopic1, bool checkTopic2, bool checkTopic3, bool checkData) external; /// Same as the previous method, but also checks supplied address against emitting contract. #[cheatcode(group = Testing, safety = Unsafe)] function expectEmitAnonymous(bool checkTopic0, bool checkTopic1, bool checkTopic2, bool checkTopic3, bool checkData, address emitter) external; /// Prepare an expected anonymous log with all topic and data checks enabled. /// Call this function, then emit an anonymous event, then call a function. Internally after the call, we check if /// logs were emitted in the expected order with the expected topics and data. #[cheatcode(group = Testing, safety = Unsafe)] function expectEmitAnonymous() external; /// Same as the previous method, but also checks supplied address against emitting contract. #[cheatcode(group = Testing, safety = Unsafe)] function expectEmitAnonymous(address emitter) external; /// Expects the deployment of the specified bytecode by the specified address using the CREATE opcode #[cheatcode(group = Testing, safety = Unsafe)] function expectCreate(bytes calldata bytecode, address deployer) external; /// Expects the deployment of the specified bytecode by the specified address using the CREATE2 opcode #[cheatcode(group = Testing, safety = Unsafe)] function expectCreate2(bytes calldata bytecode, address deployer) external; /// Expects an error on next call with any revert data. #[cheatcode(group = Testing, safety = Unsafe)] function expectRevert() external; /// Expects an error on next call that exactly matches the revert data. #[cheatcode(group = Testing, safety = Unsafe)] function expectRevert(bytes4 revertData) external; /// Expects an error on next call that exactly matches the revert data. #[cheatcode(group = Testing, safety = Unsafe)] function expectRevert(bytes calldata revertData) external; /// Expects an error with any revert data on next call to reverter address. #[cheatcode(group = Testing, safety = Unsafe)] function expectRevert(address reverter) external; /// Expects an error from reverter address on next call, with any revert data. #[cheatcode(group = Testing, safety = Unsafe)] function expectRevert(bytes4 revertData, address reverter) external; /// Expects an error from reverter address on next call, that exactly matches the revert data. #[cheatcode(group = Testing, safety = Unsafe)] function expectRevert(bytes calldata revertData, address reverter) external; /// Expects a `count` number of reverts from the upcoming calls with any revert data or reverter. #[cheatcode(group = Testing, safety = Unsafe)] function expectRevert(uint64 count) external; /// Expects a `count` number of reverts from the upcoming calls that match the revert data. #[cheatcode(group = Testing, safety = Unsafe)] function expectRevert(bytes4 revertData, uint64 count) external; /// Expects a `count` number of reverts from the upcoming calls that exactly match the revert data. #[cheatcode(group = Testing, safety = Unsafe)] function expectRevert(bytes calldata revertData, uint64 count) external; /// Expects a `count` number of reverts from the upcoming calls from the reverter address. #[cheatcode(group = Testing, safety = Unsafe)] function expectRevert(address reverter, uint64 count) external; /// Expects a `count` number of reverts from the upcoming calls from the reverter address that match the revert data. #[cheatcode(group = Testing, safety = Unsafe)] function expectRevert(bytes4 revertData, address reverter, uint64 count) external; /// Expects a `count` number of reverts from the upcoming calls from the reverter address that exactly match the revert data. #[cheatcode(group = Testing, safety = Unsafe)] function expectRevert(bytes calldata revertData, address reverter, uint64 count) external; /// Expects an error on next call that starts with the revert data. #[cheatcode(group = Testing, safety = Unsafe)] function expectPartialRevert(bytes4 revertData) external; /// Expects an error on next call to reverter address, that starts with the revert data. #[cheatcode(group = Testing, safety = Unsafe)] function expectPartialRevert(bytes4 revertData, address reverter) external; /// Expects an error on next cheatcode call with any revert data. #[cheatcode(group = Testing, safety = Unsafe, status = Internal)] function _expectCheatcodeRevert() external; /// Expects an error on next cheatcode call that starts with the revert data. #[cheatcode(group = Testing, safety = Unsafe, status = Internal)] function _expectCheatcodeRevert(bytes4 revertData) external; /// Expects an error on next cheatcode call that exactly matches the revert data. #[cheatcode(group = Testing, safety = Unsafe, status = Internal)] function _expectCheatcodeRevert(bytes calldata revertData) external; /// Only allows memory writes to offsets [0x00, 0x60) ∪ [min, max) in the current subcontext. If any other /// memory is written to, the test will fail. Can be called multiple times to add more ranges to the set. #[cheatcode(group = Testing, safety = Unsafe)] function expectSafeMemory(uint64 min, uint64 max) external; /// Stops all safe memory expectation in the current subcontext. #[cheatcode(group = Testing, safety = Unsafe)] function stopExpectSafeMemory() external; /// Only allows memory writes to offsets [0x00, 0x60) ∪ [min, max) in the next created subcontext. /// If any other memory is written to, the test will fail. Can be called multiple times to add more ranges /// to the set. #[cheatcode(group = Testing, safety = Unsafe)] function expectSafeMemoryCall(uint64 min, uint64 max) external; /// Marks a test as skipped. Must be called at the top level of a test. #[cheatcode(group = Testing, safety = Unsafe)] function skip(bool skipTest) external; /// Marks a test as skipped with a reason. Must be called at the top level of a test. #[cheatcode(group = Testing, safety = Unsafe)] function skip(bool skipTest, string calldata reason) external; /// Asserts that the given condition is true. #[cheatcode(group = Testing, safety = Safe)] function assertTrue(bool condition) external pure; /// Asserts that the given condition is true and includes error message into revert string on failure. #[cheatcode(group = Testing, safety = Safe)] function assertTrue(bool condition, string calldata error) external pure; /// Asserts that the given condition is false. #[cheatcode(group = Testing, safety = Safe)] function assertFalse(bool condition) external pure; /// Asserts that the given condition is false and includes error message into revert string on failure. #[cheatcode(group = Testing, safety = Safe)] function assertFalse(bool condition, string calldata error) external pure; /// Asserts that two `bool` values are equal. #[cheatcode(group = Testing, safety = Safe)] function assertEq(bool left, bool right) external pure; /// Asserts that two `bool` values are equal and includes error message into revert string on failure. #[cheatcode(group = Testing, safety = Safe)] function assertEq(bool left, bool right, string calldata error) external pure; /// Asserts that two `uint256` values are equal. #[cheatcode(group = Testing, safety = Safe)] function assertEq(uint256 left, uint256 right) external pure; /// Asserts that two `uint256` values are equal and includes error message into revert string on failure. #[cheatcode(group = Testing, safety = Safe)] function assertEq(uint256 left, uint256 right, string calldata error) external pure; /// Asserts that two `int256` values are equal. #[cheatcode(group = Testing, safety = Safe)] function assertEq(int256 left, int256 right) external pure; /// Asserts that two `int256` values are equal and includes error message into revert string on failure. #[cheatcode(group = Testing, safety = Safe)] function assertEq(int256 left, int256 right, string calldata error) external pure; /// Asserts that two `address` values are equal. #[cheatcode(group = Testing, safety = Safe)] function assertEq(address left, address right) external pure; /// Asserts that two `address` values are equal and includes error message into revert string on failure. #[cheatcode(group = Testing, safety = Safe)] function assertEq(address left, address right, string calldata error) external pure; /// Asserts that two `bytes32` values are equal. #[cheatcode(group = Testing, safety = Safe)] function assertEq(bytes32 left, bytes32 right) external pure; /// Asserts that two `bytes32` values are equal and includes error message into revert string on failure. #[cheatcode(group = Testing, safety = Safe)] function assertEq(bytes32 left, bytes32 right, string calldata error) external pure; /// Asserts that two `string` values are equal. #[cheatcode(group = Testing, safety = Safe)] function assertEq(string calldata left, string calldata right) external pure; /// Asserts that two `string` values are equal and includes error message into revert string on failure. #[cheatcode(group = Testing, safety = Safe)] function assertEq(string calldata left, string calldata right, string calldata error) external pure; /// Asserts that two `bytes` values are equal. #[cheatcode(group = Testing, safety = Safe)] function assertEq(bytes calldata left, bytes calldata right) external pure; /// Asserts that two `bytes` values are equal and includes error message into revert string on failure. #[cheatcode(group = Testing, safety = Safe)] function assertEq(bytes calldata left, bytes calldata right, string calldata error) external pure; /// Asserts that two arrays of `bool` values are equal. #[cheatcode(group = Testing, safety = Safe)] function assertEq(bool[] calldata left, bool[] calldata right) external pure; /// Asserts that two arrays of `bool` values are equal and includes error message into revert string on failure. #[cheatcode(group = Testing, safety = Safe)] function assertEq(bool[] calldata left, bool[] calldata right, string calldata error) external pure; /// Asserts that two arrays of `uint256 values are equal. #[cheatcode(group = Testing, safety = Safe)] function assertEq(uint256[] calldata left, uint256[] calldata right) external pure; /// Asserts that two arrays of `uint256` values are equal and includes error message into revert string on failure. #[cheatcode(group = Testing, safety = Safe)] function assertEq(uint256[] calldata left, uint256[] calldata right, string calldata error) external pure; /// Asserts that two arrays of `int256` values are equal. #[cheatcode(group = Testing, safety = Safe)] function assertEq(int256[] calldata left, int256[] calldata right) external pure; /// Asserts that two arrays of `int256` values are equal and includes error message into revert string on failure. #[cheatcode(group = Testing, safety = Safe)] function assertEq(int256[] calldata left, int256[] calldata right, string calldata error) external pure; /// Asserts that two arrays of `address` values are equal. #[cheatcode(group = Testing, safety = Safe)] function assertEq(address[] calldata left, address[] calldata right) external pure; /// Asserts that two arrays of `address` values are equal and includes error message into revert string on failure. #[cheatcode(group = Testing, safety = Safe)] function assertEq(address[] calldata left, address[] calldata right, string calldata error) external pure; /// Asserts that two arrays of `bytes32` values are equal. #[cheatcode(group = Testing, safety = Safe)] function assertEq(bytes32[] calldata left, bytes32[] calldata right) external pure; /// Asserts that two arrays of `bytes32` values are equal and includes error message into revert string on failure. #[cheatcode(group = Testing, safety = Safe)] function assertEq(bytes32[] calldata left, bytes32[] calldata right, string calldata error) external pure; /// Asserts that two arrays of `string` values are equal. #[cheatcode(group = Testing, safety = Safe)] function assertEq(string[] calldata left, string[] calldata right) external pure; /// Asserts that two arrays of `string` values are equal and includes error message into revert string on failure. #[cheatcode(group = Testing, safety = Safe)] function assertEq(string[] calldata left, string[] calldata right, string calldata error) external pure; /// Asserts that two arrays of `bytes` values are equal. #[cheatcode(group = Testing, safety = Safe)] function assertEq(bytes[] calldata left, bytes[] calldata right) external pure; /// Asserts that two arrays of `bytes` values are equal and includes error message into revert string on failure. #[cheatcode(group = Testing, safety = Safe)] function assertEq(bytes[] calldata left, bytes[] calldata right, string calldata error) external pure; /// Asserts that two `uint256` values are equal, formatting them with decimals in failure message. #[cheatcode(group = Testing, safety = Safe)] function assertEqDecimal(uint256 left, uint256 right, uint256 decimals) external pure; /// Asserts that two `uint256` values are equal, formatting them with decimals in failure message. /// Includes error message into revert string on failure. #[cheatcode(group = Testing, safety = Safe)] function assertEqDecimal(uint256 left, uint256 right, uint256 decimals, string calldata error) external pure; /// Asserts that two `int256` values are equal, formatting them with decimals in failure message. #[cheatcode(group = Testing, safety = Safe)] function assertEqDecimal(int256 left, int256 right, uint256 decimals) external pure; /// Asserts that two `int256` values are equal, formatting them with decimals in failure message. /// Includes error message into revert string on failure. #[cheatcode(group = Testing, safety = Safe)] function assertEqDecimal(int256 left, int256 right, uint256 decimals, string calldata error) external pure; /// Asserts that two `bool` values are not equal. #[cheatcode(group = Testing, safety = Safe)] function assertNotEq(bool left, bool right) external pure; /// Asserts that two `bool` values are not equal and includes error message into revert string on failure. #[cheatcode(group = Testing, safety = Safe)] function assertNotEq(bool left, bool right, string calldata error) external pure; /// Asserts that two `uint256` values are not equal. #[cheatcode(group = Testing, safety = Safe)] function assertNotEq(uint256 left, uint256 right) external pure; /// Asserts that two `uint256` values are not equal and includes error message into revert string on failure. #[cheatcode(group = Testing, safety = Safe)] function assertNotEq(uint256 left, uint256 right, string calldata error) external pure; /// Asserts that two `int256` values are not equal. #[cheatcode(group = Testing, safety = Safe)] function assertNotEq(int256 left, int256 right) external pure; /// Asserts that two `int256` values are not equal and includes error message into revert string on failure. #[cheatcode(group = Testing, safety = Safe)] function assertNotEq(int256 left, int256 right, string calldata error) external pure; /// Asserts that two `address` values are not equal. #[cheatcode(group = Testing, safety = Safe)] function assertNotEq(address left, address right) external pure; /// Asserts that two `address` values are not equal and includes error message into revert string on failure. #[cheatcode(group = Testing, safety = Safe)] function assertNotEq(address left, address right, string calldata error) external pure; /// Asserts that two `bytes32` values are not equal. #[cheatcode(group = Testing, safety = Safe)] function assertNotEq(bytes32 left, bytes32 right) external pure; /// Asserts that two `bytes32` values are not equal and includes error message into revert string on failure. #[cheatcode(group = Testing, safety = Safe)] function assertNotEq(bytes32 left, bytes32 right, string calldata error) external pure; /// Asserts that two `string` values are not equal. #[cheatcode(group = Testing, safety = Safe)] function assertNotEq(string calldata left, string calldata right) external pure; /// Asserts that two `string` values are not equal and includes error message into revert string on failure. #[cheatcode(group = Testing, safety = Safe)] function assertNotEq(string calldata left, string calldata right, string calldata error) external pure; /// Asserts that two `bytes` values are not equal. #[cheatcode(group = Testing, safety = Safe)] function assertNotEq(bytes calldata left, bytes calldata right) external pure; /// Asserts that two `bytes` values are not equal and includes error message into revert string on failure. #[cheatcode(group = Testing, safety = Safe)] function assertNotEq(bytes calldata left, bytes calldata right, string calldata error) external pure; /// Asserts that two arrays of `bool` values are not equal. #[cheatcode(group = Testing, safety = Safe)] function assertNotEq(bool[] calldata left, bool[] calldata right) external pure; /// Asserts that two arrays of `bool` values are not equal and includes error message into revert string on failure. #[cheatcode(group = Testing, safety = Safe)] function assertNotEq(bool[] calldata left, bool[] calldata right, string calldata error) external pure; /// Asserts that two arrays of `uint256` values are not equal. #[cheatcode(group = Testing, safety = Safe)] function assertNotEq(uint256[] calldata left, uint256[] calldata right) external pure; /// Asserts that two arrays of `uint256` values are not equal and includes error message into revert string on failure. #[cheatcode(group = Testing, safety = Safe)] function assertNotEq(uint256[] calldata left, uint256[] calldata right, string calldata error) external pure; /// Asserts that two arrays of `int256` values are not equal. #[cheatcode(group = Testing, safety = Safe)] function assertNotEq(int256[] calldata left, int256[] calldata right) external pure; /// Asserts that two arrays of `int256` values are not equal and includes error message into revert string on failure. #[cheatcode(group = Testing, safety = Safe)] function assertNotEq(int256[] calldata left, int256[] calldata right, string calldata error) external pure; /// Asserts that two arrays of `address` values are not equal. #[cheatcode(group = Testing, safety = Safe)] function assertNotEq(address[] calldata left, address[] calldata right) external pure; /// Asserts that two arrays of `address` values are not equal and includes error message into revert string on failure. #[cheatcode(group = Testing, safety = Safe)] function assertNotEq(address[] calldata left, address[] calldata right, string calldata error) external pure; /// Asserts that two arrays of `bytes32` values are not equal. #[cheatcode(group = Testing, safety = Safe)] function assertNotEq(bytes32[] calldata left, bytes32[] calldata right) external pure; /// Asserts that two arrays of `bytes32` values are not equal and includes error message into revert string on failure. #[cheatcode(group = Testing, safety = Safe)] function assertNotEq(bytes32[] calldata left, bytes32[] calldata right, string calldata error) external pure; /// Asserts that two arrays of `string` values are not equal. #[cheatcode(group = Testing, safety = Safe)] function assertNotEq(string[] calldata left, string[] calldata right) external pure; /// Asserts that two arrays of `string` values are not equal and includes error message into revert string on failure. #[cheatcode(group = Testing, safety = Safe)] function assertNotEq(string[] calldata left, string[] calldata right, string calldata error) external pure; /// Asserts that two arrays of `bytes` values are not equal. #[cheatcode(group = Testing, safety = Safe)] function assertNotEq(bytes[] calldata left, bytes[] calldata right) external pure; /// Asserts that two arrays of `bytes` values are not equal and includes error message into revert string on failure. #[cheatcode(group = Testing, safety = Safe)] function assertNotEq(bytes[] calldata left, bytes[] calldata right, string calldata error) external pure; /// Asserts that two `uint256` values are not equal, formatting them with decimals in failure message. #[cheatcode(group = Testing, safety = Safe)] function assertNotEqDecimal(uint256 left, uint256 right, uint256 decimals) external pure; /// Asserts that two `uint256` values are not equal, formatting them with decimals in failure message. /// Includes error message into revert string on failure. #[cheatcode(group = Testing, safety = Safe)] function assertNotEqDecimal(uint256 left, uint256 right, uint256 decimals, string calldata error) external pure; /// Asserts that two `int256` values are not equal, formatting them with decimals in failure message. #[cheatcode(group = Testing, safety = Safe)] function assertNotEqDecimal(int256 left, int256 right, uint256 decimals) external pure; /// Asserts that two `int256` values are not equal, formatting them with decimals in failure message. /// Includes error message into revert string on failure. #[cheatcode(group = Testing, safety = Safe)] function assertNotEqDecimal(int256 left, int256 right, uint256 decimals, string calldata error) external pure; /// Compares two `uint256` values. Expects first value to be greater than second. #[cheatcode(group = Testing, safety = Safe)] function assertGt(uint256 left, uint256 right) external pure; /// Compares two `uint256` values. Expects first value to be greater than second. /// Includes error message into revert string on failure. #[cheatcode(group = Testing, safety = Safe)] function assertGt(uint256 left, uint256 right, string calldata error) external pure; /// Compares two `int256` values. Expects first value to be greater than second. #[cheatcode(group = Testing, safety = Safe)] function assertGt(int256 left, int256 right) external pure; /// Compares two `int256` values. Expects first value to be greater than second. /// Includes error message into revert string on failure. #[cheatcode(group = Testing, safety = Safe)] function assertGt(int256 left, int256 right, string calldata error) external pure; /// Compares two `uint256` values. Expects first value to be greater than second. /// Formats values with decimals in failure message. #[cheatcode(group = Testing, safety = Safe)] function assertGtDecimal(uint256 left, uint256 right, uint256 decimals) external pure; /// Compares two `uint256` values. Expects first value to be greater than second. /// Formats values with decimals in failure message. Includes error message into revert string on failure. #[cheatcode(group = Testing, safety = Safe)] function assertGtDecimal(uint256 left, uint256 right, uint256 decimals, string calldata error) external pure; /// Compares two `int256` values. Expects first value to be greater than second. /// Formats values with decimals in failure message. #[cheatcode(group = Testing, safety = Safe)] function assertGtDecimal(int256 left, int256 right, uint256 decimals) external pure; /// Compares two `int256` values. Expects first value to be greater than second. /// Formats values with decimals in failure message. Includes error message into revert string on failure. #[cheatcode(group = Testing, safety = Safe)] function assertGtDecimal(int256 left, int256 right, uint256 decimals, string calldata error) external pure; /// Compares two `uint256` values. Expects first value to be greater than or equal to second. #[cheatcode(group = Testing, safety = Safe)] function assertGe(uint256 left, uint256 right) external pure; /// Compares two `uint256` values. Expects first value to be greater than or equal to second. /// Includes error message into revert string on failure. #[cheatcode(group = Testing, safety = Safe)] function assertGe(uint256 left, uint256 right, string calldata error) external pure; /// Compares two `int256` values. Expects first value to be greater than or equal to second. #[cheatcode(group = Testing, safety = Safe)] function assertGe(int256 left, int256 right) external pure; /// Compares two `int256` values. Expects first value to be greater than or equal to second. /// Includes error message into revert string on failure. #[cheatcode(group = Testing, safety = Safe)] function assertGe(int256 left, int256 right, string calldata error) external pure; /// Compares two `uint256` values. Expects first value to be greater than or equal to second. /// Formats values with decimals in failure message. #[cheatcode(group = Testing, safety = Safe)] function assertGeDecimal(uint256 left, uint256 right, uint256 decimals) external pure; /// Compares two `uint256` values. Expects first value to be greater than or equal to second. /// Formats values with decimals in failure message. Includes error message into revert string on failure. #[cheatcode(group = Testing, safety = Safe)] function assertGeDecimal(uint256 left, uint256 right, uint256 decimals, string calldata error) external pure; /// Compares two `int256` values. Expects first value to be greater than or equal to second. /// Formats values with decimals in failure message. #[cheatcode(group = Testing, safety = Safe)] function assertGeDecimal(int256 left, int256 right, uint256 decimals) external pure; /// Compares two `int256` values. Expects first value to be greater than or equal to second. /// Formats values with decimals in failure message. Includes error message into revert string on failure. #[cheatcode(group = Testing, safety = Safe)] function assertGeDecimal(int256 left, int256 right, uint256 decimals, string calldata error) external pure; /// Compares two `uint256` values. Expects first value to be less than second. #[cheatcode(group = Testing, safety = Safe)] function assertLt(uint256 left, uint256 right) external pure; /// Compares two `uint256` values. Expects first value to be less than second. /// Includes error message into revert string on failure. #[cheatcode(group = Testing, safety = Safe)] function assertLt(uint256 left, uint256 right, string calldata error) external pure; /// Compares two `int256` values. Expects first value to be less than second. #[cheatcode(group = Testing, safety = Safe)] function assertLt(int256 left, int256 right) external pure; /// Compares two `int256` values. Expects first value to be less than second. /// Includes error message into revert string on failure. #[cheatcode(group = Testing, safety = Safe)] function assertLt(int256 left, int256 right, string calldata error) external pure; /// Compares two `uint256` values. Expects first value to be less than second. /// Formats values with decimals in failure message. #[cheatcode(group = Testing, safety = Safe)] function assertLtDecimal(uint256 left, uint256 right, uint256 decimals) external pure; /// Compares two `uint256` values. Expects first value to be less than second. /// Formats values with decimals in failure message. Includes error message into revert string on failure. #[cheatcode(group = Testing, safety = Safe)] function assertLtDecimal(uint256 left, uint256 right, uint256 decimals, string calldata error) external pure; /// Compares two `int256` values. Expects first value to be less than second. /// Formats values with decimals in failure message. #[cheatcode(group = Testing, safety = Safe)] function assertLtDecimal(int256 left, int256 right, uint256 decimals) external pure; /// Compares two `int256` values. Expects first value to be less than second. /// Formats values with decimals in failure message. Includes error message into revert string on failure. #[cheatcode(group = Testing, safety = Safe)] function assertLtDecimal(int256 left, int256 right, uint256 decimals, string calldata error) external pure; /// Compares two `uint256` values. Expects first value to be less than or equal to second. #[cheatcode(group = Testing, safety = Safe)] function assertLe(uint256 left, uint256 right) external pure; /// Compares two `uint256` values. Expects first value to be less than or equal to second. /// Includes error message into revert string on failure. #[cheatcode(group = Testing, safety = Safe)] function assertLe(uint256 left, uint256 right, string calldata error) external pure; /// Compares two `int256` values. Expects first value to be less than or equal to second. #[cheatcode(group = Testing, safety = Safe)] function assertLe(int256 left, int256 right) external pure; /// Compares two `int256` values. Expects first value to be less than or equal to second. /// Includes error message into revert string on failure. #[cheatcode(group = Testing, safety = Safe)] function assertLe(int256 left, int256 right, string calldata error) external pure; /// Compares two `uint256` values. Expects first value to be less than or equal to second. /// Formats values with decimals in failure message. #[cheatcode(group = Testing, safety = Safe)] function assertLeDecimal(uint256 left, uint256 right, uint256 decimals) external pure; /// Compares two `uint256` values. Expects first value to be less than or equal to second. /// Formats values with decimals in failure message. Includes error message into revert string on failure. #[cheatcode(group = Testing, safety = Safe)] function assertLeDecimal(uint256 left, uint256 right, uint256 decimals, string calldata error) external pure; /// Compares two `int256` values. Expects first value to be less than or equal to second. /// Formats values with decimals in failure message. #[cheatcode(group = Testing, safety = Safe)] function assertLeDecimal(int256 left, int256 right, uint256 decimals) external pure; /// Compares two `int256` values. Expects first value to be less than or equal to second. /// Formats values with decimals in failure message. Includes error message into revert string on failure. #[cheatcode(group = Testing, safety = Safe)] function assertLeDecimal(int256 left, int256 right, uint256 decimals, string calldata error) external pure; /// Compares two `uint256` values. Expects difference to be less than or equal to `maxDelta`. #[cheatcode(group = Testing, safety = Safe)] function assertApproxEqAbs(uint256 left, uint256 right, uint256 maxDelta) external pure; /// Compares two `uint256` values. Expects difference to be less than or equal to `maxDelta`. /// Includes error message into revert string on failure. #[cheatcode(group = Testing, safety = Safe)] function assertApproxEqAbs(uint256 left, uint256 right, uint256 maxDelta, string calldata error) external pure; /// Compares two `int256` values. Expects difference to be less than or equal to `maxDelta`. #[cheatcode(group = Testing, safety = Safe)] function assertApproxEqAbs(int256 left, int256 right, uint256 maxDelta) external pure; /// Compares two `int256` values. Expects difference to be less than or equal to `maxDelta`. /// Includes error message into revert string on failure. #[cheatcode(group = Testing, safety = Safe)] function assertApproxEqAbs(int256 left, int256 right, uint256 maxDelta, string calldata error) external pure; /// Compares two `uint256` values. Expects difference to be less than or equal to `maxDelta`. /// Formats values with decimals in failure message. #[cheatcode(group = Testing, safety = Safe)] function assertApproxEqAbsDecimal(uint256 left, uint256 right, uint256 maxDelta, uint256 decimals) external pure; /// Compares two `uint256` values. Expects difference to be less than or equal to `maxDelta`. /// Formats values with decimals in failure message. Includes error message into revert string on failure. #[cheatcode(group = Testing, safety = Safe)] function assertApproxEqAbsDecimal( uint256 left, uint256 right, uint256 maxDelta, uint256 decimals, string calldata error ) external pure; /// Compares two `int256` values. Expects difference to be less than or equal to `maxDelta`. /// Formats values with decimals in failure message. #[cheatcode(group = Testing, safety = Safe)] function assertApproxEqAbsDecimal(int256 left, int256 right, uint256 maxDelta, uint256 decimals) external pure; /// Compares two `int256` values. Expects difference to be less than or equal to `maxDelta`. /// Formats values with decimals in failure message. Includes error message into revert string on failure. #[cheatcode(group = Testing, safety = Safe)] function assertApproxEqAbsDecimal( int256 left, int256 right, uint256 maxDelta, uint256 decimals, string calldata error ) external pure; /// Compares two `uint256` values. Expects relative difference in percents to be less than or equal to `maxPercentDelta`. /// `maxPercentDelta` is an 18 decimal fixed point number, where 1e18 == 100% #[cheatcode(group = Testing, safety = Safe)] function assertApproxEqRel(uint256 left, uint256 right, uint256 maxPercentDelta) external pure; /// Compares two `uint256` values. Expects relative difference in percents to be less than or equal to `maxPercentDelta`. /// `maxPercentDelta` is an 18 decimal fixed point number, where 1e18 == 100% /// Includes error message into revert string on failure. #[cheatcode(group = Testing, safety = Safe)] function assertApproxEqRel(uint256 left, uint256 right, uint256 maxPercentDelta, string calldata error) external pure; /// Compares two `int256` values. Expects relative difference in percents to be less than or equal to `maxPercentDelta`. /// `maxPercentDelta` is an 18 decimal fixed point number, where 1e18 == 100% #[cheatcode(group = Testing, safety = Safe)] function assertApproxEqRel(int256 left, int256 right, uint256 maxPercentDelta) external pure; /// Compares two `int256` values. Expects relative difference in percents to be less than or equal to `maxPercentDelta`. /// `maxPercentDelta` is an 18 decimal fixed point number, where 1e18 == 100% /// Includes error message into revert string on failure. #[cheatcode(group = Testing, safety = Safe)] function assertApproxEqRel(int256 left, int256 right, uint256 maxPercentDelta, string calldata error) external pure; /// Compares two `uint256` values. Expects relative difference in percents to be less than or equal to `maxPercentDelta`. /// `maxPercentDelta` is an 18 decimal fixed point number, where 1e18 == 100% /// Formats values with decimals in failure message. #[cheatcode(group = Testing, safety = Safe)] function assertApproxEqRelDecimal( uint256 left, uint256 right, uint256 maxPercentDelta, uint256 decimals ) external pure; /// Compares two `uint256` values. Expects relative difference in percents to be less than or equal to `maxPercentDelta`. /// `maxPercentDelta` is an 18 decimal fixed point number, where 1e18 == 100% /// Formats values with decimals in failure message. Includes error message into revert string on failure. #[cheatcode(group = Testing, safety = Safe)] function assertApproxEqRelDecimal( uint256 left, uint256 right, uint256 maxPercentDelta, uint256 decimals, string calldata error ) external pure; /// Compares two `int256` values. Expects relative difference in percents to be less than or equal to `maxPercentDelta`. /// `maxPercentDelta` is an 18 decimal fixed point number, where 1e18 == 100% /// Formats values with decimals in failure message. #[cheatcode(group = Testing, safety = Safe)] function assertApproxEqRelDecimal( int256 left, int256 right, uint256 maxPercentDelta, uint256 decimals ) external pure; /// Compares two `int256` values. Expects relative difference in percents to be less than or equal to `maxPercentDelta`. /// `maxPercentDelta` is an 18 decimal fixed point number, where 1e18 == 100% /// Formats values with decimals in failure message. Includes error message into revert string on failure. #[cheatcode(group = Testing, safety = Safe)] function assertApproxEqRelDecimal( int256 left, int256 right, uint256 maxPercentDelta, uint256 decimals, string calldata error ) external pure; /// Returns true if the current Foundry version is greater than or equal to the given version. /// The given version string must be in the format `major.minor.patch`. /// /// This is equivalent to `foundryVersionCmp(version) >= 0`. #[cheatcode(group = Testing, safety = Safe)] function foundryVersionAtLeast(string calldata version) external view returns (bool); /// Compares the current Foundry version with the given version string. /// The given version string must be in the format `major.minor.patch`. /// /// Returns: /// -1 if current Foundry version is less than the given version /// 0 if current Foundry version equals the given version /// 1 if current Foundry version is greater than the given version /// /// This result can then be used with a comparison operator against `0`. /// For example, to check if the current Foundry version is greater than or equal to `1.0.0`: /// `if (foundryVersionCmp("1.0.0") >= 0) { ... }` #[cheatcode(group = Testing, safety = Safe)] function foundryVersionCmp(string calldata version) external view returns (int256); // ======== OS and Filesystem ======== // -------- Metadata -------- /// Returns true if the given path points to an existing entity, else returns false. #[cheatcode(group = Filesystem)] function exists(string calldata path) external view returns (bool result); /// Given a path, query the file system to get information about a file, directory, etc. #[cheatcode(group = Filesystem)] function fsMetadata(string calldata path) external view returns (FsMetadata memory metadata); /// Returns true if the path exists on disk and is pointing at a directory, else returns false. #[cheatcode(group = Filesystem)] function isDir(string calldata path) external view returns (bool result); /// Returns true if the path exists on disk and is pointing at a regular file, else returns false. #[cheatcode(group = Filesystem)] function isFile(string calldata path) external view returns (bool result); /// Get the path of the current project root. #[cheatcode(group = Filesystem)] function projectRoot() external view returns (string memory path); /// Get the source file path of the currently running test or script contract, /// relative to the project root. #[cheatcode(group = Filesystem)] function currentFilePath() external view returns (string memory path); /// Returns the time since unix epoch in milliseconds. #[cheatcode(group = Filesystem)] function unixTime() external view returns (uint256 milliseconds); // -------- Reading and writing -------- /// Closes file for reading, resetting the offset and allowing to read it from beginning with readLine. /// `path` is relative to the project root. #[cheatcode(group = Filesystem)] function closeFile(string calldata path) external; /// Copies the contents of one file to another. This function will **overwrite** the contents of `to`. /// On success, the total number of bytes copied is returned and it is equal to the length of the `to` file as reported by `metadata`. /// Both `from` and `to` are relative to the project root. #[cheatcode(group = Filesystem)] function copyFile(string calldata from, string calldata to) external returns (uint64 copied); /// Creates a new, empty directory at the provided path. /// This cheatcode will revert in the following situations, but is not limited to just these cases: /// - User lacks permissions to modify `path`. /// - A parent of the given path doesn't exist and `recursive` is false. /// - `path` already exists and `recursive` is false. /// `path` is relative to the project root. #[cheatcode(group = Filesystem)] function createDir(string calldata path, bool recursive) external; /// Reads the directory at the given path recursively, up to `maxDepth`. /// `maxDepth` defaults to 1, meaning only the direct children of the given directory will be returned. /// Follows symbolic links if `followLinks` is true. #[cheatcode(group = Filesystem)] function readDir(string calldata path) external view returns (DirEntry[] memory entries); /// See `readDir(string)`. #[cheatcode(group = Filesystem)] function readDir(string calldata path, uint64 maxDepth) external view returns (DirEntry[] memory entries); /// See `readDir(string)`. #[cheatcode(group = Filesystem)] function readDir(string calldata path, uint64 maxDepth, bool followLinks) external view returns (DirEntry[] memory entries); /// Reads the entire content of file to string. `path` is relative to the project root. #[cheatcode(group = Filesystem)] function readFile(string calldata path) external view returns (string memory data); /// Reads the entire content of file as binary. `path` is relative to the project root. #[cheatcode(group = Filesystem)] function readFileBinary(string calldata path) external view returns (bytes memory data); /// Reads next line of file to string. #[cheatcode(group = Filesystem)] function readLine(string calldata path) external view returns (string memory line); /// Reads a symbolic link, returning the path that the link points to. /// This cheatcode will revert in the following situations, but is not limited to just these cases: /// - `path` is not a symbolic link. /// - `path` does not exist. #[cheatcode(group = Filesystem)] function readLink(string calldata linkPath) external view returns (string memory targetPath); /// Removes a directory at the provided path. /// This cheatcode will revert in the following situations, but is not limited to just these cases: /// - `path` doesn't exist. /// - `path` isn't a directory. /// - User lacks permissions to modify `path`. /// - The directory is not empty and `recursive` is false. /// `path` is relative to the project root. #[cheatcode(group = Filesystem)] function removeDir(string calldata path, bool recursive) external; /// Removes a file from the filesystem. /// This cheatcode will revert in the following situations, but is not limited to just these cases: /// - `path` points to a directory. /// - The file doesn't exist. /// - The user lacks permissions to remove the file. /// `path` is relative to the project root. #[cheatcode(group = Filesystem)] function removeFile(string calldata path) external; /// Writes data to file, creating a file if it does not exist, and entirely replacing its contents if it does. /// `path` is relative to the project root. #[cheatcode(group = Filesystem)] function writeFile(string calldata path, string calldata data) external; /// Writes binary data to a file, creating a file if it does not exist, and entirely replacing its contents if it does. /// `path` is relative to the project root. #[cheatcode(group = Filesystem)] function writeFileBinary(string calldata path, bytes calldata data) external; /// Writes line to file, creating a file if it does not exist. /// `path` is relative to the project root. #[cheatcode(group = Filesystem)] function writeLine(string calldata path, string calldata data) external; /// Gets the artifact path from code (aka. creation code). #[cheatcode(group = Filesystem)] function getArtifactPathByCode(bytes calldata code) external view returns (string memory path); /// Gets the artifact path from deployed code (aka. runtime code). #[cheatcode(group = Filesystem)] function getArtifactPathByDeployedCode(bytes calldata deployedCode) external view returns (string memory path); /// Gets the creation bytecode from an artifact file. Takes in the relative path to the json file or the path to the /// artifact in the form of :: where and parts are optional. #[cheatcode(group = Filesystem)] function getCode(string calldata artifactPath) external view returns (bytes memory creationBytecode); /// Deploys a contract from an artifact file. Takes in the relative path to the json file or the path to the /// artifact in the form of :: where and parts are optional. /// Reverts if the target artifact contains unlinked library placeholders. #[cheatcode(group = Filesystem)] function deployCode(string calldata artifactPath) external returns (address deployedAddress); /// Deploys a contract from an artifact file. Takes in the relative path to the json file or the path to the /// artifact in the form of :: where and parts are optional. /// Reverts if the target artifact contains unlinked library placeholders. /// /// Additionally accepts abi-encoded constructor arguments. #[cheatcode(group = Filesystem)] function deployCode(string calldata artifactPath, bytes calldata constructorArgs) external returns (address deployedAddress); /// Deploys a contract from an artifact file. Takes in the relative path to the json file or the path to the /// artifact in the form of :: where and parts are optional. /// Reverts if the target artifact contains unlinked library placeholders. /// /// Additionally accepts `msg.value`. #[cheatcode(group = Filesystem)] function deployCode(string calldata artifactPath, uint256 value) external returns (address deployedAddress); /// Deploys a contract from an artifact file. Takes in the relative path to the json file or the path to the /// artifact in the form of :: where and parts are optional. /// Reverts if the target artifact contains unlinked library placeholders. /// /// Additionally accepts abi-encoded constructor arguments and `msg.value`. #[cheatcode(group = Filesystem)] function deployCode(string calldata artifactPath, bytes calldata constructorArgs, uint256 value) external returns (address deployedAddress); /// Deploys a contract from an artifact file, using the CREATE2 salt. Takes in the relative path to the json file or the path to the /// artifact in the form of :: where and parts are optional. /// Reverts if the target artifact contains unlinked library placeholders. #[cheatcode(group = Filesystem)] function deployCode(string calldata artifactPath, bytes32 salt) external returns (address deployedAddress); /// Deploys a contract from an artifact file, using the CREATE2 salt. Takes in the relative path to the json file or the path to the /// artifact in the form of :: where and parts are optional. /// Reverts if the target artifact contains unlinked library placeholders. /// /// Additionally accepts abi-encoded constructor arguments. #[cheatcode(group = Filesystem)] function deployCode(string calldata artifactPath, bytes calldata constructorArgs, bytes32 salt) external returns (address deployedAddress); /// Deploys a contract from an artifact file, using the CREATE2 salt. Takes in the relative path to the json file or the path to the /// artifact in the form of :: where and parts are optional. /// Reverts if the target artifact contains unlinked library placeholders. /// /// Additionally accepts `msg.value`. #[cheatcode(group = Filesystem)] function deployCode(string calldata artifactPath, uint256 value, bytes32 salt) external returns (address deployedAddress); /// Deploys a contract from an artifact file, using the CREATE2 salt. Takes in the relative path to the json file or the path to the /// artifact in the form of :: where and parts are optional. /// Reverts if the target artifact contains unlinked library placeholders. /// /// Additionally accepts abi-encoded constructor arguments and `msg.value`. #[cheatcode(group = Filesystem)] function deployCode(string calldata artifactPath, bytes calldata constructorArgs, uint256 value, bytes32 salt) external returns (address deployedAddress); /// Gets the deployed bytecode from an artifact file. Takes in the relative path to the json file or the path to the /// artifact in the form of :: where and parts are optional. #[cheatcode(group = Filesystem)] function getDeployedCode(string calldata artifactPath) external view returns (bytes memory runtimeBytecode); /// Returns the most recent broadcast for the given contract on `chainId` matching `txType`. /// /// For example: /// /// The most recent deployment can be fetched by passing `txType` as `CREATE` or `CREATE2`. /// /// The most recent call can be fetched by passing `txType` as `CALL`. #[cheatcode(group = Filesystem)] function getBroadcast(string calldata contractName, uint64 chainId, BroadcastTxType txType) external view returns (BroadcastTxSummary memory); /// Returns all broadcasts for the given contract on `chainId` with the specified `txType`. /// /// Sorted such that the most recent broadcast is the first element, and the oldest is the last. i.e descending order of BroadcastTxSummary.blockNumber. #[cheatcode(group = Filesystem)] function getBroadcasts(string calldata contractName, uint64 chainId, BroadcastTxType txType) external view returns (BroadcastTxSummary[] memory); /// Returns all broadcasts for the given contract on `chainId`. /// /// Sorted such that the most recent broadcast is the first element, and the oldest is the last. i.e descending order of BroadcastTxSummary.blockNumber. #[cheatcode(group = Filesystem)] function getBroadcasts(string calldata contractName, uint64 chainId) external view returns (BroadcastTxSummary[] memory); /// Returns the most recent deployment for the current `chainId`. #[cheatcode(group = Filesystem)] function getDeployment(string calldata contractName) external view returns (address deployedAddress); /// Returns the most recent deployment for the given contract on `chainId` #[cheatcode(group = Filesystem)] function getDeployment(string calldata contractName, uint64 chainId) external view returns (address deployedAddress); /// Returns all deployments for the given contract on `chainId` /// /// Sorted in descending order of deployment time i.e descending order of BroadcastTxSummary.blockNumber. /// /// The most recent deployment is the first element, and the oldest is the last. #[cheatcode(group = Filesystem)] function getDeployments(string calldata contractName, uint64 chainId) external view returns (address[] memory deployedAddresses); // -------- Foreign Function Interface -------- /// Performs a foreign function call via the terminal. #[cheatcode(group = Filesystem)] function ffi(string[] calldata commandInput) external returns (bytes memory result); /// Performs a foreign function call via terminal and returns the exit code, stdout, and stderr. #[cheatcode(group = Filesystem)] function tryFfi(string[] calldata commandInput) external returns (FfiResult memory result); // -------- User Interaction -------- /// Prompts the user for a string value in the terminal. #[cheatcode(group = Filesystem)] function prompt(string calldata promptText) external returns (string memory input); /// Prompts the user for a hidden string value in the terminal. #[cheatcode(group = Filesystem)] function promptSecret(string calldata promptText) external returns (string memory input); /// Prompts the user for hidden uint256 in the terminal (usually pk). #[cheatcode(group = Filesystem)] function promptSecretUint(string calldata promptText) external returns (uint256); /// Prompts the user for an address in the terminal. #[cheatcode(group = Filesystem)] function promptAddress(string calldata promptText) external returns (address); /// Prompts the user for uint256 in the terminal. #[cheatcode(group = Filesystem)] function promptUint(string calldata promptText) external returns (uint256); // ======== Environment Variables ======== /// Resolves the env variable placeholders of a given input string. #[cheatcode(group = Environment)] function resolveEnv(string calldata input) external returns (string memory); /// Sets environment variables. #[cheatcode(group = Environment)] function setEnv(string calldata name, string calldata value) external; /// Gets the environment variable `name` and returns true if it exists, else returns false. #[cheatcode(group = Environment)] function envExists(string calldata name) external view returns (bool result); /// Gets the environment variable `name` and parses it as `bool`. /// Reverts if the variable was not found or could not be parsed. #[cheatcode(group = Environment)] function envBool(string calldata name) external view returns (bool value); /// Gets the environment variable `name` and parses it as `uint256`. /// Reverts if the variable was not found or could not be parsed. #[cheatcode(group = Environment)] function envUint(string calldata name) external view returns (uint256 value); /// Gets the environment variable `name` and parses it as `int256`. /// Reverts if the variable was not found or could not be parsed. #[cheatcode(group = Environment)] function envInt(string calldata name) external view returns (int256 value); /// Gets the environment variable `name` and parses it as `address`. /// Reverts if the variable was not found or could not be parsed. #[cheatcode(group = Environment)] function envAddress(string calldata name) external view returns (address value); /// Gets the environment variable `name` and parses it as `bytes32`. /// Reverts if the variable was not found or could not be parsed. #[cheatcode(group = Environment)] function envBytes32(string calldata name) external view returns (bytes32 value); /// Gets the environment variable `name` and parses it as `string`. /// Reverts if the variable was not found or could not be parsed. #[cheatcode(group = Environment)] function envString(string calldata name) external view returns (string memory value); /// Gets the environment variable `name` and parses it as `bytes`. /// Reverts if the variable was not found or could not be parsed. #[cheatcode(group = Environment)] function envBytes(string calldata name) external view returns (bytes memory value); /// Gets the environment variable `name` and parses it as an array of `bool`, delimited by `delim`. /// Reverts if the variable was not found or could not be parsed. #[cheatcode(group = Environment)] function envBool(string calldata name, string calldata delim) external view returns (bool[] memory value); /// Gets the environment variable `name` and parses it as an array of `uint256`, delimited by `delim`. /// Reverts if the variable was not found or could not be parsed. #[cheatcode(group = Environment)] function envUint(string calldata name, string calldata delim) external view returns (uint256[] memory value); /// Gets the environment variable `name` and parses it as an array of `int256`, delimited by `delim`. /// Reverts if the variable was not found or could not be parsed. #[cheatcode(group = Environment)] function envInt(string calldata name, string calldata delim) external view returns (int256[] memory value); /// Gets the environment variable `name` and parses it as an array of `address`, delimited by `delim`. /// Reverts if the variable was not found or could not be parsed. #[cheatcode(group = Environment)] function envAddress(string calldata name, string calldata delim) external view returns (address[] memory value); /// Gets the environment variable `name` and parses it as an array of `bytes32`, delimited by `delim`. /// Reverts if the variable was not found or could not be parsed. #[cheatcode(group = Environment)] function envBytes32(string calldata name, string calldata delim) external view returns (bytes32[] memory value); /// Gets the environment variable `name` and parses it as an array of `string`, delimited by `delim`. /// Reverts if the variable was not found or could not be parsed. #[cheatcode(group = Environment)] function envString(string calldata name, string calldata delim) external view returns (string[] memory value); /// Gets the environment variable `name` and parses it as an array of `bytes`, delimited by `delim`. /// Reverts if the variable was not found or could not be parsed. #[cheatcode(group = Environment)] function envBytes(string calldata name, string calldata delim) external view returns (bytes[] memory value); /// Gets the environment variable `name` and parses it as `bool`. /// Reverts if the variable could not be parsed. /// Returns `defaultValue` if the variable was not found. #[cheatcode(group = Environment)] function envOr(string calldata name, bool defaultValue) external view returns (bool value); /// Gets the environment variable `name` and parses it as `uint256`. /// Reverts if the variable could not be parsed. /// Returns `defaultValue` if the variable was not found. #[cheatcode(group = Environment)] function envOr(string calldata name, uint256 defaultValue) external view returns (uint256 value); /// Gets the environment variable `name` and parses it as `int256`. /// Reverts if the variable could not be parsed. /// Returns `defaultValue` if the variable was not found. #[cheatcode(group = Environment)] function envOr(string calldata name, int256 defaultValue) external view returns (int256 value); /// Gets the environment variable `name` and parses it as `address`. /// Reverts if the variable could not be parsed. /// Returns `defaultValue` if the variable was not found. #[cheatcode(group = Environment)] function envOr(string calldata name, address defaultValue) external view returns (address value); /// Gets the environment variable `name` and parses it as `bytes32`. /// Reverts if the variable could not be parsed. /// Returns `defaultValue` if the variable was not found. #[cheatcode(group = Environment)] function envOr(string calldata name, bytes32 defaultValue) external view returns (bytes32 value); /// Gets the environment variable `name` and parses it as `string`. /// Reverts if the variable could not be parsed. /// Returns `defaultValue` if the variable was not found. #[cheatcode(group = Environment)] function envOr(string calldata name, string calldata defaultValue) external view returns (string memory value); /// Gets the environment variable `name` and parses it as `bytes`. /// Reverts if the variable could not be parsed. /// Returns `defaultValue` if the variable was not found. #[cheatcode(group = Environment)] function envOr(string calldata name, bytes calldata defaultValue) external view returns (bytes memory value); /// Gets the environment variable `name` and parses it as an array of `bool`, delimited by `delim`. /// Reverts if the variable could not be parsed. /// Returns `defaultValue` if the variable was not found. #[cheatcode(group = Environment)] function envOr(string calldata name, string calldata delim, bool[] calldata defaultValue) external view returns (bool[] memory value); /// Gets the environment variable `name` and parses it as an array of `uint256`, delimited by `delim`. /// Reverts if the variable could not be parsed. /// Returns `defaultValue` if the variable was not found. #[cheatcode(group = Environment)] function envOr(string calldata name, string calldata delim, uint256[] calldata defaultValue) external view returns (uint256[] memory value); /// Gets the environment variable `name` and parses it as an array of `int256`, delimited by `delim`. /// Reverts if the variable could not be parsed. /// Returns `defaultValue` if the variable was not found. #[cheatcode(group = Environment)] function envOr(string calldata name, string calldata delim, int256[] calldata defaultValue) external view returns (int256[] memory value); /// Gets the environment variable `name` and parses it as an array of `address`, delimited by `delim`. /// Reverts if the variable could not be parsed. /// Returns `defaultValue` if the variable was not found. #[cheatcode(group = Environment)] function envOr(string calldata name, string calldata delim, address[] calldata defaultValue) external view returns (address[] memory value); /// Gets the environment variable `name` and parses it as an array of `bytes32`, delimited by `delim`. /// Reverts if the variable could not be parsed. /// Returns `defaultValue` if the variable was not found. #[cheatcode(group = Environment)] function envOr(string calldata name, string calldata delim, bytes32[] calldata defaultValue) external view returns (bytes32[] memory value); /// Gets the environment variable `name` and parses it as an array of `string`, delimited by `delim`. /// Reverts if the variable could not be parsed. /// Returns `defaultValue` if the variable was not found. #[cheatcode(group = Environment)] function envOr(string calldata name, string calldata delim, string[] calldata defaultValue) external view returns (string[] memory value); /// Gets the environment variable `name` and parses it as an array of `bytes`, delimited by `delim`. /// Reverts if the variable could not be parsed. /// Returns `defaultValue` if the variable was not found. #[cheatcode(group = Environment)] function envOr(string calldata name, string calldata delim, bytes[] calldata defaultValue) external view returns (bytes[] memory value); /// Returns true if `forge` command was executed in given context. #[cheatcode(group = Environment)] function isContext(ForgeContext context) external view returns (bool result); // ======== Scripts ======== // -------- Broadcasting Transactions -------- /// Has the next call (at this call depth only) create transactions that can later be signed and sent onchain. /// /// Broadcasting address is determined by checking the following in order: /// 1. If `--sender` argument was provided, that address is used. /// 2. If exactly one signer (e.g. private key, hw wallet, keystore) is set when `forge broadcast` is invoked, that signer is used. /// 3. Otherwise, default foundry sender (1804c8AB1F12E6bbf3894d4083f33e07309d1f38) is used. #[cheatcode(group = Scripting)] function broadcast() external; /// Has the next call (at this call depth only) create a transaction with the address provided /// as the sender that can later be signed and sent onchain. #[cheatcode(group = Scripting)] function broadcast(address signer) external; /// Has the next call (at this call depth only) create a transaction with the private key /// provided as the sender that can later be signed and sent onchain. #[cheatcode(group = Scripting)] function broadcast(uint256 privateKey) external; /// Has all subsequent calls (at this call depth only) create transactions that can later be signed and sent onchain. /// /// Broadcasting address is determined by checking the following in order: /// 1. If `--sender` argument was provided, that address is used. /// 2. If exactly one signer (e.g. private key, hw wallet, keystore) is set when `forge broadcast` is invoked, that signer is used. /// 3. Otherwise, default foundry sender (1804c8AB1F12E6bbf3894d4083f33e07309d1f38) is used. #[cheatcode(group = Scripting)] function startBroadcast() external; /// Has all subsequent calls (at this call depth only) create transactions with the address /// provided that can later be signed and sent onchain. #[cheatcode(group = Scripting)] function startBroadcast(address signer) external; /// Has all subsequent calls (at this call depth only) create transactions with the private key /// provided that can later be signed and sent onchain. #[cheatcode(group = Scripting)] function startBroadcast(uint256 privateKey) external; /// Stops collecting onchain transactions. #[cheatcode(group = Scripting)] function stopBroadcast() external; /// Takes a signed transaction and broadcasts it to the network. #[cheatcode(group = Scripting)] function broadcastRawTransaction(bytes calldata data) external; /// Sign an EIP-7702 authorization for delegation #[cheatcode(group = Scripting)] function signDelegation(address implementation, uint256 privateKey) external returns (SignedDelegation memory signedDelegation); /// Sign an EIP-7702 authorization for delegation for specific nonce #[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); /// Sign an EIP-7702 authorization and designate the next call as an EIP-7702 transaction for specific nonce #[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; /// Returns addresses of available unlocked wallets in the script environment. #[cheatcode(group = Scripting)] function getWallets() external view returns (address[] memory wallets); // ======== Utilities ======== // -------- Strings -------- /// Converts the given value to a `string`. #[cheatcode(group = String)] function toString(address value) external pure returns (string memory stringifiedValue); /// Converts the given value to a `string`. #[cheatcode(group = String)] function toString(bytes calldata value) external pure returns (string memory stringifiedValue); /// Converts the given value to a `string`. #[cheatcode(group = String)] function toString(bytes32 value) external pure returns (string memory stringifiedValue); /// Converts the given value to a `string`. #[cheatcode(group = String)] function toString(bool value) external pure returns (string memory stringifiedValue); /// Converts the given value to a `string`. #[cheatcode(group = String)] function toString(uint256 value) external pure returns (string memory stringifiedValue); /// Converts the given value to a `string`. #[cheatcode(group = String)] function toString(int256 value) external pure returns (string memory stringifiedValue); /// Parses the given `string` into `bytes`. #[cheatcode(group = String)] function parseBytes(string calldata stringifiedValue) external pure returns (bytes memory parsedValue); /// Parses the given `string` into an `address`. #[cheatcode(group = String)] function parseAddress(string calldata stringifiedValue) external pure returns (address parsedValue); /// Parses the given `string` into a `uint256`. #[cheatcode(group = String)] function parseUint(string calldata stringifiedValue) external pure returns (uint256 parsedValue); /// Parses the given `string` into a `int256`. #[cheatcode(group = String)] function parseInt(string calldata stringifiedValue) external pure returns (int256 parsedValue); /// Parses the given `string` into a `bytes32`. #[cheatcode(group = String)] function parseBytes32(string calldata stringifiedValue) external pure returns (bytes32 parsedValue); /// Parses the given `string` into a `bool`. #[cheatcode(group = String)] function parseBool(string calldata stringifiedValue) external pure returns (bool parsedValue); /// Converts the given `string` value to Lowercase. #[cheatcode(group = String)] function toLowercase(string calldata input) external pure returns (string memory output); /// Converts the given `string` value to Uppercase. #[cheatcode(group = String)] function toUppercase(string calldata input) external pure returns (string memory output); /// Trims leading and trailing whitespace from the given `string` value. #[cheatcode(group = String)] function trim(string calldata input) external pure returns (string memory output); /// Replaces occurrences of `from` in the given `string` with `to`. #[cheatcode(group = String)] function replace(string calldata input, string calldata from, string calldata to) external pure returns (string memory output); /// Splits the given `string` into an array of strings divided by the `delimiter`. #[cheatcode(group = String)] function split(string calldata input, string calldata delimiter) external pure returns (string[] memory outputs); /// Returns the index of the first occurrence of a `key` in an `input` string. /// Returns `NOT_FOUND` (i.e. `type(uint256).max`) if the `key` is not found. /// Returns 0 in case of an empty `key`. #[cheatcode(group = String)] function indexOf(string calldata input, string calldata key) external pure returns (uint256); /// Returns true if `search` is found in `subject`, false otherwise. #[cheatcode(group = String)] function contains(string calldata subject, string calldata search) external pure returns (bool result); // ======== JSON Parsing and Manipulation ======== // -------- Reading -------- // NOTE: Please read https://book.getfoundry.sh/cheatcodes/parse-json to understand the // limitations and caveats of the JSON parsing cheats. /// Checks if `key` exists in a JSON object /// `keyExists` is being deprecated in favor of `keyExistsJson`. It will be removed in future versions. #[cheatcode(group = Json, status = Deprecated(Some("replaced by `keyExistsJson`")))] function keyExists(string calldata json, string calldata key) external view returns (bool); /// Checks if `key` exists in a JSON object. #[cheatcode(group = Json)] function keyExistsJson(string calldata json, string calldata key) external view returns (bool); /// ABI-encodes a JSON object. #[cheatcode(group = Json)] function parseJson(string calldata json) external pure returns (bytes memory abiEncodedData); /// ABI-encodes a JSON object at `key`. #[cheatcode(group = Json)] function parseJson(string calldata json, string calldata key) external pure returns (bytes memory abiEncodedData); // The following parseJson cheatcodes will do type coercion, for the type that they indicate. // For example, parseJsonUint will coerce all values to a uint256. That includes stringified numbers '12.' // and hex numbers '0xEF.'. // Type coercion works ONLY for discrete values or arrays. That means that the key must return a value or array, not // a JSON object. /// Parses a string of JSON data at `key` and coerces it to `uint256`. #[cheatcode(group = Json)] function parseJsonUint(string calldata json, string calldata key) external pure returns (uint256); /// Parses a string of JSON data at `key` and coerces it to `uint256[]`. #[cheatcode(group = Json)] function parseJsonUintArray(string calldata json, string calldata key) external pure returns (uint256[] memory); /// Parses a string of JSON data at `key` and coerces it to `int256`. #[cheatcode(group = Json)] function parseJsonInt(string calldata json, string calldata key) external pure returns (int256); /// Parses a string of JSON data at `key` and coerces it to `int256[]`. #[cheatcode(group = Json)] function parseJsonIntArray(string calldata json, string calldata key) external pure returns (int256[] memory); /// Parses a string of JSON data at `key` and coerces it to `bool`. #[cheatcode(group = Json)] function parseJsonBool(string calldata json, string calldata key) external pure returns (bool); /// Parses a string of JSON data at `key` and coerces it to `bool[]`. #[cheatcode(group = Json)] function parseJsonBoolArray(string calldata json, string calldata key) external pure returns (bool[] memory); /// Parses a string of JSON data at `key` and coerces it to `address`. #[cheatcode(group = Json)] function parseJsonAddress(string calldata json, string calldata key) external pure returns (address); /// Parses a string of JSON data at `key` and coerces it to `address[]`. #[cheatcode(group = Json)] function parseJsonAddressArray(string calldata json, string calldata key) external pure returns (address[] memory); /// Parses a string of JSON data at `key` and coerces it to `string`. #[cheatcode(group = Json)] function parseJsonString(string calldata json, string calldata key) external pure returns (string memory); /// Parses a string of JSON data at `key` and coerces it to `string[]`. #[cheatcode(group = Json)] function parseJsonStringArray(string calldata json, string calldata key) external pure returns (string[] memory); /// Parses a string of JSON data at `key` and coerces it to `bytes`. #[cheatcode(group = Json)] function parseJsonBytes(string calldata json, string calldata key) external pure returns (bytes memory); /// Parses a string of JSON data at `key` and coerces it to `bytes[]`. #[cheatcode(group = Json)] function parseJsonBytesArray(string calldata json, string calldata key) external pure returns (bytes[] memory); /// Parses a string of JSON data at `key` and coerces it to `bytes32`. #[cheatcode(group = Json)] function parseJsonBytes32(string calldata json, string calldata key) external pure returns (bytes32); /// Parses a string of JSON data at `key` and coerces it to `bytes32[]`. #[cheatcode(group = Json)] function parseJsonBytes32Array(string calldata json, string calldata key) external pure returns (bytes32[] memory); /// Parses a string of JSON data and coerces it to type corresponding to `typeDescription`. #[cheatcode(group = Json)] function parseJsonType(string calldata json, string calldata typeDescription) external pure returns (bytes memory); /// Parses a string of JSON data at `key` and coerces it to type corresponding to `typeDescription`. #[cheatcode(group = Json)] function parseJsonType(string calldata json, string calldata key, string calldata typeDescription) external pure returns (bytes memory); /// Parses a string of JSON data at `key` and coerces it to type array corresponding to `typeDescription`. #[cheatcode(group = Json)] function parseJsonTypeArray(string calldata json, string calldata key, string calldata typeDescription) external pure returns (bytes memory); /// Returns an array of all the keys in a JSON object. #[cheatcode(group = Json)] function parseJsonKeys(string calldata json, string calldata key) external pure returns (string[] memory keys); // -------- Writing -------- // NOTE: Please read https://book.getfoundry.sh/cheatcodes/serialize-json to understand how // to use the serialization cheats. /// Serializes a key and value to a JSON object stored in-memory that can be later written to a file. /// Returns the stringified version of the specific JSON file up to that moment. #[cheatcode(group = Json)] function serializeJson(string calldata objectKey, string calldata value) external returns (string memory json); /// See `serializeJson`. #[cheatcode(group = Json)] function serializeBool(string calldata objectKey, string calldata valueKey, bool value) external returns (string memory json); /// See `serializeJson`. #[cheatcode(group = Json)] function serializeUint(string calldata objectKey, string calldata valueKey, uint256 value) external returns (string memory json); /// See `serializeJson`. #[cheatcode(group = Json)] function serializeUintToHex(string calldata objectKey, string calldata valueKey, uint256 value) external returns (string memory json); /// See `serializeJson`. #[cheatcode(group = Json)] function serializeInt(string calldata objectKey, string calldata valueKey, int256 value) external returns (string memory json); /// See `serializeJson`. #[cheatcode(group = Json)] function serializeAddress(string calldata objectKey, string calldata valueKey, address value) external returns (string memory json); /// See `serializeJson`. #[cheatcode(group = Json)] function serializeBytes32(string calldata objectKey, string calldata valueKey, bytes32 value) external returns (string memory json); /// See `serializeJson`. #[cheatcode(group = Json)] function serializeString(string calldata objectKey, string calldata valueKey, string calldata value) external returns (string memory json); /// See `serializeJson`. #[cheatcode(group = Json)] function serializeBytes(string calldata objectKey, string calldata valueKey, bytes calldata value) external returns (string memory json); /// See `serializeJson`. #[cheatcode(group = Json)] function serializeBool(string calldata objectKey, string calldata valueKey, bool[] calldata values) external returns (string memory json); /// See `serializeJson`. #[cheatcode(group = Json)] function serializeUint(string calldata objectKey, string calldata valueKey, uint256[] calldata values) external returns (string memory json); /// See `serializeJson`. #[cheatcode(group = Json)] function serializeInt(string calldata objectKey, string calldata valueKey, int256[] calldata values) external returns (string memory json); /// See `serializeJson`. #[cheatcode(group = Json)] function serializeAddress(string calldata objectKey, string calldata valueKey, address[] calldata values) external returns (string memory json); /// See `serializeJson`. #[cheatcode(group = Json)] function serializeBytes32(string calldata objectKey, string calldata valueKey, bytes32[] calldata values) external returns (string memory json); /// See `serializeJson`. #[cheatcode(group = Json)] function serializeString(string calldata objectKey, string calldata valueKey, string[] calldata values) external returns (string memory json); /// See `serializeJson`. #[cheatcode(group = Json)] function serializeBytes(string calldata objectKey, string calldata valueKey, bytes[] calldata values) external returns (string memory json); /// See `serializeJson`. #[cheatcode(group = Json)] function serializeJsonType(string calldata typeDescription, bytes calldata value) external pure returns (string memory json); /// See `serializeJson`. #[cheatcode(group = Json)] function serializeJsonType(string calldata objectKey, string calldata valueKey, string calldata typeDescription, bytes calldata value) external returns (string memory json); // NOTE: Please read https://book.getfoundry.sh/cheatcodes/write-json to understand how // to use the JSON writing cheats. /// Write a serialized JSON object to a file. If the file exists, it will be overwritten. #[cheatcode(group = Json)] function writeJson(string calldata json, string calldata path) external; /// Write a serialized JSON object to an **existing** JSON file, replacing a value with key = /// This is useful to replace a specific value of a JSON file, without having to parse the entire thing. /// This cheatcode will create new keys if they didn't previously exist. #[cheatcode(group = Json)] function writeJson(string calldata json, string calldata path, string calldata valueKey) external; // ======== TOML Parsing and Manipulation ======== // -------- Reading -------- // NOTE: Please read https://book.getfoundry.sh/cheatcodes/parse-toml to understand the // limitations and caveats of the TOML parsing cheat. /// Checks if `key` exists in a TOML table. #[cheatcode(group = Toml)] function keyExistsToml(string calldata toml, string calldata key) external view returns (bool); /// ABI-encodes a TOML table. #[cheatcode(group = Toml)] function parseToml(string calldata toml) external pure returns (bytes memory abiEncodedData); /// ABI-encodes a TOML table at `key`. #[cheatcode(group = Toml)] function parseToml(string calldata toml, string calldata key) external pure returns (bytes memory abiEncodedData); // The following parseToml cheatcodes will do type coercion, for the type that they indicate. // For example, parseTomlUint will coerce all values to a uint256. That includes stringified numbers '12.' // and hex numbers '0xEF.'. // Type coercion works ONLY for discrete values or arrays. That means that the key must return a value or array, not // a TOML table. /// Parses a string of TOML data at `key` and coerces it to `uint256`. #[cheatcode(group = Toml)] function parseTomlUint(string calldata toml, string calldata key) external pure returns (uint256); /// Parses a string of TOML data at `key` and coerces it to `uint256[]`. #[cheatcode(group = Toml)] function parseTomlUintArray(string calldata toml, string calldata key) external pure returns (uint256[] memory); /// Parses a string of TOML data at `key` and coerces it to `int256`. #[cheatcode(group = Toml)] function parseTomlInt(string calldata toml, string calldata key) external pure returns (int256); /// Parses a string of TOML data at `key` and coerces it to `int256[]`. #[cheatcode(group = Toml)] function parseTomlIntArray(string calldata toml, string calldata key) external pure returns (int256[] memory); /// Parses a string of TOML data at `key` and coerces it to `bool`. #[cheatcode(group = Toml)] function parseTomlBool(string calldata toml, string calldata key) external pure returns (bool); /// Parses a string of TOML data at `key` and coerces it to `bool[]`. #[cheatcode(group = Toml)] function parseTomlBoolArray(string calldata toml, string calldata key) external pure returns (bool[] memory); /// Parses a string of TOML data at `key` and coerces it to `address`. #[cheatcode(group = Toml)] function parseTomlAddress(string calldata toml, string calldata key) external pure returns (address); /// Parses a string of TOML data at `key` and coerces it to `address[]`. #[cheatcode(group = Toml)] function parseTomlAddressArray(string calldata toml, string calldata key) external pure returns (address[] memory); /// Parses a string of TOML data at `key` and coerces it to `string`. #[cheatcode(group = Toml)] function parseTomlString(string calldata toml, string calldata key) external pure returns (string memory); /// Parses a string of TOML data at `key` and coerces it to `string[]`. #[cheatcode(group = Toml)] function parseTomlStringArray(string calldata toml, string calldata key) external pure returns (string[] memory); /// Parses a string of TOML data at `key` and coerces it to `bytes`. #[cheatcode(group = Toml)] function parseTomlBytes(string calldata toml, string calldata key) external pure returns (bytes memory); /// Parses a string of TOML data at `key` and coerces it to `bytes[]`. #[cheatcode(group = Toml)] function parseTomlBytesArray(string calldata toml, string calldata key) external pure returns (bytes[] memory); /// Parses a string of TOML data at `key` and coerces it to `bytes32`. #[cheatcode(group = Toml)] function parseTomlBytes32(string calldata toml, string calldata key) external pure returns (bytes32); /// Parses a string of TOML data at `key` and coerces it to `bytes32[]`. #[cheatcode(group = Toml)] function parseTomlBytes32Array(string calldata toml, string calldata key) external pure returns (bytes32[] memory); /// Parses a string of TOML data and coerces it to type corresponding to `typeDescription`. #[cheatcode(group = Toml)] function parseTomlType(string calldata toml, string calldata typeDescription) external pure returns (bytes memory); /// Parses a string of TOML data at `key` and coerces it to type corresponding to `typeDescription`. #[cheatcode(group = Toml)] function parseTomlType(string calldata toml, string calldata key, string calldata typeDescription) external pure returns (bytes memory); /// Parses a string of TOML data at `key` and coerces it to type array corresponding to `typeDescription`. #[cheatcode(group = Toml)] function parseTomlTypeArray(string calldata toml, string calldata key, string calldata typeDescription) external pure returns (bytes memory); /// Returns an array of all the keys in a TOML table. #[cheatcode(group = Toml)] function parseTomlKeys(string calldata toml, string calldata key) external pure returns (string[] memory keys); // -------- Writing -------- // NOTE: Please read https://book.getfoundry.sh/cheatcodes/write-toml to understand how // to use the TOML writing cheat. /// Takes serialized JSON, converts to TOML and write a serialized TOML to a file. #[cheatcode(group = Toml)] function writeToml(string calldata json, string calldata path) external; /// Takes serialized JSON, converts to TOML and write a serialized TOML table to an **existing** TOML file, replacing a value with key = /// This is useful to replace a specific value of a TOML file, without having to parse the entire thing. /// This cheatcode will create new keys if they didn't previously exist. #[cheatcode(group = Toml)] function writeToml(string calldata json, string calldata path, string calldata valueKey) external; // ======== Cryptography ======== // -------- Key Management -------- /// Derives a private key from the name, labels the account with that name, and returns the wallet. #[cheatcode(group = Crypto)] function createWallet(string calldata walletLabel) external returns (Wallet memory wallet); /// Generates a wallet from the private key and returns the wallet. #[cheatcode(group = Crypto)] function createWallet(uint256 privateKey) external returns (Wallet memory wallet); /// Generates a wallet from the private key, labels the account with that name, and returns the wallet. #[cheatcode(group = Crypto)] function createWallet(uint256 privateKey, string calldata walletLabel) external returns (Wallet memory wallet); /// Signs data with a `Wallet`. #[cheatcode(group = Crypto)] function sign(Wallet calldata wallet, bytes32 digest) external pure returns (uint8 v, bytes32 r, bytes32 s); /// Signs data with a `Wallet`. /// /// Returns a compact signature (`r`, `vs`) as per EIP-2098, where `vs` encodes both the /// signature's `s` value, and the recovery id `v` in a single bytes32. /// This format reduces the signature size from 65 to 64 bytes. #[cheatcode(group = Crypto)] function signCompact(Wallet calldata wallet, bytes32 digest) external pure returns (bytes32 r, bytes32 vs); /// Signs `digest` with `privateKey` using the secp256k1 curve. #[cheatcode(group = Crypto)] function sign(uint256 privateKey, bytes32 digest) external pure returns (uint8 v, bytes32 r, bytes32 s); /// Signs `digest` with `privateKey` on the secp256k1 curve, using the given `nonce` /// as the raw ephemeral k value in ECDSA (instead of deriving it deterministically). #[cheatcode(group = Crypto)] function signWithNonceUnsafe(uint256 privateKey, bytes32 digest, uint256 nonce) external pure returns (uint8 v, bytes32 r, bytes32 s); /// Signs `digest` with `privateKey` using the secp256k1 curve. /// /// Returns a compact signature (`r`, `vs`) as per EIP-2098, where `vs` encodes both the /// signature's `s` value, and the recovery id `v` in a single bytes32. /// This format reduces the signature size from 65 to 64 bytes. #[cheatcode(group = Crypto)] function signCompact(uint256 privateKey, bytes32 digest) external pure returns (bytes32 r, bytes32 vs); /// Signs `digest` with signer provided to script using the secp256k1 curve. /// /// If `--sender` is provided, the signer with provided address is used, otherwise, /// if exactly one signer is provided to the script, that signer is used. /// /// Raises error if signer passed through `--sender` does not match any unlocked signers or /// if `--sender` is not provided and not exactly one signer is passed to the script. #[cheatcode(group = Crypto)] function sign(bytes32 digest) external pure returns (uint8 v, bytes32 r, bytes32 s); /// Signs `digest` with signer provided to script using the secp256k1 curve. /// /// Returns a compact signature (`r`, `vs`) as per EIP-2098, where `vs` encodes both the /// signature's `s` value, and the recovery id `v` in a single bytes32. /// This format reduces the signature size from 65 to 64 bytes. /// /// If `--sender` is provided, the signer with provided address is used, otherwise, /// if exactly one signer is provided to the script, that signer is used. /// /// Raises error if signer passed through `--sender` does not match any unlocked signers or /// if `--sender` is not provided and not exactly one signer is passed to the script. #[cheatcode(group = Crypto)] function signCompact(bytes32 digest) external pure returns (bytes32 r, bytes32 vs); /// Signs `digest` with signer provided to script using the secp256k1 curve. /// /// Raises error if none of the signers passed into the script have provided address. #[cheatcode(group = Crypto)] function sign(address signer, bytes32 digest) external pure returns (uint8 v, bytes32 r, bytes32 s); /// Signs `digest` with signer provided to script using the secp256k1 curve. /// /// Returns a compact signature (`r`, `vs`) as per EIP-2098, where `vs` encodes both the /// signature's `s` value, and the recovery id `v` in a single bytes32. /// This format reduces the signature size from 65 to 64 bytes. /// /// Raises error if none of the signers passed into the script have provided address. #[cheatcode(group = Crypto)] function signCompact(address signer, bytes32 digest) external pure returns (bytes32 r, bytes32 vs); /// Signs `digest` with `privateKey` using the secp256r1 curve. #[cheatcode(group = Crypto)] function signP256(uint256 privateKey, bytes32 digest) external pure returns (bytes32 r, bytes32 s); /// Derives secp256r1 public key from the provided `privateKey`. #[cheatcode(group = Crypto)] function publicKeyP256(uint256 privateKey) external pure returns (uint256 publicKeyX, uint256 publicKeyY); /// Generates an Ed25519 key pair from a deterministic salt. /// Returns (publicKey, privateKey) as 32-byte values. #[cheatcode(group = Crypto, safety = Safe)] function createEd25519Key(bytes32 salt) external pure returns (bytes32 publicKey, bytes32 privateKey); /// Derives the Ed25519 public key from a private key. #[cheatcode(group = Crypto, safety = Safe)] function publicKeyEd25519(bytes32 privateKey) external pure returns (bytes32 publicKey); /// Signs a message with namespace using Ed25519. /// The signature covers namespace || message for domain separation. /// Returns a 64-byte Ed25519 signature. #[cheatcode(group = Crypto, safety = Safe)] function signEd25519(bytes calldata namespace, bytes calldata message, bytes32 privateKey) external pure returns (bytes memory signature); /// Verifies an Ed25519 signature over namespace || message. /// Returns true if signature is valid, false otherwise. #[cheatcode(group = Crypto, safety = Safe)] function verifyEd25519( bytes calldata signature, bytes calldata namespace, bytes calldata message, bytes32 publicKey ) external pure returns (bool valid); /// Derive a private key from a provided mnemonic string (or mnemonic file path) /// at the derivation path `m/44'/60'/0'/0/{index}`. #[cheatcode(group = Crypto)] function deriveKey(string calldata mnemonic, uint32 index) external pure returns (uint256 privateKey); /// Derive a private key from a provided mnemonic string (or mnemonic file path) /// at `{derivationPath}{index}`. #[cheatcode(group = Crypto)] function deriveKey(string calldata mnemonic, string calldata derivationPath, uint32 index) external pure returns (uint256 privateKey); /// Derive a private key from a provided mnemonic string (or mnemonic file path) in the specified language /// at the derivation path `m/44'/60'/0'/0/{index}`. #[cheatcode(group = Crypto)] function deriveKey(string calldata mnemonic, uint32 index, string calldata language) external pure returns (uint256 privateKey); /// Derive a private key from a provided mnemonic string (or mnemonic file path) in the specified language /// at `{derivationPath}{index}`. #[cheatcode(group = Crypto)] function deriveKey(string calldata mnemonic, string calldata derivationPath, uint32 index, string calldata language) external pure returns (uint256 privateKey); /// Adds a private key to the local forge wallet and returns the address. #[cheatcode(group = Crypto)] function rememberKey(uint256 privateKey) external returns (address keyAddr); /// Derive a set number of wallets from a mnemonic at the derivation path `m/44'/60'/0'/0/{0..count}`. /// /// The respective private keys are saved to the local forge wallet for later use and their addresses are returned. #[cheatcode(group = Crypto)] function rememberKeys(string calldata mnemonic, string calldata derivationPath, uint32 count) external returns (address[] memory keyAddrs); /// Derive a set number of wallets from a mnemonic in the specified language at the derivation path `m/44'/60'/0'/0/{0..count}`. /// /// The respective private keys are saved to the local forge wallet for later use and their addresses are returned. #[cheatcode(group = Crypto)] function rememberKeys(string calldata mnemonic, string calldata derivationPath, string calldata language, uint32 count) external returns (address[] memory keyAddrs); // -------- Uncategorized Utilities -------- /// Labels an address in call traces. #[cheatcode(group = Utilities)] function label(address account, string calldata newLabel) external; /// Gets the label for the specified address. #[cheatcode(group = Utilities)] function getLabel(address account) external view returns (string memory currentLabel); /// Compute the address a contract will be deployed at for a given deployer address and nonce. #[cheatcode(group = Utilities)] function computeCreateAddress(address deployer, uint256 nonce) external pure returns (address); /// Compute the address of a contract created with CREATE2 using the given CREATE2 deployer. #[cheatcode(group = Utilities)] function computeCreate2Address(bytes32 salt, bytes32 initCodeHash, address deployer) external pure returns (address); /// Compute the address of a contract created with CREATE2 using the default CREATE2 deployer. #[cheatcode(group = Utilities)] function computeCreate2Address(bytes32 salt, bytes32 initCodeHash) external pure returns (address); /// Encodes a `bytes` value to a base64 string. #[cheatcode(group = Utilities)] function toBase64(bytes calldata data) external pure returns (string memory); /// Encodes a `string` value to a base64 string. #[cheatcode(group = Utilities)] function toBase64(string calldata data) external pure returns (string memory); /// Encodes a `bytes` value to a base64url string. #[cheatcode(group = Utilities)] function toBase64URL(bytes calldata data) external pure returns (string memory); /// Encodes a `string` value to a base64url string. #[cheatcode(group = Utilities)] function toBase64URL(string calldata data) external pure returns (string memory); /// Returns ENS namehash for provided string. #[cheatcode(group = Utilities)] function ensNamehash(string calldata name) external pure returns (bytes32); /// Returns an uint256 value bounded in given range and different from the current one. #[cheatcode(group = Utilities)] function bound(uint256 current, uint256 min, uint256 max) external view returns (uint256); /// Returns a random uint256 value. #[cheatcode(group = Utilities)] function randomUint() external view returns (uint256); /// Returns random uint256 value between the provided range (=min..=max). #[cheatcode(group = Utilities)] function randomUint(uint256 min, uint256 max) external view returns (uint256); /// Returns a random `uint256` value of given bits. #[cheatcode(group = Utilities)] function randomUint(uint256 bits) external view returns (uint256); /// Returns a random `address`. #[cheatcode(group = Utilities)] function randomAddress() external view returns (address); /// Returns an int256 value bounded in given range and different from the current one. #[cheatcode(group = Utilities)] function bound(int256 current, int256 min, int256 max) external view returns (int256); /// Returns a random `int256` value. #[cheatcode(group = Utilities)] function randomInt() external view returns (int256); /// Returns a random `int256` value of given bits. #[cheatcode(group = Utilities)] function randomInt(uint256 bits) external view returns (int256); /// Returns a random `bool`. #[cheatcode(group = Utilities)] function randomBool() external view returns (bool); /// Returns a random byte array value of the given length. #[cheatcode(group = Utilities)] function randomBytes(uint256 len) external view returns (bytes memory); /// Returns a random fixed-size byte array of length 4. #[cheatcode(group = Utilities)] function randomBytes4() external view returns (bytes4); /// Returns a random fixed-size byte array of length 8. #[cheatcode(group = Utilities)] function randomBytes8() external view returns (bytes8); /// Pauses collection of call traces. Useful in cases when you want to skip tracing of /// complex calls which are not useful for debugging. #[cheatcode(group = Utilities)] function pauseTracing() external view; /// Unpauses collection of call traces. #[cheatcode(group = Utilities)] function resumeTracing() external view; /// Utility cheatcode to copy storage of `from` contract to another `to` contract. #[cheatcode(group = Utilities)] function copyStorage(address from, address to) external; /// Utility cheatcode to set arbitrary storage for given target address. #[cheatcode(group = Utilities)] function setArbitraryStorage(address target) external; /// Utility cheatcode to set arbitrary storage for given target address and overwrite /// any storage slots that have been previously set. #[cheatcode(group = Utilities)] function setArbitraryStorage(address target, bool overwrite) external; /// Sorts an array in ascending order. #[cheatcode(group = Utilities)] function sort(uint256[] calldata array) external returns (uint256[] memory); /// Randomly shuffles an array. #[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: /// vm.interceptInitcode(); /// bytes memory initcode; /// try new MyContract(param1, param2) { assert(false); } /// 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); /// RLP encodes a list of bytes into an RLP payload. #[cheatcode(group = Utilities)] function toRlp(bytes[] calldata data) external pure returns (bytes memory); /// RLP decodes an RLP payload into a list of bytes. #[cheatcode(group = Utilities)] function fromRlp(bytes calldata rlp) external pure returns (bytes[] memory data); } } impl PartialEq for ForgeContext { // Handles test group case (any of test, coverage or snapshot) // and script group case (any of dry run, broadcast or resume). fn eq(&self, other: &Self) -> bool { match (self, other) { (_, Self::TestGroup) => { matches!(self, Self::Test | Self::Snapshot | Self::Coverage) } (_, Self::ScriptGroup) => { matches!(self, Self::ScriptDryRun | Self::ScriptBroadcast | Self::ScriptResume) } (Self::Test, Self::Test) | (Self::Snapshot, Self::Snapshot) | (Self::Coverage, Self::Coverage) | (Self::ScriptDryRun, Self::ScriptDryRun) | (Self::ScriptBroadcast, Self::ScriptBroadcast) | (Self::ScriptResume, Self::ScriptResume) | (Self::Unknown, Self::Unknown) => true, _ => false, } } } impl fmt::Display for Vm::CheatcodeError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { self.message.fmt(f) } } impl fmt::Display for Vm::VmErrors { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::CheatcodeError(err) => err.fmt(f), } } } #[track_caller] const fn panic_unknown_safety() -> ! { panic!("cannot determine safety from the group, add a `#[cheatcode(safety = ...)]` attribute") } ================================================ FILE: crates/cheatcodes/src/base64.rs ================================================ use crate::{Cheatcode, Cheatcodes, Result, Vm::*}; use alloy_sol_types::SolValue; use base64::prelude::*; impl Cheatcode for toBase64_0Call { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { data } = self; Ok(BASE64_STANDARD.encode(data).abi_encode()) } } impl Cheatcode for toBase64_1Call { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { data } = self; Ok(BASE64_STANDARD.encode(data).abi_encode()) } } impl Cheatcode for toBase64URL_0Call { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { data } = self; Ok(BASE64_URL_SAFE.encode(data).abi_encode()) } } impl Cheatcode for toBase64URL_1Call { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { data } = self; Ok(BASE64_URL_SAFE.encode(data).abi_encode()) } } ================================================ FILE: crates/cheatcodes/src/config.rs ================================================ use super::Result; use crate::Vm::Rpc; use alloy_primitives::{U256, map::AddressHashMap}; use foundry_common::{ContractsByArtifact, fs::normalize_path}; use foundry_compilers::{ArtifactId, ProjectPathsConfig, utils::canonicalize}; use foundry_config::{ Config, FsPermissions, ResolvedRpcEndpoint, ResolvedRpcEndpoints, RpcEndpoint, RpcEndpointUrl, cache::StorageCachingConfig, fs_permissions::FsAccessKind, }; use foundry_evm_core::opts::EvmOpts; use std::{ path::{Path, PathBuf}, time::Duration, }; /// Additional, configurable context the `Cheatcodes` inspector has access to /// /// This is essentially a subset of various `Config` settings `Cheatcodes` needs to know. #[derive(Clone, Debug)] pub struct CheatsConfig { /// Whether the FFI cheatcode is enabled. pub ffi: bool, /// Use the create 2 factory in all cases including tests and non-broadcasting scripts. pub always_use_create_2_factory: bool, /// Sets a timeout for vm.prompt cheatcodes pub prompt_timeout: Duration, /// RPC storage caching settings determines what chains and endpoints to cache pub rpc_storage_caching: StorageCachingConfig, /// Disables storage caching entirely. pub no_storage_caching: bool, /// All known endpoints and their aliases 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 pub root: PathBuf, /// Absolute Path to broadcast dir i.e project_root/broadcast pub broadcast: PathBuf, /// How the evm was configured by the user pub evm_opts: EvmOpts, /// Address labels from config pub labels: AddressHashMap, /// Artifacts which are guaranteed to be fresh (either recompiled or cached). /// If Some, `vm.getDeployedCode` invocations are validated to be in scope of this list. /// If None, no validation is performed. pub available_artifacts: Option, /// Currently running artifact. pub running_artifact: Option, /// Whether to enable legacy (non-reverting) assertions. pub assertions_revert: bool, /// Optional seed for the RNG algorithm. pub seed: Option, /// Whether to allow `expectRevert` to work for internal calls. pub internal_expect_revert: bool, } impl CheatsConfig { /// Extracts the necessary settings from the Config pub fn new( config: &Config, evm_opts: EvmOpts, available_artifacts: Option, running_artifact: Option, ) -> Self { let rpc_endpoints = config.rpc_endpoints.clone().resolved(); trace!(?rpc_endpoints, "using resolved rpc endpoints"); // If user explicitly disabled safety checks, do not set available_artifacts let available_artifacts = if config.unchecked_cheatcode_artifacts { None } else { available_artifacts }; Self { ffi: evm_opts.ffi, always_use_create_2_factory: evm_opts.always_use_create_2_factory, prompt_timeout: Duration::from_secs(config.prompt_timeout), rpc_storage_caching: config.rpc_storage_caching.clone(), 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), evm_opts, labels: config.labels.clone(), available_artifacts, running_artifact, assertions_revert: config.assertions_revert, seed: config.fuzz.seed, internal_expect_revert: config.allow_internal_expect_revert, } } /// Returns a new `CheatsConfig` configured with the given `Config` and `EvmOpts`. pub fn clone_with(&self, config: &Config, evm_opts: EvmOpts) -> Self { Self::new(config, evm_opts, self.available_artifacts.clone(), self.running_artifact.clone()) } /// Attempts to canonicalize (see [std::fs::canonicalize]) the path. /// /// Canonicalization fails for non-existing paths, in which case we just normalize the path. pub fn normalized_path(&self, path: impl AsRef) -> PathBuf { let path = self.root.join(path); canonicalize(&path).unwrap_or_else(|_| normalize_path(&path)) } /// Returns true if the given path is allowed, if any path `allowed_paths` is an ancestor of the /// path /// /// We only allow paths that are inside allowed paths. To prevent path traversal /// ("../../etc/passwd") we canonicalize/normalize the path first. We always join with the /// configured root directory. pub fn is_path_allowed(&self, path: impl AsRef, kind: FsAccessKind) -> bool { self.is_normalized_path_allowed(&self.normalized_path(path), kind) } fn is_normalized_path_allowed(&self, path: &Path, kind: FsAccessKind) -> bool { self.fs_permissions.is_path_allowed(path, kind) } /// Returns an error if no access is granted to access `path`, See also [Self::is_path_allowed] /// /// Returns the normalized version of `path`, see [`CheatsConfig::normalized_path`] pub fn ensure_path_allowed( &self, path: impl AsRef, kind: FsAccessKind, ) -> Result { let path = path.as_ref(); let normalized = self.normalized_path(path); ensure!( self.is_normalized_path_allowed(&normalized, kind), "the path {} is not allowed to be accessed for {kind} operations", normalized.strip_prefix(&self.root).unwrap_or(path).display() ); Ok(normalized) } /// Returns true if the given `path` is the project's foundry.toml file /// /// Note: this should be called with normalized path pub fn is_foundry_toml(&self, path: impl AsRef) -> bool { // path methods that do not access the filesystem are such as [`Path::starts_with`], are // case-sensitive no matter the platform or filesystem. to make this case-sensitive // we convert the underlying `OssStr` to lowercase checking that `path` and // `foundry.toml` are the same file by comparing the FD, because it may not exist let foundry_toml = self.root.join(Config::FILE_NAME); Path::new(&foundry_toml.to_string_lossy().to_lowercase()) .starts_with(Path::new(&path.as_ref().to_string_lossy().to_lowercase())) } /// Same as [`Self::is_foundry_toml`] but returns an `Err` if [`Self::is_foundry_toml`] returns /// true pub fn ensure_not_foundry_toml(&self, path: impl AsRef) -> Result<()> { ensure!(!self.is_foundry_toml(path), "access to `foundry.toml` is not allowed"); Ok(()) } /// Returns the RPC to use /// /// If `url_or_alias` is a known alias in the `ResolvedRpcEndpoints` then it returns the /// corresponding URL of that alias. otherwise this assumes `url_or_alias` is itself a URL /// if it starts with a `http` or `ws` scheme. /// /// If the url is a path to an existing file, it is also considered a valid RPC URL, IPC path. /// /// # Errors /// /// - Returns an error if `url_or_alias` is a known alias but references an unresolved env var. /// - Returns an error if `url_or_alias` is not an alias but does not start with a `http` or /// `ws` `scheme` and is not a path to an existing file pub fn rpc_endpoint(&self, url_or_alias: &str) -> Result { if let Some(endpoint) = self.rpc_endpoints.get(url_or_alias) { Ok(endpoint.clone().try_resolve()) } else { // check if it's a URL or a path to an existing file to an ipc socket if url_or_alias.starts_with("http") || url_or_alias.starts_with("ws") || // check for existing ipc file Path::new(url_or_alias).exists() { let url = RpcEndpointUrl::Env(url_or_alias.to_string()); Ok(RpcEndpoint::new(url).resolve()) } else { Err(fmt_err!("invalid rpc url: {url_or_alias}")) } } } /// Returns all the RPC urls and their alias. pub fn rpc_urls(&self) -> Result> { let mut urls = Vec::with_capacity(self.rpc_endpoints.len()); for alias in self.rpc_endpoints.keys() { let url = self.rpc_endpoint(alias)?.url()?; urls.push(Rpc { key: alias.clone(), url }); } Ok(urls) } } impl Default for CheatsConfig { fn default() -> Self { Self { ffi: false, always_use_create_2_factory: false, prompt_timeout: Duration::from_secs(120), rpc_storage_caching: Default::default(), no_storage_caching: false, rpc_endpoints: Default::default(), 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(), evm_opts: Default::default(), labels: Default::default(), available_artifacts: Default::default(), running_artifact: Default::default(), assertions_revert: true, seed: None, internal_expect_revert: false, } } } #[cfg(test)] mod tests { use super::*; use foundry_config::fs_permissions::PathPermission; fn config(root: &str, fs_permissions: FsPermissions) -> CheatsConfig { CheatsConfig::new( &Config { root: root.into(), fs_permissions, ..Default::default() }, Default::default(), None, None, ) } #[test] fn test_allowed_paths() { let root = "/my/project/root/"; let config = config(root, FsPermissions::new(vec![PathPermission::read_write("./")])); assert!(config.ensure_path_allowed("./t.txt", FsAccessKind::Read).is_ok()); assert!(config.ensure_path_allowed("./t.txt", FsAccessKind::Write).is_ok()); assert!(config.ensure_path_allowed("../root/t.txt", FsAccessKind::Read).is_ok()); assert!(config.ensure_path_allowed("../root/t.txt", FsAccessKind::Write).is_ok()); assert!(config.ensure_path_allowed("../../root/t.txt", FsAccessKind::Read).is_err()); assert!(config.ensure_path_allowed("../../root/t.txt", FsAccessKind::Write).is_err()); } #[test] fn test_is_foundry_toml() { let root = "/my/project/root/"; let config = config(root, FsPermissions::new(vec![PathPermission::read_write("./")])); let f = format!("{root}foundry.toml"); assert!(config.is_foundry_toml(f)); let f = format!("{root}Foundry.toml"); assert!(config.is_foundry_toml(f)); let f = format!("{root}lib/other/foundry.toml"); assert!(!config.is_foundry_toml(f)); } } ================================================ FILE: crates/cheatcodes/src/crypto.rs ================================================ //! Implementations of [`Crypto`](spec::Group::Crypto) Cheatcodes. use crate::{Cheatcode, Cheatcodes, Result, Vm::*}; use alloy_primitives::{Address, B256, U256, keccak256}; use alloy_signer::{Signer, SignerSync}; use alloy_signer_local::{ LocalSigner, MnemonicBuilder, PrivateKeySigner, coins_bip39::{ ChineseSimplified, ChineseTraditional, Czech, English, French, Italian, Japanese, Korean, Portuguese, Spanish, Wordlist, }, }; use alloy_sol_types::SolValue; use k256::{ FieldBytes, Scalar, ecdsa::{SigningKey, hazmat}, elliptic_curve::{bigint::ArrayEncoding, sec1::ToEncodedPoint}, }; use p256::ecdsa::{ Signature as P256Signature, SigningKey as P256SigningKey, signature::hazmat::PrehashSigner, }; use ed25519_consensus::{ Signature as Ed25519Signature, SigningKey as Ed25519SigningKey, VerificationKey as Ed25519VerificationKey, }; /// The BIP32 default derivation path prefix. const DEFAULT_DERIVATION_PATH_PREFIX: &str = "m/44'/60'/0'/0/"; impl Cheatcode for createWallet_0Call { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { walletLabel } = self; create_wallet(&U256::from_be_bytes(keccak256(walletLabel).0), Some(walletLabel), state) } } impl Cheatcode for createWallet_1Call { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { privateKey } = self; create_wallet(privateKey, None, state) } } impl Cheatcode for createWallet_2Call { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { privateKey, walletLabel } = self; create_wallet(privateKey, Some(walletLabel), state) } } impl Cheatcode for sign_0Call { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { wallet, digest } = self; let sig = sign(&wallet.privateKey, digest)?; Ok(encode_full_sig(sig)) } } impl Cheatcode for signWithNonceUnsafeCall { fn apply(&self, _state: &mut Cheatcodes) -> Result { let pk: U256 = self.privateKey; let digest: B256 = self.digest; let nonce: U256 = self.nonce; let sig: alloy_primitives::Signature = sign_with_nonce(&pk, &digest, &nonce)?; Ok(encode_full_sig(sig)) } } impl Cheatcode for signCompact_0Call { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { wallet, digest } = self; let sig = sign(&wallet.privateKey, digest)?; Ok(encode_compact_sig(sig)) } } impl Cheatcode for deriveKey_0Call { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { mnemonic, index } = self; derive_key::(mnemonic, DEFAULT_DERIVATION_PATH_PREFIX, *index) } } impl Cheatcode for deriveKey_1Call { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { mnemonic, derivationPath, index } = self; derive_key::(mnemonic, derivationPath, *index) } } impl Cheatcode for deriveKey_2Call { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { mnemonic, index, language } = self; derive_key_str(mnemonic, DEFAULT_DERIVATION_PATH_PREFIX, *index, language) } } impl Cheatcode for deriveKey_3Call { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { mnemonic, derivationPath, index, language } = self; derive_key_str(mnemonic, derivationPath, *index, language) } } impl Cheatcode for rememberKeyCall { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { privateKey } = self; let wallet = parse_wallet(privateKey)?; let address = inject_wallet(state, wallet); Ok(address.abi_encode()) } } impl Cheatcode for rememberKeys_0Call { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { mnemonic, derivationPath, count } = self; let wallets = derive_wallets::(mnemonic, derivationPath, *count)?; let mut addresses = Vec::
::with_capacity(wallets.len()); for wallet in wallets { let addr = inject_wallet(state, wallet); addresses.push(addr); } Ok(addresses.abi_encode()) } } impl Cheatcode for rememberKeys_1Call { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { mnemonic, derivationPath, language, count } = self; let wallets = derive_wallets_str(mnemonic, derivationPath, language, *count)?; let mut addresses = Vec::
::with_capacity(wallets.len()); for wallet in wallets { let addr = inject_wallet(state, wallet); addresses.push(addr); } Ok(addresses.abi_encode()) } } fn inject_wallet(state: &mut Cheatcodes, wallet: LocalSigner) -> Address { let address = wallet.address(); state.wallets().add_local_signer(wallet); address } impl Cheatcode for sign_1Call { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { privateKey, digest } = self; let sig = sign(privateKey, digest)?; Ok(encode_full_sig(sig)) } } impl Cheatcode for signCompact_1Call { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { privateKey, digest } = self; let sig = sign(privateKey, digest)?; Ok(encode_compact_sig(sig)) } } impl Cheatcode for sign_2Call { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { digest } = self; let sig = sign_with_wallet(state, None, digest)?; Ok(encode_full_sig(sig)) } } impl Cheatcode for signCompact_2Call { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { digest } = self; let sig = sign_with_wallet(state, None, digest)?; Ok(encode_compact_sig(sig)) } } impl Cheatcode for sign_3Call { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { signer, digest } = self; let sig = sign_with_wallet(state, Some(*signer), digest)?; Ok(encode_full_sig(sig)) } } impl Cheatcode for signCompact_3Call { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { signer, digest } = self; let sig = sign_with_wallet(state, Some(*signer), digest)?; Ok(encode_compact_sig(sig)) } } impl Cheatcode for signP256Call { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { privateKey, digest } = self; sign_p256(privateKey, digest) } } impl Cheatcode for publicKeyP256Call { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { privateKey } = self; let pub_key = parse_private_key_p256(privateKey)?.verifying_key().as_affine().to_encoded_point(false); let pub_key_x = U256::from_be_bytes((*pub_key.x().unwrap()).into()); let pub_key_y = U256::from_be_bytes((*pub_key.y().unwrap()).into()); Ok((pub_key_x, pub_key_y).abi_encode()) } } impl Cheatcode for createEd25519KeyCall { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { salt } = self; create_ed25519_key(salt) } } impl Cheatcode for publicKeyEd25519Call { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { privateKey } = self; public_key_ed25519(privateKey) } } impl Cheatcode for signEd25519Call { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { namespace, message, privateKey } = self; sign_ed25519(namespace, message, privateKey) } } impl Cheatcode for verifyEd25519Call { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { signature, namespace, message, publicKey } = self; verify_ed25519(signature, namespace, message, publicKey) } } /// Using a given private key, return its public ETH address, its public key affine x and y /// coordinates, and its private key (see the 'Wallet' struct) /// /// If 'label' is set to 'Some()', assign that label to the associated ETH address in state fn create_wallet(private_key: &U256, label: Option<&str>, state: &mut Cheatcodes) -> Result { let key = parse_private_key(private_key)?; let addr = alloy_signer::utils::secret_key_to_address(&key); let pub_key = key.verifying_key().as_affine().to_encoded_point(false); let pub_key_x = U256::from_be_bytes((*pub_key.x().unwrap()).into()); let pub_key_y = U256::from_be_bytes((*pub_key.y().unwrap()).into()); if let Some(label) = label { state.labels.insert(addr, label.into()); } Ok(Wallet { addr, publicKeyX: pub_key_x, publicKeyY: pub_key_y, privateKey: *private_key } .abi_encode()) } 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()); let s = B256::from(sig.s()); (v, r, s).abi_encode() } 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(); vs.set_bit(255, sig.v()); (r, vs).abi_encode() } 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)?; debug_assert_eq!(sig.recover_address_from_prehash(digest)?, wallet.address()); Ok(sig) } /// Signs `digest` on secp256k1 using a user-supplied ephemeral nonce `k` (no RFC6979). /// - `private_key` and `nonce` must be in (0, n) /// - `digest` is a 32-byte prehash. /// /// # Warning /// /// Use [`sign_with_nonce`] with extreme caution! /// Reusing the same nonce (`k`) with the same private key in ECDSA will leak the private key. /// Always generate `nonce` with a cryptographically secure RNG, and never reuse it across /// signatures. fn sign_with_nonce( private_key: &U256, digest: &B256, nonce: &U256, ) -> Result { let d_scalar: Scalar = ::from_repr(private_key.to_be_bytes().into()) .into_option() .ok_or_else(|| fmt_err!("invalid private key scalar"))?; if bool::from(d_scalar.is_zero()) { return Err(fmt_err!("private key cannot be 0")); } let k_scalar: Scalar = ::from_repr(nonce.to_be_bytes().into()) .into_option() .ok_or_else(|| fmt_err!("invalid nonce scalar"))?; if bool::from(k_scalar.is_zero()) { return Err(fmt_err!("nonce cannot be 0")); } let mut z = [0u8; 32]; z.copy_from_slice(digest.as_slice()); let z_fb: FieldBytes = FieldBytes::from(z); // Hazmat signing using the scalar `d` (SignPrimitive is implemented for `Scalar`) // Note: returns (Signature, Option) let (sig_raw, recid_opt) = >::try_sign_prehashed( &d_scalar, k_scalar, &z_fb, ) .map_err(|e| fmt_err!("sign_prehashed failed: {e}"))?; // Enforce low-s; if mirrored, parity flips (we’ll account for it below if we use recid) let (sig_low, flipped) = if let Some(norm) = sig_raw.normalize_s() { (norm, true) } else { (sig_raw, false) }; let r_u256 = U256::from_be_bytes(sig_low.r().to_bytes().into()); let s_u256 = U256::from_be_bytes(sig_low.s().to_bytes().into()); // Determine v parity in {0,1} let v_parity = if let Some(id) = recid_opt { let mut v = id.to_byte() & 1; if flipped { v ^= 1; } v } else { // Fallback: choose parity by recovery to expected address let expected_addr = { let sk: SigningKey = parse_private_key(private_key)?; alloy_signer::utils::secret_key_to_address(&sk) }; // Try v = 0 let cand0 = alloy_primitives::Signature::new(r_u256, s_u256, false); if cand0.recover_address_from_prehash(digest).ok() == Some(expected_addr) { return Ok(cand0); } // Try v = 1 let cand1 = alloy_primitives::Signature::new(r_u256, s_u256, true); if cand1.recover_address_from_prehash(digest).ok() == Some(expected_addr) { return Ok(cand1); } return Err(fmt_err!("failed to determine recovery id for signature")); }; let y_parity = v_parity != 0; Ok(alloy_primitives::Signature::new(r_u256, s_u256, y_parity)) } fn sign_with_wallet( state: &mut Cheatcodes, signer: Option
, digest: &B256, ) -> Result { if state.wallets().is_empty() { bail!("no wallets available"); } let mut wallets = state.wallets().inner.lock(); let maybe_provided_sender = wallets.provided_sender; let signers = wallets.multi_wallet.signers()?; let signer = if let Some(signer) = signer { signer } else if let Some(provided_sender) = maybe_provided_sender { provided_sender } else if signers.len() == 1 { *signers.keys().next().unwrap() } else { bail!( "could not determine signer, there are multiple signers available use vm.sign(signer, digest) to specify one" ); }; let wallet = signers .get(&signer) .ok_or_else(|| fmt_err!("signer with address {signer} is not available"))?; let sig = foundry_common::block_on(wallet.sign_hash(digest))?; debug_assert_eq!(sig.recover_address_from_prehash(digest)?, signer); Ok(sig) } fn sign_p256(private_key: &U256, digest: &B256) -> Result { let signing_key = parse_private_key_p256(private_key)?; let signature: P256Signature = signing_key.sign_prehash(digest.as_slice())?; let signature = signature.normalize_s().unwrap_or(signature); let r_bytes: [u8; 32] = signature.r().to_bytes().into(); let s_bytes: [u8; 32] = signature.s().to_bytes().into(); Ok((r_bytes, s_bytes).abi_encode()) } fn validate_private_key(private_key: &U256) -> Result<()> { ensure!(*private_key != U256::ZERO, "private key cannot be 0"); let order = U256::from_be_slice(&C::ORDER.to_be_byte_array()); ensure!( *private_key < order, "private key must be less than the {curve:?} curve order ({order})", curve = C::default(), ); Ok(()) } fn parse_private_key(private_key: &U256) -> Result { validate_private_key::(private_key)?; Ok(SigningKey::from_bytes((&private_key.to_be_bytes()).into())?) } fn parse_private_key_p256(private_key: &U256) -> Result { validate_private_key::(private_key)?; Ok(P256SigningKey::from_bytes((&private_key.to_be_bytes()).into())?) } fn parse_signing_key_ed25519(private_key: &B256) -> Result { Ed25519SigningKey::try_from(private_key.as_slice()) .map_err(|e| fmt_err!("invalid Ed25519 private key: {e}")) } fn create_ed25519_key(salt: &B256) -> Result { let signing_key = parse_signing_key_ed25519(salt)?; let public_key = B256::from_slice(signing_key.verification_key().as_ref()); Ok((public_key, *salt).abi_encode()) } fn public_key_ed25519(private_key: &B256) -> Result { let signing_key = parse_signing_key_ed25519(private_key)?; Ok(B256::from_slice(signing_key.verification_key().as_ref()).abi_encode()) } fn sign_ed25519(namespace: &[u8], message: &[u8], private_key: &B256) -> Result { let signing_key = parse_signing_key_ed25519(private_key)?; let combined = [namespace, message].concat(); let signature: [u8; 64] = signing_key.sign(&combined).into(); Ok(signature.to_vec().abi_encode()) } fn verify_ed25519(signature: &[u8], namespace: &[u8], message: &[u8], public_key: &B256) -> Result { if signature.len() != 64 { return Ok(false.abi_encode()); } let Ok(verification_key) = Ed25519VerificationKey::try_from(public_key.as_slice()) else { return Ok(false.abi_encode()); }; let Ok(sig_bytes): Result<[u8; 64], _> = signature.try_into() else { return Ok(false.abi_encode()); }; let combined = [namespace, message].concat(); let valid = verification_key.verify(&Ed25519Signature::from(sig_bytes), &combined).is_ok(); Ok(valid.abi_encode()) } pub(super) fn parse_wallet(private_key: &U256) -> Result { parse_private_key(private_key).map(PrivateKeySigner::from) } fn derive_key_str(mnemonic: &str, path: &str, index: u32, language: &str) -> Result { match language { "chinese_simplified" => derive_key::(mnemonic, path, index), "chinese_traditional" => derive_key::(mnemonic, path, index), "czech" => derive_key::(mnemonic, path, index), "english" => derive_key::(mnemonic, path, index), "french" => derive_key::(mnemonic, path, index), "italian" => derive_key::(mnemonic, path, index), "japanese" => derive_key::(mnemonic, path, index), "korean" => derive_key::(mnemonic, path, index), "portuguese" => derive_key::(mnemonic, path, index), "spanish" => derive_key::(mnemonic, path, index), _ => Err(fmt_err!("unsupported mnemonic language: {language:?}")), } } fn derive_key(mnemonic: &str, path: &str, index: u32) -> Result { fn derive_key_path(path: &str, index: u32) -> String { let mut out = path.to_string(); if !out.ends_with('/') { out.push('/'); } out.push_str(&index.to_string()); out } let wallet = MnemonicBuilder::::default() .phrase(mnemonic) .derivation_path(derive_key_path(path, index))? .build()?; let private_key = U256::from_be_bytes(wallet.credential().to_bytes().into()); Ok(private_key.abi_encode()) } fn derive_wallets_str( mnemonic: &str, path: &str, language: &str, count: u32, ) -> Result>> { match language { "chinese_simplified" => derive_wallets::(mnemonic, path, count), "chinese_traditional" => derive_wallets::(mnemonic, path, count), "czech" => derive_wallets::(mnemonic, path, count), "english" => derive_wallets::(mnemonic, path, count), "french" => derive_wallets::(mnemonic, path, count), "italian" => derive_wallets::(mnemonic, path, count), "japanese" => derive_wallets::(mnemonic, path, count), "korean" => derive_wallets::(mnemonic, path, count), "portuguese" => derive_wallets::(mnemonic, path, count), "spanish" => derive_wallets::(mnemonic, path, count), _ => Err(fmt_err!("unsupported mnemonic language: {language:?}")), } } fn derive_wallets( mnemonic: &str, path: &str, count: u32, ) -> Result>> { let mut out = path.to_string(); if !out.ends_with('/') { out.push('/'); } let mut wallets = Vec::with_capacity(count as usize); for idx in 0..count { let wallet = MnemonicBuilder::::default() .phrase(mnemonic) .derivation_path(format!("{out}{idx}"))? .build()?; wallets.push(wallet); } Ok(wallets) } #[cfg(test)] mod tests { use super::*; use alloy_primitives::{FixedBytes, hex::FromHex}; use k256::elliptic_curve::Curve; use p256::ecdsa::signature::hazmat::PrehashVerifier; #[test] fn test_sign_p256() { use p256::ecdsa::VerifyingKey; let pk_u256: U256 = "1".parse().unwrap(); let signing_key = P256SigningKey::from_bytes(&pk_u256.to_be_bytes().into()).unwrap(); let digest = FixedBytes::from_hex( "0x44acf6b7e36c1342c2c5897204fe09504e1e2efb1a900377dbc4e7a6a133ec56", ) .unwrap(); let result = sign_p256(&pk_u256, &digest).unwrap(); let result_bytes: [u8; 64] = result.try_into().unwrap(); let signature = P256Signature::from_bytes(&result_bytes.into()).unwrap(); let verifying_key = VerifyingKey::from(&signing_key); assert!(verifying_key.verify_prehash(digest.as_slice(), &signature).is_ok()); } #[test] fn test_sign_p256_pk_too_large() { // max n from https://neuromancer.sk/std/secg/secp256r1 let pk = "0xffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551".parse().unwrap(); let digest = FixedBytes::from_hex( "0x54705ba3baafdbdfba8c5f9a70f7a89bee98d906b53e31074da7baecdc0da9ad", ) .unwrap(); let result = sign_p256(&pk, &digest); assert_eq!( result.err().unwrap().to_string(), "private key must be less than the NistP256 curve order (115792089210356248762697446949407573529996955224135760342422259061068512044369)" ); } #[test] fn test_sign_p256_pk_0() { let digest = FixedBytes::from_hex( "0x54705ba3baafdbdfba8c5f9a70f7a89bee98d906b53e31074da7baecdc0da9ad", ) .unwrap(); let result = sign_p256(&U256::ZERO, &digest); assert_eq!(result.err().unwrap().to_string(), "private key cannot be 0"); } #[test] fn test_sign_with_nonce_varies_and_recovers() { // Given a fixed private key and digest let pk_u256: U256 = U256::from(1u64); let digest = FixedBytes::from_hex( "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", ) .unwrap(); // Two distinct nonces let n1: U256 = U256::from(123u64); let n2: U256 = U256::from(456u64); // Sign with both nonces let sig1 = sign_with_nonce(&pk_u256, &digest, &n1).expect("sig1"); let sig2 = sign_with_nonce(&pk_u256, &digest, &n2).expect("sig2"); // (r,s) must differ when nonce differs assert!( sig1.r() != sig2.r() || sig1.s() != sig2.s(), "signatures should differ with different nonces" ); // ecrecover must yield the address for both signatures let sk = parse_private_key(&pk_u256).unwrap(); let expected = alloy_signer::utils::secret_key_to_address(&sk); assert_eq!(sig1.recover_address_from_prehash(&digest).unwrap(), expected); assert_eq!(sig2.recover_address_from_prehash(&digest).unwrap(), expected); } #[test] fn test_sign_with_nonce_zero_nonce_errors() { // nonce = 0 should be rejected let pk_u256: U256 = U256::from(1u64); let digest = FixedBytes::from_hex( "0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", ) .unwrap(); let n0: U256 = U256::ZERO; let err = sign_with_nonce(&pk_u256, &digest, &n0).unwrap_err(); let msg = err.to_string(); assert!(msg.contains("nonce cannot be 0"), "unexpected error: {msg}"); } #[test] fn test_sign_with_nonce_nonce_ge_order_errors() { // nonce >= n should be rejected use k256::Secp256k1; // Curve order n as U256 let n_u256 = U256::from_be_slice(&Secp256k1::ORDER.to_be_byte_array()); let pk_u256: U256 = U256::from(1u64); let digest = FixedBytes::from_hex( "0xcccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc", ) .unwrap(); // Try exactly n (>= n invalid) let err = sign_with_nonce(&pk_u256, &digest, &n_u256).unwrap_err(); let msg = err.to_string(); assert!(msg.contains("invalid nonce scalar"), "unexpected error: {msg}"); } #[test] fn test_create_ed25519_key_determinism() { let salt = B256::from([1u8; 32]); let result1 = create_ed25519_key(&salt).unwrap(); let result2 = create_ed25519_key(&salt).unwrap(); assert_eq!(result1, result2, "same salt should produce same keys"); } #[test] fn test_create_ed25519_key_different_salts() { let salt1 = B256::from([1u8; 32]); let salt2 = B256::from([2u8; 32]); let result1 = create_ed25519_key(&salt1).unwrap(); let result2 = create_ed25519_key(&salt2).unwrap(); assert_ne!(result1, result2, "different salts should produce different keys"); } #[test] fn test_public_key_ed25519_consistency() { let salt = B256::from([42u8; 32]); let create_result = create_ed25519_key(&salt).unwrap(); let (expected_public, private): (B256, B256) = <(B256, B256)>::abi_decode(&create_result).unwrap(); let derived_public_result = public_key_ed25519(&private).unwrap(); let derived_public = B256::abi_decode(&derived_public_result).unwrap(); assert_eq!(expected_public, derived_public, "derived public key should match"); } #[test] fn test_sign_and_verify_ed25519_valid() { let salt = B256::from([123u8; 32]); let create_result = create_ed25519_key(&salt).unwrap(); let (public_key, private_key): (B256, B256) = <(B256, B256)>::abi_decode(&create_result).unwrap(); let namespace = b"test.namespace"; let message = b"hello world"; let sig_result = sign_ed25519(namespace, message, &private_key).unwrap(); let sig_bytes: Vec = Vec::abi_decode(&sig_result).unwrap(); let verify_result = verify_ed25519(&sig_bytes, namespace, message, &public_key).unwrap(); let valid = bool::abi_decode(&verify_result).unwrap(); assert!(valid, "signature should be valid"); } #[test] fn test_verify_ed25519_invalid_signature() { let salt = B256::from([123u8; 32]); let create_result = create_ed25519_key(&salt).unwrap(); let (public_key, _): (B256, B256) = <(B256, B256)>::abi_decode(&create_result).unwrap(); let invalid_sig = [0u8; 64]; let namespace = b"test.namespace"; let message = b"hello world"; let verify_result = verify_ed25519(&invalid_sig, namespace, message, &public_key).unwrap(); let valid = bool::abi_decode(&verify_result).unwrap(); assert!(!valid, "invalid signature should not verify"); } #[test] fn test_verify_ed25519_namespace_separation() { let salt = B256::from([123u8; 32]); let create_result = create_ed25519_key(&salt).unwrap(); let (public_key, private_key): (B256, B256) = <(B256, B256)>::abi_decode(&create_result).unwrap(); let namespace_a = b"namespace.a"; let message = b"message"; let sig_result = sign_ed25519(namespace_a, message, &private_key).unwrap(); let sig_bytes: Vec = Vec::abi_decode(&sig_result).unwrap(); let namespace_b = b"namespace.b"; let verify_result = verify_ed25519(&sig_bytes, namespace_b, message, &public_key).unwrap(); let valid = bool::abi_decode(&verify_result).unwrap(); assert!(!valid, "signature with namespace A should not verify with namespace B"); let verify_result = verify_ed25519(&sig_bytes, namespace_a, message, &public_key).unwrap(); let valid = bool::abi_decode(&verify_result).unwrap(); assert!(valid, "signature should verify with correct namespace"); } #[test] fn test_verify_ed25519_invalid_signature_length() { let salt = B256::from([123u8; 32]); let create_result = create_ed25519_key(&salt).unwrap(); let (public_key, _): (B256, B256) = <(B256, B256)>::abi_decode(&create_result).unwrap(); let invalid_sig = [0u8; 32]; let namespace = b"test"; let message = b"message"; let verify_result = verify_ed25519(&invalid_sig, namespace, message, &public_key).unwrap(); let valid = bool::abi_decode(&verify_result).unwrap(); assert!(!valid, "signature with wrong length should not verify"); } } ================================================ FILE: crates/cheatcodes/src/env.rs ================================================ //! Implementations of [`Environment`](spec::Group::Environment) cheatcodes. use crate::{Cheatcode, Cheatcodes, Error, Result, Vm::*, string}; use alloy_dyn_abi::DynSolType; use alloy_sol_types::SolValue; use std::{env, sync::OnceLock}; /// Stores the forge execution context for the duration of the program. pub static FORGE_CONTEXT: OnceLock = OnceLock::new(); impl Cheatcode for setEnvCall { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { name: key, value } = self; if key.is_empty() { Err(fmt_err!("environment variable key can't be empty")) } else if key.contains('=') { Err(fmt_err!("environment variable key can't contain equal sign `=`")) } else if key.contains('\0') { Err(fmt_err!("environment variable key can't contain NUL character `\\0`")) } else if value.contains('\0') { Err(fmt_err!("environment variable value can't contain NUL character `\\0`")) } else { unsafe { env::set_var(key, value); } Ok(Default::default()) } } } impl Cheatcode for resolveEnvCall { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { input } = self; let resolved = foundry_config::resolve::interpolate(input) .map_err(|e| fmt_err!("failed to resolve env var: {e}"))?; Ok(resolved.abi_encode()) } } impl Cheatcode for envExistsCall { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { name } = self; Ok(env::var(name).is_ok().abi_encode()) } } impl Cheatcode for envBool_0Call { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { name } = self; env(name, &DynSolType::Bool) } } impl Cheatcode for envUint_0Call { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { name } = self; env(name, &DynSolType::Uint(256)) } } impl Cheatcode for envInt_0Call { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { name } = self; env(name, &DynSolType::Int(256)) } } impl Cheatcode for envAddress_0Call { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { name } = self; env(name, &DynSolType::Address) } } impl Cheatcode for envBytes32_0Call { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { name } = self; env(name, &DynSolType::FixedBytes(32)) } } impl Cheatcode for envString_0Call { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { name } = self; env(name, &DynSolType::String) } } impl Cheatcode for envBytes_0Call { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { name } = self; env(name, &DynSolType::Bytes) } } impl Cheatcode for envBool_1Call { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { name, delim } = self; env_array(name, delim, &DynSolType::Bool) } } impl Cheatcode for envUint_1Call { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { name, delim } = self; env_array(name, delim, &DynSolType::Uint(256)) } } impl Cheatcode for envInt_1Call { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { name, delim } = self; env_array(name, delim, &DynSolType::Int(256)) } } impl Cheatcode for envAddress_1Call { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { name, delim } = self; env_array(name, delim, &DynSolType::Address) } } impl Cheatcode for envBytes32_1Call { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { name, delim } = self; env_array(name, delim, &DynSolType::FixedBytes(32)) } } impl Cheatcode for envString_1Call { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { name, delim } = self; env_array(name, delim, &DynSolType::String) } } impl Cheatcode for envBytes_1Call { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { name, delim } = self; env_array(name, delim, &DynSolType::Bytes) } } // bool impl Cheatcode for envOr_0Call { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { name, defaultValue } = self; env_default(name, defaultValue, &DynSolType::Bool) } } // uint256 impl Cheatcode for envOr_1Call { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { name, defaultValue } = self; env_default(name, defaultValue, &DynSolType::Uint(256)) } } // int256 impl Cheatcode for envOr_2Call { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { name, defaultValue } = self; env_default(name, defaultValue, &DynSolType::Int(256)) } } // address impl Cheatcode for envOr_3Call { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { name, defaultValue } = self; env_default(name, defaultValue, &DynSolType::Address) } } // bytes32 impl Cheatcode for envOr_4Call { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { name, defaultValue } = self; env_default(name, defaultValue, &DynSolType::FixedBytes(32)) } } // string impl Cheatcode for envOr_5Call { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { name, defaultValue } = self; env_default(name, defaultValue, &DynSolType::String) } } // bytes impl Cheatcode for envOr_6Call { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { name, defaultValue } = self; env_default(name, defaultValue, &DynSolType::Bytes) } } // bool[] impl Cheatcode for envOr_7Call { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { name, delim, defaultValue } = self; env_array_default(name, delim, defaultValue, &DynSolType::Bool) } } // uint256[] impl Cheatcode for envOr_8Call { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { name, delim, defaultValue } = self; env_array_default(name, delim, defaultValue, &DynSolType::Uint(256)) } } // int256[] impl Cheatcode for envOr_9Call { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { name, delim, defaultValue } = self; env_array_default(name, delim, defaultValue, &DynSolType::Int(256)) } } // address[] impl Cheatcode for envOr_10Call { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { name, delim, defaultValue } = self; env_array_default(name, delim, defaultValue, &DynSolType::Address) } } // bytes32[] impl Cheatcode for envOr_11Call { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { name, delim, defaultValue } = self; env_array_default(name, delim, defaultValue, &DynSolType::FixedBytes(32)) } } // string[] impl Cheatcode for envOr_12Call { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { name, delim, defaultValue } = self; env_array_default(name, delim, defaultValue, &DynSolType::String) } } // bytes[] impl Cheatcode for envOr_13Call { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { name, delim, defaultValue } = self; let default = defaultValue.to_vec(); env_array_default(name, delim, &default, &DynSolType::Bytes) } } impl Cheatcode for isContextCall { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { context } = self; Ok((FORGE_CONTEXT.get() == Some(context)).abi_encode()) } } /// Set `forge` command current execution context for the duration of the program. /// Execution context is immutable, subsequent calls of this function won't change the context. pub fn set_execution_context(context: ForgeContext) { let _ = FORGE_CONTEXT.set(context); } fn env(key: &str, ty: &DynSolType) -> Result { get_env(key).and_then(|val| string::parse(&val, ty).map_err(map_env_err(key, &val))) } fn env_default(key: &str, default: &T, ty: &DynSolType) -> Result { Ok(env(key, ty).unwrap_or_else(|_| default.abi_encode())) } fn env_array(key: &str, delim: &str, ty: &DynSolType) -> Result { get_env(key).and_then(|val| { string::parse_array(val.split(delim).map(str::trim), ty).map_err(map_env_err(key, &val)) }) } fn env_array_default(key: &str, delim: &str, default: &T, ty: &DynSolType) -> Result { Ok(env_array(key, delim, ty).unwrap_or_else(|_| default.abi_encode())) } fn get_env(key: &str) -> Result { match env::var(key) { Ok(val) => Ok(val), Err(env::VarError::NotPresent) => Err(fmt_err!("environment variable {key:?} not found")), Err(env::VarError::NotUnicode(s)) => { Err(fmt_err!("environment variable {key:?} was not valid unicode: {s:?}")) } } } /// Converts the error message of a failed parsing attempt to a more user-friendly message that /// doesn't leak the value. fn map_env_err<'a>(key: &'a str, value: &'a str) -> impl FnOnce(Error) -> Error + 'a { move |e| { // failed parsing as type `uint256`: parser error: // // ^ // expected at least one digit let mut e = e.to_string(); e = e.replacen(&format!("\"{value}\""), &format!("${key}"), 1); e = e.replacen(&format!("\n{value}\n"), &format!("\n${key}\n"), 1); Error::from(e) } } #[cfg(test)] mod tests { use super::*; #[test] fn parse_env_uint() { let key = "parse_env_uint"; let value = "t"; unsafe { env::set_var(key, value); } let err = env(key, &DynSolType::Uint(256)).unwrap_err().to_string(); assert_eq!(err.matches("$parse_env_uint").count(), 2, "{err:?}"); unsafe { env::remove_var(key); } } } ================================================ FILE: crates/cheatcodes/src/error.rs ================================================ use crate::Vm; use alloy_primitives::{Bytes, hex}; use alloy_signer::Error as SignerError; use alloy_signer_local::LocalSignerError; use alloy_sol_types::SolError; use foundry_common::errors::FsPathError; 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::context_interface::result::EVMError; use std::{borrow::Cow, fmt}; /// Cheatcode result type. /// /// Type alias with a default Ok type of [`Vec`], and default Err type of [`Error`]. pub type Result, E = Error> = std::result::Result; macro_rules! fmt_err { ($msg:literal $(,)?) => { $crate::Error::fmt(::std::format_args!($msg)) }; ($err:expr $(,)?) => { <$crate::Error as ::std::convert::From<_>>::from($err) }; ($fmt:expr, $($arg:tt)*) => { $crate::Error::fmt(::std::format_args!($fmt, $($arg)*)) }; } macro_rules! bail { ($msg:literal $(,)?) => { return ::std::result::Result::Err(fmt_err!($msg)) }; ($err:expr $(,)?) => { return ::std::result::Result::Err(fmt_err!($err)) }; ($fmt:expr, $($arg:tt)*) => { return ::std::result::Result::Err(fmt_err!($fmt, $($arg)*)) }; } macro_rules! ensure { ($cond:expr $(,)?) => { if !$cond { return ::std::result::Result::Err($crate::Error::custom( ::std::concat!("Condition failed: `", ::std::stringify!($cond), "`") )); } }; ($cond:expr, $msg:literal $(,)?) => { if !$cond { return ::std::result::Result::Err(fmt_err!($msg)); } }; ($cond:expr, $err:expr $(,)?) => { if !$cond { return ::std::result::Result::Err(fmt_err!($err)); } }; ($cond:expr, $fmt:expr, $($arg:tt)*) => { if !$cond { return ::std::result::Result::Err(fmt_err!($fmt, $($arg)*)); } }; } /// Error thrown by cheatcodes. // This uses a custom repr to minimize the size of the error. // The repr is basically `enum { Cow<'static, str>, Cow<'static, [u8]> }` pub struct Error { /// If true, encode `data` as `Error(string)`, otherwise encode it directly as `bytes`. is_str: bool, /// Whether this was constructed from an owned byte vec, which means we have to drop the data /// in `impl Drop`. drop: bool, /// The error data. Always a valid pointer, and never modified. data: *const [u8], } impl std::error::Error for Error {} impl fmt::Debug for Error { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.write_str("Error::")?; self.kind().fmt(f) } } impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { self.kind().fmt(f) } } /// Kind of cheatcode errors. /// /// Constructed by [`Error::kind`]. #[derive(Debug)] pub enum ErrorKind<'a> { /// A string error, ABI-encoded as `CheatcodeError(string)`. String(&'a str), /// A raw bytes error. Does not get encoded. Bytes(&'a [u8]), } impl fmt::Display for ErrorKind<'_> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match *self { Self::String(ss) => f.write_str(ss), Self::Bytes(b) => f.write_str(&hex::encode_prefixed(b)), } } } impl Error { /// Creates a new error and ABI encodes it as `CheatcodeError(string)`. pub fn encode(error: impl Into) -> Bytes { error.into().abi_encode().into() } /// Creates a new error with a custom message. pub fn display(msg: impl fmt::Display) -> Self { Self::fmt(format_args!("{msg}")) } /// Creates a new error with a custom [`fmt::Arguments`] message. pub fn fmt(args: fmt::Arguments<'_>) -> Self { match args.as_str() { Some(s) => Self::new_str(s), None => Self::new_string(std::fmt::format(args)), } } /// ABI-encodes this error as `CheatcodeError(string)` if the inner message is a string, /// otherwise returns the raw bytes. pub fn abi_encode(&self) -> Vec { match self.kind() { ErrorKind::String(string) => Vm::CheatcodeError { message: string.into() }.abi_encode(), ErrorKind::Bytes(bytes) => bytes.into(), } } /// Returns the kind of this error. pub fn kind(&self) -> ErrorKind<'_> { let data = self.data(); if self.is_str { debug_assert!(std::str::from_utf8(data).is_ok()); ErrorKind::String(unsafe { std::str::from_utf8_unchecked(data) }) } else { ErrorKind::Bytes(data) } } /// Returns the raw data of this error. pub fn data(&self) -> &[u8] { unsafe { &*self.data } } /// Returns `true` if this error is a human-readable string. pub fn is_str(&self) -> bool { self.is_str } fn new_str(data: &'static str) -> Self { Self::_new(true, false, data.as_bytes()) } fn new_string(data: String) -> Self { Self::_new(true, true, Box::into_raw(data.into_boxed_str().into_boxed_bytes())) } fn new_bytes(data: &'static [u8]) -> Self { Self::_new(false, false, data) } fn new_vec(data: Vec) -> Self { Self::_new(false, true, Box::into_raw(data.into_boxed_slice())) } fn _new(is_str: bool, drop: bool, data: *const [u8]) -> Self { debug_assert!(!data.is_null()); Self { is_str, drop, data } } } impl Drop for Error { fn drop(&mut self) { if self.drop { drop(unsafe { Box::<[u8]>::from_raw(self.data.cast_mut()) }); } } } impl From> for Error { fn from(value: Cow<'static, str>) -> Self { match value { Cow::Borrowed(str) => Self::new_str(str), Cow::Owned(string) => Self::new_string(string), } } } impl From for Error { fn from(value: String) -> Self { Self::new_string(value) } } impl From<&'static str> for Error { fn from(value: &'static str) -> Self { Self::new_str(value) } } impl From> for Error { fn from(value: Cow<'static, [u8]>) -> Self { match value { Cow::Borrowed(bytes) => Self::new_bytes(bytes), Cow::Owned(vec) => Self::new_vec(vec), } } } impl From<&'static [u8]> for Error { fn from(value: &'static [u8]) -> Self { Self::new_bytes(value) } } impl From<&'static [u8; N]> for Error { fn from(value: &'static [u8; N]) -> Self { Self::new_bytes(value) } } impl From> for Error { fn from(value: Vec) -> Self { Self::new_vec(value) } } impl From for Error { fn from(value: Bytes) -> Self { Self::new_vec(value.into()) } } // So we can use `?` on `Result<_, Error>`. macro_rules! impl_from { ($($t:ty),* $(,)?) => {$( impl From<$t> for Error { fn from(value: $t) -> Self { Self::display(value) } } )*}; } impl_from!( alloy_sol_types::Error, alloy_dyn_abi::Error, alloy_primitives::SignatureError, alloy_consensus::crypto::RecoveryError, FsPathError, hex::FromHexError, BackendError, DatabaseError, jsonpath_lib::JsonPathError, serde_json::Error, SignatureError, std::io::Error, std::num::TryFromIntError, std::str::Utf8Error, std::string::FromUtf8Error, UnresolvedEnvVarError, LocalSignerError, SignerError, WalletSignerError, ); impl> From> for Error { fn from(err: EVMError) -> Self { Self::display(BackendError::from(err)) } } impl From for Error { fn from(err: eyre::Report) -> Self { Self::new_string(foundry_common::errors::display_chain(&err)) } } #[cfg(test)] mod tests { use super::*; #[test] fn encode() { let error = Vm::CheatcodeError { message: "hello".into() }.abi_encode(); assert_eq!(Error::from("hello").abi_encode(), error); assert_eq!(Error::encode("hello"), error); assert_eq!(Error::from(b"hello").abi_encode(), b"hello"); assert_eq!(Error::encode(b"hello"), b"hello"[..]); } } ================================================ FILE: crates/cheatcodes/src/evm/fork.rs ================================================ use crate::{ Cheatcode, Cheatcodes, CheatcodesExecutor, CheatsCtxt, DatabaseExt, EthCheatCtx, Result, Vm::*, json::json_value_to_token, }; use alloy_dyn_abi::DynSolValue; use alloy_evm::EvmEnv; use alloy_network::AnyNetwork; use alloy_primitives::{B256, U256}; use alloy_provider::Provider; use alloy_rpc_types::Filter; use alloy_sol_types::SolValue; use foundry_common::provider::ProviderBuilder; use foundry_evm_core::{FoundryContextExt, backend::JournaledState, fork::CreateFork}; use revm::context::{Cfg, ContextTr}; impl Cheatcode for activeForkCall { fn apply_stateful>( &self, ccx: &mut CheatsCtxt<'_, CTX>, ) -> Result { let Self {} = self; ccx.ecx .db() .active_fork_id() .map(|id| id.abi_encode()) .ok_or_else(|| fmt_err!("no active fork")) } } impl Cheatcode for createFork_0Call { fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { urlOrAlias } = self; create_fork(ccx, urlOrAlias, None) } } impl Cheatcode for createFork_1Call { fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { urlOrAlias, blockNumber } = self; create_fork(ccx, urlOrAlias, Some(blockNumber.saturating_to())) } } impl Cheatcode for createFork_2Call { fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { urlOrAlias, txHash } = self; create_fork_at_transaction(ccx, urlOrAlias, txHash) } } impl Cheatcode for createSelectFork_0Call { fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { urlOrAlias } = self; create_select_fork(ccx, urlOrAlias, None) } } impl Cheatcode for createSelectFork_1Call { fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { urlOrAlias, blockNumber } = self; create_select_fork(ccx, urlOrAlias, Some(blockNumber.saturating_to())) } } impl Cheatcode for createSelectFork_2Call { fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { urlOrAlias, txHash } = self; create_select_fork_at_transaction(ccx, urlOrAlias, txHash) } } impl Cheatcode for rollFork_0Call { fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { blockNumber } = self; persist_caller(ccx); fork_env_op(ccx, |db, evm_env, tx_env, inner| { db.roll_fork(None, (*blockNumber).to(), evm_env, tx_env, inner) }) } } impl Cheatcode for rollFork_1Call { fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { txHash } = self; persist_caller(ccx); fork_env_op(ccx, |db, evm_env, tx_env, inner| { db.roll_fork_to_transaction(None, *txHash, evm_env, tx_env, inner) }) } } impl Cheatcode for rollFork_2Call { fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { forkId, blockNumber } = self; persist_caller(ccx); fork_env_op(ccx, |db, evm_env, tx_env, inner| { db.roll_fork(Some(*forkId), (*blockNumber).to(), evm_env, tx_env, inner) }) } } impl Cheatcode for rollFork_3Call { fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { forkId, txHash } = self; persist_caller(ccx); fork_env_op(ccx, |db, evm_env, tx_env, inner| { db.roll_fork_to_transaction(Some(*forkId), *txHash, evm_env, tx_env, inner) }) } } impl Cheatcode for selectForkCall { fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { forkId } = self; persist_caller(ccx); check_broadcast(ccx.state)?; fork_env_op(ccx, |db, evm_env, tx_env, inner| { db.select_fork(*forkId, evm_env, tx_env, inner) }) } } impl Cheatcode for transact_0Call { fn apply_full( &self, ccx: &mut CheatsCtxt<'_, CTX>, executor: &mut dyn CheatcodesExecutor, ) -> Result { let Self { txHash } = *self; transact(ccx, executor, txHash, None) } } impl Cheatcode for transact_1Call { fn apply_full( &self, ccx: &mut CheatsCtxt<'_, CTX>, executor: &mut dyn CheatcodesExecutor, ) -> Result { let Self { forkId, txHash } = *self; transact(ccx, executor, txHash, Some(forkId)) } } impl Cheatcode for allowCheatcodesCall { fn apply_stateful>( &self, ccx: &mut CheatsCtxt<'_, CTX>, ) -> Result { let Self { account } = self; ccx.ecx.db_mut().allow_cheatcode_access(*account); Ok(Default::default()) } } impl Cheatcode for makePersistent_0Call { fn apply_stateful>( &self, ccx: &mut CheatsCtxt<'_, CTX>, ) -> Result { let Self { account } = self; ccx.ecx.db_mut().add_persistent_account(*account); Ok(Default::default()) } } impl Cheatcode for makePersistent_1Call { fn apply_stateful>( &self, ccx: &mut CheatsCtxt<'_, CTX>, ) -> Result { let Self { account0, account1 } = self; ccx.ecx.db_mut().add_persistent_account(*account0); ccx.ecx.db_mut().add_persistent_account(*account1); Ok(Default::default()) } } impl Cheatcode for makePersistent_2Call { fn apply_stateful>( &self, ccx: &mut CheatsCtxt<'_, CTX>, ) -> Result { let Self { account0, account1, account2 } = self; ccx.ecx.db_mut().add_persistent_account(*account0); ccx.ecx.db_mut().add_persistent_account(*account1); ccx.ecx.db_mut().add_persistent_account(*account2); Ok(Default::default()) } } impl Cheatcode for makePersistent_3Call { fn apply_stateful>( &self, ccx: &mut CheatsCtxt<'_, CTX>, ) -> Result { let Self { accounts } = self; for account in accounts { ccx.ecx.db_mut().add_persistent_account(*account); } Ok(Default::default()) } } impl Cheatcode for revokePersistent_0Call { fn apply_stateful>( &self, ccx: &mut CheatsCtxt<'_, CTX>, ) -> Result { let Self { account } = self; ccx.ecx.db_mut().remove_persistent_account(account); Ok(Default::default()) } } impl Cheatcode for revokePersistent_1Call { fn apply_stateful>( &self, ccx: &mut CheatsCtxt<'_, CTX>, ) -> Result { let Self { accounts } = self; for account in accounts { ccx.ecx.db_mut().remove_persistent_account(account); } Ok(Default::default()) } } impl Cheatcode for isPersistentCall { fn apply_stateful>( &self, ccx: &mut CheatsCtxt<'_, CTX>, ) -> Result { let Self { account } = self; Ok(ccx.ecx.db().is_persistent(account).abi_encode()) } } impl Cheatcode for rpc_0Call { fn apply_stateful>( &self, ccx: &mut CheatsCtxt<'_, CTX>, ) -> Result { let Self { method, params } = self; let url = ccx.ecx.db().active_fork_url().ok_or_else(|| fmt_err!("no active fork URL found"))?; rpc_call(&url, method, params) } } impl Cheatcode for rpc_1Call { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { urlOrAlias, method, params } = self; let url = state.config.rpc_endpoint(urlOrAlias)?.url()?; rpc_call(&url, method, params) } } impl Cheatcode for eth_getLogsCall { fn apply_stateful>( &self, ccx: &mut CheatsCtxt<'_, CTX>, ) -> Result { let Self { fromBlock, toBlock, target, topics } = self; let (Ok(from_block), Ok(to_block)) = (u64::try_from(fromBlock), u64::try_from(toBlock)) else { bail!("blocks in block range must be less than 2^64") }; if topics.len() > 4 { 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 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() { filter.topics[i] = topic.into(); } let logs = foundry_common::block_on(provider.get_logs(&filter)) .map_err(|e| fmt_err!("failed to get logs: {e}"))?; let eth_logs = logs .into_iter() .map(|log| EthGetLogs { emitter: log.address(), topics: log.topics().to_vec(), data: log.inner.data.data, blockHash: log.block_hash.unwrap_or_default(), blockNumber: log.block_number.unwrap_or_default(), transactionHash: log.transaction_hash.unwrap_or_default(), transactionIndex: log.transaction_index.unwrap_or_default(), logIndex: U256::from(log.log_index.unwrap_or_default()), removed: log.removed, }) .collect::>(); Ok(eth_logs.abi_encode()) } } impl Cheatcode for getRawBlockHeaderCall { fn apply_stateful>( &self, ccx: &mut CheatsCtxt<'_, CTX>, ) -> Result { let Self { blockNumber } = self; let url = ccx.ecx.db().active_fork_url().ok_or_else(|| fmt_err!("no active fork"))?; let provider = ProviderBuilder::::new(&url).build()?; let block_number = u64::try_from(blockNumber) .map_err(|_| fmt_err!("block number must be less than 2^64"))?; let block = foundry_common::block_on(async move { provider.get_block(block_number.into()).await }) .map_err(|e| fmt_err!("failed to get block: {e}"))? .ok_or_else(|| fmt_err!("block {block_number} not found"))?; let header: alloy_consensus::Header = block .into_inner() .header .inner .try_into_header() .map_err(|e| fmt_err!("failed to convert to header: {e}"))?; Ok(alloy_rlp::encode(&header).abi_encode()) } } /// Creates and then also selects the new fork fn create_select_fork( ccx: &mut CheatsCtxt<'_, CTX>, url_or_alias: &str, block: Option, ) -> Result { check_broadcast(ccx.state)?; let fork = create_fork_request(ccx, url_or_alias, block)?; fork_env_op(ccx, |db, evm_env, tx_env, inner| { db.create_select_fork(fork, evm_env, tx_env, inner) }) } /// Creates a new fork fn create_fork( ccx: &mut CheatsCtxt<'_, CTX>, url_or_alias: &str, block: Option, ) -> Result { let fork = create_fork_request(ccx, url_or_alias, block)?; let id = ccx.ecx.db_mut().create_fork(fork)?; Ok(id.abi_encode()) } /// Creates and then also selects the new fork at the given transaction fn create_select_fork_at_transaction( ccx: &mut CheatsCtxt<'_, CTX>, url_or_alias: &str, transaction: &B256, ) -> Result { check_broadcast(ccx.state)?; let fork = create_fork_request(ccx, url_or_alias, None)?; fork_env_op(ccx, |db, evm_env, tx_env, inner| { db.create_select_fork_at_transaction(fork, evm_env, tx_env, inner, *transaction) }) } /// Creates a new fork at the given transaction fn create_fork_at_transaction( ccx: &mut CheatsCtxt<'_, CTX>, url_or_alias: &str, transaction: &B256, ) -> Result { let fork = create_fork_request(ccx, url_or_alias, None)?; let id = ccx.ecx.db_mut().create_fork_at_transaction(fork, *transaction)?; Ok(id.abi_encode()) } /// Creates the request object for a new fork request fn create_fork_request( ccx: &mut CheatsCtxt<'_, CTX>, url_or_alias: &str, block: Option, ) -> Result { persist_caller(ccx); let rpc_endpoint = ccx.state.config.rpc_endpoint(url_or_alias)?; let url = rpc_endpoint.url()?; let mut evm_opts = ccx.state.config.evm_opts.clone(); evm_opts.fork_block_number = block; evm_opts.fork_retries = rpc_endpoint.config.retries; evm_opts.fork_retry_backoff = rpc_endpoint.config.retry_backoff; if let Some(Ok(auth)) = rpc_endpoint.auth { evm_opts.fork_headers = Some(vec![format!("Authorization: {auth}")]); } let fork = CreateFork { enable_caching: !ccx.state.config.no_storage_caching && ccx.state.config.rpc_storage_caching.enable_for_endpoint(&url), url, evm_env: EvmEnv { cfg_env: ccx.ecx.cfg_mut().clone(), block_env: ccx.ecx.block_mut().clone(), }, evm_opts, }; Ok(fork) } /// Clones the EVM and tx environments, runs a fork operation that may modify them, then writes /// them back. This is the common pattern for all fork-switching cheatcodes (rollFork, selectFork, /// createSelectFork). fn fork_env_op( ccx: &mut CheatsCtxt<'_, CTX>, f: impl FnOnce( &mut dyn DatabaseExt::Spec>, &mut EvmEnv<::Spec, CTX::Block>, &mut CTX::Tx, &mut JournaledState, ) -> eyre::Result, ) -> Result { let mut evm_env = ccx.ecx.evm_clone(); let mut tx_env = ccx.ecx.tx_clone(); let (db, inner) = ccx.ecx.db_journal_inner_mut(); let result = f(db, &mut evm_env, &mut tx_env, inner)?; ccx.ecx.set_evm(evm_env); ccx.ecx.set_tx(tx_env); Ok(result.abi_encode()) } fn check_broadcast(state: &Cheatcodes) -> Result<()> { if state.broadcast.is_none() { Ok(()) } else { Err(fmt_err!("cannot select forks during a broadcast")) } } fn transact( ccx: &mut CheatsCtxt<'_, CTX>, executor: &mut dyn CheatcodesExecutor, transaction: B256, fork_id: Option, ) -> Result { executor.transact_on_db(ccx.state, ccx.ecx, fork_id, transaction)?; Ok(Default::default()) } // Helper to add the caller of fork cheat code as persistent account (in order to make sure that the // state of caller contract is not lost when fork changes). // Applies to create, select and roll forks actions. // https://github.com/foundry-rs/foundry/issues/8004 fn persist_caller>(ccx: &mut CheatsCtxt<'_, CTX>) { ccx.ecx.db_mut().add_persistent_account(ccx.caller); } /// Performs an Ethereum JSON-RPC request to the given endpoint. fn rpc_call(url: &str, method: &str, params: &str) -> Result { let provider = ProviderBuilder::::new(url).build()?; let params_json: serde_json::Value = serde_json::from_str(params)?; let result = foundry_common::block_on(provider.raw_request(method.to_string().into(), params_json)) .map_err(|err| fmt_err!("{method:?}: {err}"))?; let result_as_tokens = convert_to_bytes( &json_value_to_token(&result, None) .map_err(|err| fmt_err!("failed to parse result: {err}"))?, ); Ok(result_as_tokens.abi_encode()) } /// Convert fixed bytes and address values to bytes in order to prevent encoding issues. fn convert_to_bytes(token: &DynSolValue) -> DynSolValue { match token { // Convert fixed bytes to prevent encoding issues. // See: DynSolValue::FixedBytes(bytes, size) => { DynSolValue::Bytes(bytes.as_slice()[..*size].to_vec()) } DynSolValue::Address(addr) => DynSolValue::Bytes(addr.to_vec()), // Convert tuple values to prevent encoding issues. // See: DynSolValue::Tuple(vals) => DynSolValue::Tuple(vals.iter().map(convert_to_bytes).collect()), val => val.clone(), } } ================================================ FILE: crates/cheatcodes/src/evm/mapping.rs ================================================ use crate::{Cheatcode, Cheatcodes, Result, Vm::*}; use alloy_primitives::{Address, B256}; use alloy_sol_types::SolValue; use foundry_common::mapping_slots::MappingSlots; impl Cheatcode for startMappingRecordingCall { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self {} = self; state.mapping_slots.get_or_insert_default(); Ok(Default::default()) } } impl Cheatcode for stopMappingRecordingCall { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self {} = self; state.mapping_slots = None; Ok(Default::default()) } } impl Cheatcode for getMappingLengthCall { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { target, mappingSlot } = self; let result = slot_child(state, target, mappingSlot).map(Vec::len).unwrap_or(0); Ok((result as u64).abi_encode()) } } impl Cheatcode for getMappingSlotAtCall { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { target, mappingSlot, idx } = self; let result = slot_child(state, target, mappingSlot) .and_then(|set| set.get(idx.saturating_to::())) .copied() .unwrap_or_default(); Ok(result.abi_encode()) } } impl Cheatcode for getMappingKeyAndParentOfCall { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { target, elementSlot: slot } = self; let mut found = false; let mut key = &B256::ZERO; let mut parent = &B256::ZERO; if let Some(slots) = mapping_slot(state, target) { if let Some(key2) = slots.keys.get(slot) { found = true; key = key2; parent = &slots.parent_slots[slot]; } else if let Some((key2, parent2)) = slots.seen_sha3.get(slot) { found = true; key = key2; parent = parent2; } } Ok((found, key, parent).abi_encode_params()) } } fn mapping_slot<'a>(state: &'a Cheatcodes, target: &'a Address) -> Option<&'a MappingSlots> { state.mapping_slots.as_ref()?.get(target) } fn slot_child<'a>( state: &'a Cheatcodes, target: &'a Address, slot: &'a B256, ) -> Option<&'a Vec> { mapping_slot(state, target)?.children.get(slot) } ================================================ FILE: crates/cheatcodes/src/evm/mock.rs ================================================ use crate::{Cheatcode, Cheatcodes, CheatsCtxt, Result, Vm::*}; use alloy_primitives::{Address, Bytes, U256}; use foundry_evm_core::backend::DatabaseExt; use revm::{ bytecode::Bytecode, context::{ContextTr, JournalTr}, interpreter::InstructionResult, }; use std::{cmp::Ordering, collections::VecDeque}; /// Mocked call data. #[derive(Clone, Debug, Default, PartialEq, Eq, Hash)] pub struct MockCallDataContext { /// The partial calldata to match for mock pub calldata: Bytes, /// The value to match for mock pub value: Option, } /// Mocked return data. #[derive(Clone, Debug)] pub struct MockCallReturnData { /// The return type for the mocked call pub ret_type: InstructionResult, /// Return data or error pub data: Bytes, } impl PartialOrd for MockCallDataContext { fn partial_cmp(&self, other: &Self) -> Option { Some(self.cmp(other)) } } impl Ord for MockCallDataContext { fn cmp(&self, other: &Self) -> Ordering { // Calldata matching is reversed to ensure that a tighter match is // returned if an exact match is not found. In case, there is // a partial match to calldata that is more specific than // a match to a msg.value, then the more specific calldata takes // precedence. self.calldata.cmp(&other.calldata).reverse().then(self.value.cmp(&other.value).reverse()) } } impl Cheatcode for clearMockedCallsCall { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self {} = self; state.mocked_calls = Default::default(); Ok(Default::default()) } } impl Cheatcode for mockCall_0Call { fn apply_stateful>( &self, ccx: &mut CheatsCtxt<'_, CTX>, ) -> Result { let Self { callee, data, returnData } = self; let _ = make_acc_non_empty(callee, ccx)?; mock_call(ccx.state, callee, data, None, returnData, InstructionResult::Return); Ok(Default::default()) } } impl Cheatcode for mockCall_1Call { fn apply_stateful>( &self, ccx: &mut CheatsCtxt<'_, CTX>, ) -> Result { let Self { callee, msgValue, data, returnData } = self; let _ = make_acc_non_empty(callee, ccx)?; mock_call(ccx.state, callee, data, Some(msgValue), returnData, InstructionResult::Return); Ok(Default::default()) } } impl Cheatcode for mockCall_2Call { fn apply_stateful>( &self, ccx: &mut CheatsCtxt<'_, CTX>, ) -> Result { let Self { callee, data, returnData } = self; let _ = make_acc_non_empty(callee, ccx)?; mock_call( ccx.state, callee, &Bytes::from(*data), None, returnData, InstructionResult::Return, ); Ok(Default::default()) } } impl Cheatcode for mockCall_3Call { fn apply_stateful>( &self, ccx: &mut CheatsCtxt<'_, CTX>, ) -> Result { let Self { callee, msgValue, data, returnData } = self; let _ = make_acc_non_empty(callee, ccx)?; mock_call( ccx.state, callee, &Bytes::from(*data), Some(msgValue), returnData, InstructionResult::Return, ); Ok(Default::default()) } } impl Cheatcode for mockCalls_0Call { fn apply_stateful>( &self, ccx: &mut CheatsCtxt<'_, CTX>, ) -> Result { let Self { callee, data, returnData } = self; let _ = make_acc_non_empty(callee, ccx)?; mock_calls(ccx.state, callee, data, None, returnData, InstructionResult::Return); Ok(Default::default()) } } impl Cheatcode for mockCalls_1Call { fn apply_stateful>( &self, ccx: &mut CheatsCtxt<'_, CTX>, ) -> Result { let Self { callee, msgValue, data, returnData } = self; let _ = make_acc_non_empty(callee, ccx)?; mock_calls(ccx.state, callee, data, Some(msgValue), returnData, InstructionResult::Return); Ok(Default::default()) } } impl Cheatcode for mockCallRevert_0Call { fn apply_stateful>( &self, ccx: &mut CheatsCtxt<'_, CTX>, ) -> Result { let Self { callee, data, revertData } = self; let _ = make_acc_non_empty(callee, ccx)?; mock_call(ccx.state, callee, data, None, revertData, InstructionResult::Revert); Ok(Default::default()) } } impl Cheatcode for mockCallRevert_1Call { fn apply_stateful>( &self, ccx: &mut CheatsCtxt<'_, CTX>, ) -> Result { let Self { callee, msgValue, data, revertData } = self; let _ = make_acc_non_empty(callee, ccx)?; mock_call(ccx.state, callee, data, Some(msgValue), revertData, InstructionResult::Revert); Ok(Default::default()) } } impl Cheatcode for mockCallRevert_2Call { fn apply_stateful>( &self, ccx: &mut CheatsCtxt<'_, CTX>, ) -> Result { let Self { callee, data, revertData } = self; let _ = make_acc_non_empty(callee, ccx)?; mock_call( ccx.state, callee, &Bytes::from(*data), None, revertData, InstructionResult::Revert, ); Ok(Default::default()) } } impl Cheatcode for mockCallRevert_3Call { fn apply_stateful>( &self, ccx: &mut CheatsCtxt<'_, CTX>, ) -> Result { let Self { callee, msgValue, data, revertData } = self; let _ = make_acc_non_empty(callee, ccx)?; mock_call( ccx.state, callee, &Bytes::from(*data), Some(msgValue), revertData, InstructionResult::Revert, ); Ok(Default::default()) } } impl Cheatcode for mockFunctionCall { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { callee, target, data } = self; state.mocked_functions.entry(*callee).or_default().insert(data.clone(), *target); Ok(Default::default()) } } fn mock_call( state: &mut Cheatcodes, callee: &Address, cdata: &Bytes, value: Option<&U256>, rdata: &Bytes, ret_type: InstructionResult, ) { mock_calls(state, callee, cdata, value, std::slice::from_ref(rdata), ret_type) } fn mock_calls( state: &mut Cheatcodes, callee: &Address, cdata: &Bytes, value: Option<&U256>, rdata_vec: &[Bytes], ret_type: InstructionResult, ) { state.mocked_calls.entry(*callee).or_default().insert( MockCallDataContext { calldata: cdata.clone(), value: value.copied() }, rdata_vec .iter() .map(|rdata| MockCallReturnData { ret_type, data: rdata.clone() }) .collect::>(), ); } // 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, ccx: &mut CheatsCtxt<'_, CTX>, ) -> Result { let empty_bytecode = { let acc = ccx.ecx.journal_mut().load_account(*callee)?; acc.info.code.as_ref().is_none_or(Bytecode::is_empty) }; if empty_bytecode { let code = Bytecode::new_raw(Bytes::from_static(&[0u8])); ccx.ecx.journal_mut().set_code(*callee, code); } Ok(Default::default()) } ================================================ FILE: crates/cheatcodes/src/evm/prank.rs ================================================ use crate::{Cheatcode, CheatsCtxt, Result, Vm::*, evm::journaled_account}; use alloy_primitives::Address; use foundry_evm_core::backend::DatabaseExt; use revm::{ context::{ContextTr, JournalTr, Transaction}, inspector::JournalExt, }; /// Prank information. #[derive(Clone, Copy, Debug, Default)] pub struct Prank { /// Address of the contract that initiated the prank pub prank_caller: Address, /// Address of `tx.origin` when the prank was initiated pub prank_origin: Address, /// The address to assign to `msg.sender` pub new_caller: Address, /// The address to assign to `tx.origin` pub new_origin: Option
, /// The depth at which the prank was called 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 pub delegate_call: bool, /// Whether the prank has been used yet (false if unused) pub used: bool, } impl Prank { /// Create a new prank. pub fn new( prank_caller: Address, prank_origin: Address, new_caller: Address, new_origin: Option
, depth: usize, single_call: bool, delegate_call: bool, ) -> Self { Self { prank_caller, prank_origin, new_caller, new_origin, depth, single_call, delegate_call, used: false, } } /// Apply the prank by setting `used` to true if it is false /// Only returns self in the case it is updated (first application) pub fn first_time_applied(&self) -> Option { if self.used { None } else { Some(Self { used: true, ..*self }) } } } impl Cheatcode for prank_0Call { fn apply_stateful>( &self, ccx: &mut CheatsCtxt<'_, CTX>, ) -> Result { let Self { msgSender } = self; prank(ccx, msgSender, None, true, false) } } impl Cheatcode for startPrank_0Call { fn apply_stateful>( &self, ccx: &mut CheatsCtxt<'_, CTX>, ) -> Result { let Self { msgSender } = self; prank(ccx, msgSender, None, false, false) } } impl Cheatcode for prank_1Call { fn apply_stateful>( &self, ccx: &mut CheatsCtxt<'_, CTX>, ) -> Result { let Self { msgSender, txOrigin } = self; prank(ccx, msgSender, Some(txOrigin), true, false) } } impl Cheatcode for startPrank_1Call { fn apply_stateful>( &self, ccx: &mut CheatsCtxt<'_, CTX>, ) -> Result { let Self { msgSender, txOrigin } = self; prank(ccx, msgSender, Some(txOrigin), false, false) } } impl Cheatcode for prank_2Call { fn apply_stateful>( &self, ccx: &mut CheatsCtxt<'_, CTX>, ) -> Result { let Self { msgSender, delegateCall } = self; prank(ccx, msgSender, None, true, *delegateCall) } } impl Cheatcode for startPrank_2Call { fn apply_stateful>( &self, ccx: &mut CheatsCtxt<'_, CTX>, ) -> Result { let Self { msgSender, delegateCall } = self; prank(ccx, msgSender, None, false, *delegateCall) } } impl Cheatcode for prank_3Call { fn apply_stateful>( &self, ccx: &mut CheatsCtxt<'_, CTX>, ) -> Result { let Self { msgSender, txOrigin, delegateCall } = self; prank(ccx, msgSender, Some(txOrigin), true, *delegateCall) } } impl Cheatcode for startPrank_3Call { fn apply_stateful>( &self, ccx: &mut CheatsCtxt<'_, CTX>, ) -> Result { let Self { msgSender, txOrigin, delegateCall } = self; prank(ccx, msgSender, Some(txOrigin), false, *delegateCall) } } impl Cheatcode for stopPrankCall { fn apply_stateful>( &self, ccx: &mut CheatsCtxt<'_, CTX>, ) -> Result { let Self {} = self; ccx.state.pranks.remove(&ccx.ecx.journal().depth()); Ok(Default::default()) } } fn prank>( ccx: &mut CheatsCtxt<'_, CTX>, new_caller: &Address, new_origin: Option<&Address>, single_call: bool, delegate_call: bool, ) -> Result { // Ensure that we load the account of the pranked address and mark it as touched. // This is necessary to ensure that account state changes (such as the account's `nonce`) are // properly tracked. let account = journaled_account(ccx.ecx, *new_caller)?; // Ensure that code exists at `msg.sender` if delegate calling. if delegate_call { ensure!( account.info.code.as_ref().is_some_and(|code| !code.is_empty()), "cannot `prank` delegate call from an EOA" ); } let depth = ccx.ecx.journal().depth(); if let Some(Prank { used, single_call: current_single_call, .. }) = ccx.state.get_prank(depth) { ensure!(used, "cannot overwrite a prank until it is applied at least once"); // This case can only fail if the user calls `vm.startPrank` and then `vm.prank` later on. // This should not be possible without first calling `stopPrank` ensure!( single_call == *current_single_call, "cannot override an ongoing prank with a single vm.prank; \ use vm.startPrank to override the current prank" ); } let prank = Prank::new( ccx.caller, ccx.ecx.tx().caller(), *new_caller, new_origin.copied(), depth, single_call, delegate_call, ); ensure!( ccx.state.broadcast.is_none(), "cannot `prank` for a broadcasted transaction; \ pass the desired `tx.origin` into the `broadcast` cheatcode call" ); ccx.state.pranks.insert(prank.depth, prank); Ok(Default::default()) } ================================================ FILE: crates/cheatcodes/src/evm/record_debug_step.rs ================================================ use alloy_primitives::{Bytes, U256}; use foundry_evm_traces::CallTraceArena; use revm::{bytecode::opcode::OpCode, interpreter::InstructionResult}; use foundry_evm_core::buffer::{BufferKind, get_buffer_accesses}; use revm_inspectors::tracing::types::{ CallTraceNode, CallTraceStep, RecordedMemory, TraceMemberOrder, }; use spec::Vm::DebugStep; // Context for a CallTraceStep, includes depth and contract address. pub(crate) struct CallTraceCtx<'a> { pub node: &'a CallTraceNode, pub step: &'a CallTraceStep, } // Do a depth first traverse of the nodes and steps and return steps // that are after `node_start_idx` pub(crate) fn flatten_call_trace<'a>( root: usize, arena: &'a CallTraceArena, node_start_idx: usize, ) -> Vec> { let mut steps = Vec::new(); let mut record_started = false; // Start the recursion from the root node recursive_flatten_call_trace(root, arena, node_start_idx, &mut record_started, &mut steps); steps } // Inner recursive function to process nodes. // This implementation directly mutates `record_started` and `flatten_steps`. // So the recursive call can change the `record_started` flag even for the parent // unfinished processing, and append steps to the `flatten_steps` as the final result. fn recursive_flatten_call_trace<'a>( node_idx: usize, arena: &'a CallTraceArena, node_start_idx: usize, record_started: &mut bool, flatten_steps: &mut Vec>, ) { // Once node_idx exceeds node_start_idx, start recording steps // for all the recursive processing. if !*record_started && node_idx >= node_start_idx { *record_started = true; } let node = &arena.nodes()[node_idx]; for order in &node.ordering { match order { TraceMemberOrder::Step(step_idx) if *record_started => { let step = &node.trace.steps[*step_idx]; flatten_steps.push(CallTraceCtx { node, step }); } TraceMemberOrder::Call(call_idx) => { let child_node_idx = node.children[*call_idx]; recursive_flatten_call_trace( child_node_idx, arena, node_start_idx, record_started, flatten_steps, ); } _ => {} } } } // Function to convert CallTraceStep to DebugStep pub(crate) fn convert_call_trace_ctx_to_debug_step(ctx: &CallTraceCtx) -> DebugStep { let opcode = ctx.step.op.get(); let stack = get_stack_inputs_for_opcode(opcode, ctx.step.stack.as_deref()); let memory = get_memory_input_for_opcode(opcode, ctx.step.stack.as_deref(), ctx.step.memory.as_ref()); let is_out_of_gas = matches!( ctx.step.status, Some( InstructionResult::OutOfGas | InstructionResult::MemoryOOG | InstructionResult::MemoryLimitOOG | InstructionResult::PrecompileOOG | InstructionResult::InvalidOperandOOG ) ); let depth = ctx.node.trace.depth as u64 + 1; let contract_addr = ctx.node.execution_address(); DebugStep { stack, memoryInput: memory, opcode: ctx.step.op.get(), depth, isOutOfGas: is_out_of_gas, contractAddr: contract_addr, } } // The expected `stack` here is from the trace stack, where the top of the stack // is the last value of the vector fn get_memory_input_for_opcode( opcode: u8, stack: Option<&[U256]>, memory: Option<&RecordedMemory>, ) -> Bytes { let mut memory_input = Bytes::new(); let Some(stack_data) = stack else { return memory_input }; let Some(memory_data) = memory else { return memory_input }; if let Some(accesses) = get_buffer_accesses(opcode, stack_data) && let Some((BufferKind::Memory, access)) = accesses.read { memory_input = get_slice_from_memory(memory_data.as_bytes(), access.offset, access.len); }; memory_input } // The expected `stack` here is from the trace stack, where the top of the stack // is the last value of the vector fn get_stack_inputs_for_opcode(opcode: u8, stack: Option<&[U256]>) -> Vec { let mut inputs = Vec::new(); let Some(op) = OpCode::new(opcode) else { return inputs }; let Some(stack_data) = stack else { return inputs }; let stack_input_size = op.inputs() as usize; for i in 0..stack_input_size { inputs.push(stack_data[stack_data.len() - 1 - i]); } inputs } fn get_slice_from_memory(memory: &Bytes, start_index: usize, size: usize) -> Bytes { let memory_len = memory.len(); let end_bound = start_index + size; // Return the bytes if data is within the range. if start_index < memory_len && end_bound <= memory_len { return memory.slice(start_index..end_bound); } // Pad zero bytes if attempting to load memory partially out of range. if start_index < memory_len && end_bound > memory_len { let mut result = memory.slice(start_index..memory_len).to_vec(); result.resize(size, 0u8); return Bytes::from(result); } // Return empty bytes with the size if not in range at all. Bytes::from(vec![0u8; size]) } ================================================ FILE: crates/cheatcodes/src/evm.rs ================================================ //! Implementations of [`Evm`](spec::Group::Evm) cheatcodes. use crate::{ BroadcastableTransaction, Cheatcode, Cheatcodes, CheatcodesExecutor, CheatsCtxt, Error, EthCheatCtx, Result, Vm::*, inspector::{BroadcastKind, RecordDebugStepInfo}, }; use alloy_consensus::{ Transaction as TransactionTrait, TxEnvelope, transaction::SignerRecoverable, }; use alloy_evm::{EvmEnv, FromRecoveredTx}; use alloy_genesis::{Genesis, GenesisAccount}; use alloy_network::eip2718::EIP4844_TX_TYPE_ID; use alloy_primitives::{ Address, B256, Bytes, U256, hex, keccak256, map::{B256Map, HashMap}, }; use alloy_rlp::Decodable; use alloy_sol_types::SolValue; use foundry_common::{ fs::{read_json_file, write_json_file}, slot_identifier::{ ENCODING_BYTES, ENCODING_DYN_ARRAY, ENCODING_INPLACE, ENCODING_MAPPING, SlotIdentifier, SlotInfo, }, }; use foundry_compilers::artifacts::EvmVersion; use foundry_evm_core::{ FoundryBlock, FoundryTransaction, backend::{DatabaseExt, RevertStateSnapshotAction}, constants::{CALLER, CHEATCODE_ADDRESS, HARDHAT_CONSOLE_ADDRESS, TEST_CONTRACT_ADDRESS}, env::FoundryContextExt, utils::get_blob_base_fee_update_fraction_by_spec_id, }; use foundry_evm_traces::TraceMode; use foundry_primitives::FoundryTxEnvelope; use itertools::Itertools; use rand::Rng; use revm::{ bytecode::Bytecode, context::{Block, Cfg, ContextTr, JournalTr, Transaction, TxEnv, result::ExecutionResult}, inspector::JournalExt, primitives::{KECCAK_EMPTY, hardfork::SpecId}, state::{Account, AccountStatus}, }; use std::{ collections::{BTreeMap, HashSet, btree_map::Entry}, fmt::Display, path::Path, str::FromStr, }; mod record_debug_step; use foundry_common::fmt::format_token_raw; use foundry_config::evm_spec_id; use record_debug_step::{convert_call_trace_ctx_to_debug_step, flatten_call_trace}; use serde::Serialize; mod fork; pub(crate) mod mapping; pub(crate) mod mock; pub(crate) mod prank; /// JSON-serializable log entry for `getRecordedLogsJson`. #[derive(Serialize)] #[serde(rename_all = "camelCase")] struct LogJson { /// The topics of the log, including the signature, if any. topics: Vec, /// The raw data of the log, hex-encoded with 0x prefix. data: String, /// The address of the log's emitter. emitter: String, } /// Records storage slots reads and writes. #[derive(Clone, Debug, Default)] pub struct RecordAccess { /// Storage slots reads. pub reads: HashMap>, /// Storage slots writes. pub writes: HashMap>, } impl RecordAccess { /// Records a read access to a storage slot. pub fn record_read(&mut self, target: Address, slot: U256) { self.reads.entry(target).or_default().push(slot); } /// Records a write access to a storage slot. /// /// This also records a read internally as `SSTORE` does an implicit `SLOAD`. pub fn record_write(&mut self, target: Address, slot: U256) { self.record_read(target, slot); self.writes.entry(target).or_default().push(slot); } /// Clears the recorded reads and writes. pub fn clear(&mut self) { // Also frees memory. *self = Default::default(); } } /// Records the `snapshotGas*` cheatcodes. #[derive(Clone, Debug)] pub struct GasRecord { /// The group name of the gas snapshot. pub group: String, /// The name of the gas snapshot. pub name: String, /// The total gas used in the gas snapshot. pub gas_used: u64, /// Depth at which the gas snapshot was taken. pub depth: usize, } /// Records `deal` cheatcodes #[derive(Clone, Debug)] pub struct DealRecord { /// Target of the deal. pub address: Address, /// The balance of the address before deal was applied pub old_balance: U256, /// Balance after deal was applied pub new_balance: U256, } /// Storage slot diff info. #[derive(Serialize, Default)] #[serde(rename_all = "camelCase")] struct SlotStateDiff { /// Initial storage value. previous_value: B256, /// Current storage value. new_value: B256, /// Storage layout metadata (variable name, type, offset). /// Only present when contract has storage layout output. /// This includes decoded values when available. #[serde(skip_serializing_if = "Option::is_none", flatten)] slot_info: Option, } /// Balance diff info. #[derive(Serialize, Default)] #[serde(rename_all = "camelCase")] struct BalanceDiff { /// Initial storage value. previous_value: U256, /// Current storage value. new_value: U256, } /// Nonce diff info. #[derive(Serialize, Default)] #[serde(rename_all = "camelCase")] struct NonceDiff { /// Initial nonce value. previous_value: u64, /// Current nonce value. new_value: u64, } /// Account state diff info. #[derive(Serialize, Default)] #[serde(rename_all = "camelCase")] struct AccountStateDiffs { /// Address label, if any set. label: Option, /// Contract identifier from artifact. e.g "src/Counter.sol:Counter" contract: Option, /// Account balance changes. balance_diff: Option, /// Account nonce changes. nonce_diff: Option, /// State changes, per slot. state_diff: BTreeMap, } impl Display for AccountStateDiffs { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> eyre::Result<(), std::fmt::Error> { // Print changed account. if let Some(label) = &self.label { writeln!(f, "label: {label}")?; } if let Some(contract) = &self.contract { writeln!(f, "contract: {contract}")?; } // Print balance diff if changed. if let Some(balance_diff) = &self.balance_diff && balance_diff.previous_value != balance_diff.new_value { writeln!( f, "- balance diff: {} → {}", balance_diff.previous_value, balance_diff.new_value )?; } // Print nonce diff if changed. if let Some(nonce_diff) = &self.nonce_diff && nonce_diff.previous_value != nonce_diff.new_value { writeln!(f, "- nonce diff: {} → {}", nonce_diff.previous_value, nonce_diff.new_value)?; } // Print state diff if any. if !&self.state_diff.is_empty() { writeln!(f, "- state diff:")?; for (slot, slot_changes) in &self.state_diff { match &slot_changes.slot_info { Some(slot_info) => { if let Some(decoded) = &slot_info.decoded { // Have slot info with decoded values - show decoded values writeln!( f, "@ {slot} ({}, {}): {} → {}", slot_info.label, slot_info.slot_type.dyn_sol_type, format_token_raw(&decoded.previous_value), format_token_raw(&decoded.new_value) )?; } else { // Have slot info but no decoded values - show raw hex values writeln!( f, "@ {slot} ({}, {}): {} → {}", slot_info.label, slot_info.slot_type.dyn_sol_type, slot_changes.previous_value, slot_changes.new_value )?; } } None => { // No slot info - show raw hex values writeln!( f, "@ {slot}: {} → {}", slot_changes.previous_value, slot_changes.new_value )?; } } } } Ok(()) } } impl Cheatcode for addrCall { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { privateKey } = self; let wallet = super::crypto::parse_wallet(privateKey)?; Ok(wallet.address().abi_encode()) } } impl Cheatcode for getNonce_0Call { fn apply_stateful>( &self, ccx: &mut CheatsCtxt<'_, CTX>, ) -> Result { let Self { account } = self; get_nonce(ccx, account) } } impl Cheatcode for getNonce_1Call { fn apply_stateful>( &self, ccx: &mut CheatsCtxt<'_, CTX>, ) -> Result { let Self { wallet } = self; get_nonce(ccx, &wallet.addr) } } impl Cheatcode for loadCall { fn apply_stateful>( &self, ccx: &mut CheatsCtxt<'_, CTX>, ) -> Result { let Self { target, slot } = *self; ccx.ensure_not_precompile(&target)?; ccx.ecx.journal_mut().load_account(target)?; let mut val = ccx .ecx .journal_mut() .sload(target, slot.into()) .map_err(|e| fmt_err!("failed to load storage slot: {:?}", e))?; 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().random(); ccx.state.arbitrary_storage.as_mut().unwrap().save( ccx.ecx, target, slot.into(), rand_value, ); val.data = rand_value; } else if ccx.state.is_arbitrary_storage_copy(&target) { // 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().random(); val.data = ccx.state.arbitrary_storage.as_mut().unwrap().copy( ccx.ecx, target, slot.into(), rand_value, ); } } Ok(val.abi_encode()) } } impl Cheatcode for loadAllocsCall { fn apply_stateful>( &self, ccx: &mut CheatsCtxt<'_, CTX>, ) -> Result { let Self { pathToAllocsJson } = self; let path = Path::new(pathToAllocsJson); ensure!(path.exists(), "allocs file does not exist: {pathToAllocsJson}"); // Let's first assume we're reading a file with only the allocs. let allocs: BTreeMap = match read_json_file(path) { Ok(allocs) => allocs, Err(_) => { // Let's try and read from a genesis file, and extract allocs. let genesis = read_json_file::(path)?; genesis.alloc } }; // Then, load the allocs into the database. let (db, inner) = ccx.ecx.db_journal_inner_mut(); db.load_allocs(&allocs, inner) .map(|()| Vec::default()) .map_err(|e| fmt_err!("failed to load allocs: {e}")) } } impl Cheatcode for cloneAccountCall { fn apply_stateful>( &self, ccx: &mut CheatsCtxt<'_, CTX>, ) -> Result { let Self { source, target } = self; let account = ccx.ecx.journal_mut().load_account(*source)?; let genesis = genesis_account(account.data); let (db, inner) = ccx.ecx.db_journal_inner_mut(); db.clone_account(&genesis, target, inner)?; // Cloned account should persist in forked envs. ccx.ecx.db_mut().add_persistent_account(*target); Ok(Default::default()) } } impl Cheatcode for dumpStateCall { fn apply_stateful>( &self, ccx: &mut CheatsCtxt<'_, CTX>, ) -> Result { let Self { pathToStateJson } = self; let path = Path::new(pathToStateJson); // Do not include system account or empty accounts in the dump. let skip = |key: &Address, val: &Account| { key == &CHEATCODE_ADDRESS || key == &CALLER || key == &HARDHAT_CONSOLE_ADDRESS || key == &TEST_CONTRACT_ADDRESS || key == &ccx.caller || key == &ccx.state.config.evm_opts.sender || val.is_empty() }; let alloc = ccx .ecx .journal_mut() .evm_state_mut() .iter_mut() .filter(|(key, val)| !skip(key, val)) .map(|(key, val)| (key, genesis_account(val))) .collect::>(); write_json_file(path, &alloc)?; Ok(Default::default()) } } impl Cheatcode for recordCall { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self {} = self; state.recording_accesses = true; state.accesses.clear(); Ok(Default::default()) } } impl Cheatcode for stopRecordCall { fn apply(&self, state: &mut Cheatcodes) -> Result { state.recording_accesses = false; Ok(Default::default()) } } impl Cheatcode for accessesCall { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { target } = *self; let result = ( state.accesses.reads.entry(target).or_default().as_slice(), state.accesses.writes.entry(target).or_default().as_slice(), ); Ok(result.abi_encode_params()) } } impl Cheatcode for recordLogsCall { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self {} = self; state.recorded_logs = Some(Default::default()); Ok(Default::default()) } } impl Cheatcode for getRecordedLogsCall { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self {} = self; Ok(state.recorded_logs.replace(Default::default()).unwrap_or_default().abi_encode()) } } impl Cheatcode for getRecordedLogsJsonCall { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self {} = self; let logs = state.recorded_logs.replace(Default::default()).unwrap_or_default(); let json_logs: Vec<_> = logs .into_iter() .map(|log| LogJson { topics: log.topics.iter().map(|t| format!("{t}")).collect(), data: hex::encode_prefixed(&log.data), emitter: format!("{}", log.emitter), }) .collect(); Ok(serde_json::to_string(&json_logs)?.abi_encode()) } } impl Cheatcode for pauseGasMeteringCall { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self {} = self; state.gas_metering.paused = true; Ok(Default::default()) } } impl Cheatcode for resumeGasMeteringCall { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self {} = self; state.gas_metering.resume(); Ok(Default::default()) } } impl Cheatcode for resetGasMeteringCall { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self {} = self; state.gas_metering.reset(); Ok(Default::default()) } } impl Cheatcode for lastCallGasCall { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self {} = self; let Some(last_call_gas) = &state.gas_metering.last_call_gas else { bail!("no external call was made yet"); }; Ok(last_call_gas.abi_encode()) } } impl Cheatcode for getChainIdCall { fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self {} = self; Ok(U256::from(ccx.ecx.cfg().chain_id()).abi_encode()) } } impl Cheatcode for chainIdCall { fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { newChainId } = self; ensure!(*newChainId <= U256::from(u64::MAX), "chain ID must be less than 2^64"); ccx.ecx.cfg_mut().chain_id = newChainId.to(); Ok(Default::default()) } } impl Cheatcode for coinbaseCall { fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { newCoinbase } = self; ccx.ecx.block_mut().set_beneficiary(*newCoinbase); Ok(Default::default()) } } impl Cheatcode for difficultyCall { fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { newDifficulty } = self; ensure!( ccx.ecx.cfg().spec().into() < 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.block_mut().set_difficulty(*newDifficulty); Ok(Default::default()) } } impl Cheatcode for feeCall { fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { newBasefee } = self; ensure!(*newBasefee <= U256::from(u64::MAX), "base fee must be less than 2^64"); ccx.ecx.block_mut().set_basefee(newBasefee.saturating_to()); Ok(Default::default()) } } impl Cheatcode for prevrandao_0Call { fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { newPrevrandao } = self; ensure!( ccx.ecx.cfg().spec().into() >= 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.block_mut().set_prevrandao(Some(*newPrevrandao)); Ok(Default::default()) } } impl Cheatcode for prevrandao_1Call { fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { newPrevrandao } = self; ensure!( ccx.ecx.cfg().spec().into() >= 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.block_mut().set_prevrandao(Some((*newPrevrandao).into())); Ok(Default::default()) } } impl Cheatcode for blobhashesCall { fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { hashes } = self; ensure!( ccx.ecx.cfg().spec().into() >= SpecId::CANCUN, "`blobhashes` is not supported before the Cancun hard fork; \ see EIP-4844: https://eips.ethereum.org/EIPS/eip-4844" ); ccx.ecx.tx_mut().set_blob_hashes(hashes.clone()); // force this as 4844 txtype ccx.ecx.tx_mut().set_tx_type(EIP4844_TX_TYPE_ID); Ok(Default::default()) } } impl Cheatcode for getBlobhashesCall { fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self {} = self; ensure!( ccx.ecx.cfg().spec().into() >= SpecId::CANCUN, "`getBlobhashes` is not supported before the Cancun hard fork; \ see EIP-4844: https://eips.ethereum.org/EIPS/eip-4844" ); Ok(ccx.ecx.tx().blob_versioned_hashes().to_vec().abi_encode()) } } impl Cheatcode for rollCall { fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { newHeight } = self; ccx.ecx.block_mut().set_number(*newHeight); Ok(Default::default()) } } impl Cheatcode for getBlockNumberCall { fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self {} = self; Ok(ccx.ecx.block().number().abi_encode()) } } impl Cheatcode for txGasPriceCall { fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { newGasPrice } = self; ensure!(*newGasPrice <= U256::from(u64::MAX), "gas price must be less than 2^64"); ccx.ecx.tx_mut().set_gas_price(newGasPrice.saturating_to()); Ok(Default::default()) } } impl Cheatcode for warpCall { fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { newTimestamp } = self; ccx.ecx.block_mut().set_timestamp(*newTimestamp); Ok(Default::default()) } } impl Cheatcode for getBlockTimestampCall { fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self {} = self; Ok(ccx.ecx.block().timestamp().abi_encode()) } } impl Cheatcode for blobBaseFeeCall { fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { newBlobBaseFee } = self; ensure!( ccx.ecx.cfg().spec().into() >= SpecId::CANCUN, "`blobBaseFee` is not supported before the Cancun hard fork; \ see EIP-4844: https://eips.ethereum.org/EIPS/eip-4844" ); let spec: SpecId = ccx.ecx.cfg().spec().into(); ccx.ecx.block_mut().set_blob_excess_gas_and_price( (*newBlobBaseFee).to(), get_blob_base_fee_update_fraction_by_spec_id(spec), ); Ok(Default::default()) } } impl Cheatcode for getBlobBaseFeeCall { fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self {} = self; Ok(ccx.ecx.block().blob_excess_gas().unwrap_or(0).abi_encode()) } } impl Cheatcode for dealCall { fn apply_stateful>( &self, ccx: &mut CheatsCtxt<'_, CTX>, ) -> Result { let Self { account: address, newBalance: new_balance } = *self; let account = journaled_account(ccx.ecx, address)?; let old_balance = std::mem::replace(&mut account.info.balance, new_balance); let record = DealRecord { address, old_balance, new_balance }; ccx.state.eth_deals.push(record); Ok(Default::default()) } } impl Cheatcode for etchCall { fn apply_stateful>( &self, ccx: &mut CheatsCtxt<'_, CTX>, ) -> Result { let Self { target, newRuntimeBytecode } = self; ccx.ensure_not_precompile(target)?; ccx.ecx.journal_mut().load_account(*target)?; let bytecode = Bytecode::new_raw_checked(newRuntimeBytecode.clone()) .map_err(|e| fmt_err!("failed to create bytecode: {e}"))?; ccx.ecx.journal_mut().set_code(*target, bytecode); Ok(Default::default()) } } impl Cheatcode for resetNonceCall { fn apply_stateful>( &self, ccx: &mut CheatsCtxt<'_, CTX>, ) -> Result { let Self { account } = self; let account = journaled_account(ccx.ecx, *account)?; // Per EIP-161, EOA nonces start at 0, but contract nonces // start at 1. Comparing by code_hash instead of code // to avoid hitting the case where account's code is None. let empty = account.info.code_hash == KECCAK_EMPTY; let nonce = if empty { 0 } else { 1 }; account.info.nonce = nonce; debug!(target: "cheatcodes", nonce, "reset"); Ok(Default::default()) } } impl Cheatcode for setNonceCall { fn apply_stateful>( &self, ccx: &mut CheatsCtxt<'_, CTX>, ) -> Result { let Self { account, newNonce } = *self; let account = journaled_account(ccx.ecx, account)?; // nonce must increment only let current = account.info.nonce; ensure!( newNonce >= current, "new nonce ({newNonce}) must be strictly equal to or higher than the \ account's current nonce ({current})" ); account.info.nonce = newNonce; Ok(Default::default()) } } impl Cheatcode for setNonceUnsafeCall { fn apply_stateful>( &self, ccx: &mut CheatsCtxt<'_, CTX>, ) -> Result { let Self { account, newNonce } = *self; let account = journaled_account(ccx.ecx, account)?; account.info.nonce = newNonce; Ok(Default::default()) } } impl Cheatcode for storeCall { fn apply_stateful>( &self, ccx: &mut CheatsCtxt<'_, CTX>, ) -> Result { let Self { target, slot, value } = *self; ccx.ensure_not_precompile(&target)?; ensure_loaded_account(ccx.ecx, target)?; ccx.ecx .journal_mut() .sstore(target, slot.into(), value.into()) .map_err(|e| fmt_err!("failed to store storage slot: {:?}", e))?; Ok(Default::default()) } } impl Cheatcode for coolCall { fn apply_stateful>( &self, ccx: &mut CheatsCtxt<'_, CTX>, ) -> Result { let Self { target } = self; if let Some(account) = ccx.ecx.journal_mut().evm_state_mut().get_mut(target) { account.unmark_touch(); account.storage.values_mut().for_each(|slot| slot.mark_cold()); } Ok(Default::default()) } } impl Cheatcode for accessListCall { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { access } = self; let access_list = access .iter() .map(|item| { let keys = item.storageKeys.iter().map(|key| B256::from(*key)).collect_vec(); alloy_rpc_types::AccessListItem { address: item.target, storage_keys: keys } }) .collect_vec(); state.access_list = Some(alloy_rpc_types::AccessList::from(access_list)); Ok(Default::default()) } } impl Cheatcode for noAccessListCall { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self {} = self; // Set to empty option in order to override previous applied access list. if state.access_list.is_some() { state.access_list = Some(alloy_rpc_types::AccessList::default()); } Ok(Default::default()) } } impl Cheatcode for warmSlotCall { fn apply_stateful>( &self, ccx: &mut CheatsCtxt<'_, CTX>, ) -> Result { let Self { target, slot } = *self; set_cold_slot(ccx, target, slot.into(), false); Ok(Default::default()) } } impl Cheatcode for coolSlotCall { fn apply_stateful>( &self, ccx: &mut CheatsCtxt<'_, CTX>, ) -> Result { let Self { target, slot } = *self; set_cold_slot(ccx, target, slot.into(), true); Ok(Default::default()) } } impl Cheatcode for readCallersCall { fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self {} = self; read_callers(ccx.state, &ccx.ecx.tx().caller(), ccx.ecx.journal().depth()) } } impl Cheatcode for snapshotValue_0Call { fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { name, value } = self; inner_value_snapshot(ccx, None, Some(name.clone()), value.to_string()) } } impl Cheatcode for snapshotValue_1Call { fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { group, name, value } = self; inner_value_snapshot(ccx, Some(group.clone()), Some(name.clone()), value.to_string()) } } impl Cheatcode for snapshotGasLastCall_0Call { fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { name } = self; let Some(last_call_gas) = &ccx.state.gas_metering.last_call_gas else { bail!("no external call was made yet"); }; inner_last_gas_snapshot(ccx, None, Some(name.clone()), last_call_gas.gasTotalUsed) } } impl Cheatcode for snapshotGasLastCall_1Call { fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { name, group } = self; let Some(last_call_gas) = &ccx.state.gas_metering.last_call_gas else { bail!("no external call was made yet"); }; inner_last_gas_snapshot( ccx, Some(group.clone()), Some(name.clone()), last_call_gas.gasTotalUsed, ) } } impl Cheatcode for startSnapshotGas_0Call { fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { name } = self; inner_start_gas_snapshot(ccx, None, Some(name.clone())) } } impl Cheatcode for startSnapshotGas_1Call { fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { group, name } = self; inner_start_gas_snapshot(ccx, Some(group.clone()), Some(name.clone())) } } impl Cheatcode for stopSnapshotGas_0Call { fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self {} = self; inner_stop_gas_snapshot(ccx, None, None) } } impl Cheatcode for stopSnapshotGas_1Call { fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { name } = self; inner_stop_gas_snapshot(ccx, None, Some(name.clone())) } } impl Cheatcode for stopSnapshotGas_2Call { fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { group, name } = self; inner_stop_gas_snapshot(ccx, Some(group.clone()), Some(name.clone())) } } // Deprecated in favor of `snapshotStateCall` impl Cheatcode for snapshotCall { fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self {} = self; inner_snapshot_state(ccx) } } impl Cheatcode for snapshotStateCall { fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self {} = self; inner_snapshot_state(ccx) } } // Deprecated in favor of `revertToStateCall` impl Cheatcode for revertToCall { fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { snapshotId } = self; inner_revert_to_state(ccx, *snapshotId) } } impl Cheatcode for revertToStateCall { fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { snapshotId } = self; inner_revert_to_state(ccx, *snapshotId) } } // Deprecated in favor of `revertToStateAndDeleteCall` impl Cheatcode for revertToAndDeleteCall { fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { snapshotId } = self; inner_revert_to_state_and_delete(ccx, *snapshotId) } } impl Cheatcode for revertToStateAndDeleteCall { fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { snapshotId } = self; inner_revert_to_state_and_delete(ccx, *snapshotId) } } // Deprecated in favor of `deleteStateSnapshotCall` impl Cheatcode for deleteSnapshotCall { fn apply_stateful>( &self, ccx: &mut CheatsCtxt<'_, CTX>, ) -> Result { let Self { snapshotId } = self; inner_delete_state_snapshot(ccx, *snapshotId) } } impl Cheatcode for deleteStateSnapshotCall { fn apply_stateful>( &self, ccx: &mut CheatsCtxt<'_, CTX>, ) -> Result { let Self { snapshotId } = self; inner_delete_state_snapshot(ccx, *snapshotId) } } // Deprecated in favor of `deleteStateSnapshotsCall` impl Cheatcode for deleteSnapshotsCall { fn apply_stateful>( &self, ccx: &mut CheatsCtxt<'_, CTX>, ) -> Result { let Self {} = self; inner_delete_state_snapshots(ccx) } } impl Cheatcode for deleteStateSnapshotsCall { fn apply_stateful>( &self, ccx: &mut CheatsCtxt<'_, CTX>, ) -> Result { let Self {} = self; inner_delete_state_snapshots(ccx) } } impl Cheatcode for startStateDiffRecordingCall { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self {} = self; state.recorded_account_diffs_stack = Some(Default::default()); // Enable mapping recording to track mapping slot accesses state.mapping_slots.get_or_insert_default(); Ok(Default::default()) } } impl Cheatcode for stopAndReturnStateDiffCall { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self {} = self; get_state_diff(state) } } impl Cheatcode for getStateDiffCall { fn apply_stateful>( &self, ccx: &mut CheatsCtxt<'_, CTX>, ) -> Result { let mut diffs = String::new(); let state_diffs = get_recorded_state_diffs(ccx); for (address, state_diffs) in state_diffs { diffs.push_str(&format!("{address}\n")); diffs.push_str(&format!("{state_diffs}\n")); } Ok(diffs.abi_encode()) } } impl Cheatcode for getStateDiffJsonCall { fn apply_stateful>( &self, ccx: &mut CheatsCtxt<'_, CTX>, ) -> Result { let state_diffs = get_recorded_state_diffs(ccx); Ok(serde_json::to_string(&state_diffs)?.abi_encode()) } } impl Cheatcode for getStorageSlotsCall { fn apply_stateful>( &self, ccx: &mut CheatsCtxt<'_, CTX>, ) -> Result { let Self { target, variableName } = self; let storage_layout = get_contract_data(ccx, *target) .and_then(|(_, data)| data.storage_layout.as_ref().map(|layout| layout.clone())) .ok_or_else(|| fmt_err!("Storage layout not available for contract at {target}. Try compiling contracts with `--extra-output storageLayout`"))?; trace!(storage = ?storage_layout.storage, "fetched storage"); let variable_name_lower = variableName.to_lowercase(); let storage = storage_layout .storage .iter() .find(|s| s.label.to_lowercase() == variable_name_lower) .ok_or_else(|| fmt_err!("variable '{variableName}' not found in storage layout"))?; let storage_type = storage_layout .types .get(&storage.storage_type) .ok_or_else(|| fmt_err!("storage type not found for variable {variableName}"))?; if storage_type.encoding == ENCODING_MAPPING || storage_type.encoding == ENCODING_DYN_ARRAY { return Err(fmt_err!( "cannot get storage slots for variables with mapping or dynamic array types" )); } let slot = U256::from_str(&storage.slot).map_err(|_| { fmt_err!("invalid slot {} format for variable {variableName}", storage.slot) })?; let mut slots = Vec::new(); // Always push the base slot slots.push(slot); if storage_type.encoding == ENCODING_INPLACE { // For inplace encoding, calculate the number of slots needed let num_bytes = U256::from_str(&storage_type.number_of_bytes).map_err(|_| { fmt_err!( "invalid number_of_bytes {} for variable {variableName}", storage_type.number_of_bytes ) })?; let num_slots = num_bytes.div_ceil(U256::from(32)); // Start from 1 since base slot is already added for i in 1..num_slots.to::() { slots.push(slot + U256::from(i)); } } if storage_type.encoding == ENCODING_BYTES { // Try to check if it's a long bytes/string by reading the current storage // value if let Ok(value) = ccx.ecx.journal_mut().sload(*target, slot) { let value_bytes = value.data.to_be_bytes::<32>(); let length_byte = value_bytes[31]; // Check if it's a long bytes/string (LSB is 1) if length_byte & 1 == 1 { // Calculate data slots for long bytes/string let length: U256 = value.data >> 1; let num_data_slots = length.to::().div_ceil(32); let data_start = U256::from_be_bytes(keccak256(B256::from(slot).0).0); for i in 0..num_data_slots { slots.push(data_start + U256::from(i)); } } } } Ok(slots.abi_encode()) } } impl Cheatcode for getStorageAccessesCall { fn apply(&self, state: &mut Cheatcodes) -> Result { let mut storage_accesses = Vec::new(); if let Some(recorded_diffs) = &state.recorded_account_diffs_stack { for account_accesses in recorded_diffs.iter().flatten() { storage_accesses.extend(account_accesses.storageAccesses.clone()); } } Ok(storage_accesses.abi_encode()) } } impl Cheatcode for broadcastRawTransactionCall { fn apply_full( &self, ccx: &mut CheatsCtxt<'_, CTX>, executor: &mut dyn CheatcodesExecutor, ) -> Result { let tx = TxEnvelope::decode(&mut self.data.as_ref()) .map_err(|err| fmt_err!("failed to decode RLP-encoded transaction: {err}"))?; let from = tx.recover_signer()?; let tx_env = FromRecoveredTx::from_recovered_tx(&tx, from); executor.transact_from_tx_on_db(ccx.state, ccx.ecx, &tx_env)?; if ccx.state.broadcast.is_some() { ccx.state.broadcastable_transactions.push_back(BroadcastableTransaction { rpc: ccx.ecx.db().active_fork_url(), from, to: Some(tx.kind()), value: tx.value(), input: tx.input().clone(), nonce: tx.nonce(), gas: Some(tx.gas_limit()), kind: BroadcastKind::Signed(Bytes::copy_from_slice(&self.data)), }); } Ok(Default::default()) } } impl Cheatcode for setBlockhashCall { fn apply_stateful>( &self, ccx: &mut CheatsCtxt<'_, CTX>, ) -> Result { let Self { blockNumber, blockHash } = *self; ensure!(blockNumber <= U256::from(u64::MAX), "blockNumber must be less than 2^64"); ensure!( blockNumber <= U256::from(ccx.ecx.block().number()), "block number must be less than or equal to the current block number" ); ccx.ecx.db_mut().set_blockhash(blockNumber, blockHash); Ok(Default::default()) } } impl Cheatcode for executeTransactionCall { fn apply_full( &self, ccx: &mut CheatsCtxt<'_, CTX>, executor: &mut dyn CheatcodesExecutor, ) -> Result { use crate::env::FORGE_CONTEXT; // Block in script contexts. if let Some(ctx) = FORGE_CONTEXT.get() && *ctx == ForgeContext::ScriptGroup { return Err(fmt_err!("executeTransaction is not allowed in forge script")); } // Decode the RLP-encoded signed transaction. let tx = FoundryTxEnvelope::decode(&mut self.rawTx.as_ref()) .map_err(|err| fmt_err!("failed to decode RLP-encoded transaction: {err}"))?; // Reject unsupported transaction types. // TODO: add support for OP deposit transactions. if matches!(tx, FoundryTxEnvelope::Deposit(_)) { return Err(fmt_err!( "OP deposit transactions are not yet supported by executeTransaction" )); } // TODO: add support for Tempo AA transactions. if matches!(tx, FoundryTxEnvelope::Tempo(_)) { return Err(fmt_err!("Tempo transactions are not yet supported by executeTransaction")); } // Recover signer from the transaction signature. let sender = tx.recover().map_err(|err| fmt_err!("failed to recover signer: {err}"))?; // Build TxEnv from the recovered transaction. let tx_env = >::from_recovered_tx(&tx, sender); // Save current env for restoration after execution. let cached_evm_env = ccx.ecx.evm_clone(); let cached_tx_env = ccx.ecx.tx_clone(); // Override env for isolated execution. ccx.ecx.block_mut().set_basefee(0); ccx.ecx.set_tx(tx_env); ccx.ecx.tx_mut().set_gas_price(0); ccx.ecx.tx_mut().set_gas_priority_fee(None); // Enable nonce checks for realistic simulation. ccx.ecx.cfg_mut().disable_nonce_check = false; // EIP-3860: enforce initcode size limit. ccx.ecx.cfg_mut().limit_contract_initcode_size = Some(revm::primitives::eip3860::MAX_INITCODE_SIZE); // Snapshot the modified env for EVM construction. let modified_evm_env = ccx.ecx.evm_clone(); let modified_tx_env = ccx.ecx.tx_clone(); // Mark as inner context so isolation mode doesn't trigger a nested transact_inner // when the inner EVM executes calls at depth == 1. executor.set_in_inner_context(true, Some(sender)); // Clone journaled state and mark all accounts/slots cold. let cold_state = { let (_, journal) = ccx.ecx.db_journal_inner_mut(); let mut state = journal.state.clone(); for (addr, acc_mut) in &mut state { if journal.warm_addresses.is_cold(addr) { acc_mut.mark_cold(); } for slot_mut in acc_mut.storage.values_mut() { slot_mut.is_cold = true; slot_mut.original_value = slot_mut.present_value; } } state }; let mut res = None; let mut nested_env = None; let mut cold_state = Some(cold_state); let modified_tx = modified_tx_env.clone(); { let (db, _) = ccx.ecx.db_journal_inner_mut(); executor.with_fresh_nested_evm( ccx.state, db, modified_evm_env, modified_tx_env, &mut |evm| { // SAFETY: closure is called exactly once by the executor. evm.journal_inner_mut().state = cold_state.take().expect("called once"); // Set depth to 1 for proper trace collection. evm.journal_inner_mut().depth = 1; res = Some(evm.transact(modified_tx.clone())); nested_env = Some(evm.to_evm_env()); Ok(()) }, )?; } let (res, mut nested_evm_env) = (res.unwrap(), nested_env.unwrap()); // Restore env, preserving cheatcode cfg/block changes from the nested EVM // but restoring the original tx and basefee (which we zeroed for the nested call) // as well as cfg overrides that were applied only for the nested execution. nested_evm_env.block_env.basefee = cached_evm_env.block_env.basefee; nested_evm_env.cfg_env.disable_nonce_check = cached_evm_env.cfg_env.disable_nonce_check; nested_evm_env.cfg_env.limit_contract_initcode_size = cached_evm_env.cfg_env.limit_contract_initcode_size; ccx.ecx.set_evm(nested_evm_env); ccx.ecx.set_tx(cached_tx_env); // Reset inner context flag. executor.set_in_inner_context(false, None); let res = res.map_err(|e| fmt_err!("transaction execution failed: {e}"))?; // Merge state changes back into the parent journaled state. for (addr, mut acc) in res.state { let Some(acc_mut) = ccx.ecx.journal_mut().evm_state_mut().get_mut(&addr) else { ccx.ecx.journal_mut().evm_state_mut().insert(addr, acc); continue; }; // Preserve warm account status from parent context. if acc.status.contains(AccountStatus::Cold) && !acc_mut.status.contains(AccountStatus::Cold) { acc.status -= AccountStatus::Cold; } acc_mut.info = acc.info; acc_mut.status |= acc.status; // Merge storage changes. for (key, val) in acc.storage { let Some(slot_mut) = acc_mut.storage.get_mut(&key) else { acc_mut.storage.insert(key, val); continue; }; slot_mut.present_value = val.present_value; slot_mut.is_cold &= val.is_cold; } } // Return output bytes. let output = match res.result { ExecutionResult::Success { output, .. } => output.into_data(), ExecutionResult::Halt { reason, .. } => { return Err(fmt_err!("transaction halted: {reason:?}")); } ExecutionResult::Revert { output, .. } => { return Err(fmt_err!("transaction reverted: {}", hex::encode_prefixed(&output))); } }; Ok(output.abi_encode()) } } impl Cheatcode for startDebugTraceRecordingCall { fn apply_full( &self, ccx: &mut CheatsCtxt<'_, CTX>, executor: &mut dyn CheatcodesExecutor, ) -> Result { let Some(tracer) = executor.tracing_inspector() else { return Err(Error::from("no tracer initiated, consider adding -vvv flag")); }; let mut info = RecordDebugStepInfo { // will be updated later start_node_idx: 0, // keep the original config to revert back later original_tracer_config: *tracer.config(), }; // turn on tracer debug configuration for recording *tracer.config_mut() = TraceMode::Debug.into_config().expect("cannot be None"); // track where the recording starts if let Some(last_node) = tracer.traces().nodes().last() { info.start_node_idx = last_node.idx; } ccx.state.record_debug_steps_info = Some(info); Ok(Default::default()) } } impl Cheatcode for stopAndReturnDebugTraceRecordingCall { fn apply_full( &self, ccx: &mut CheatsCtxt<'_, CTX>, executor: &mut dyn CheatcodesExecutor, ) -> Result { let Some(tracer) = executor.tracing_inspector() else { return Err(Error::from("no tracer initiated, consider adding -vvv flag")); }; let Some(record_info) = ccx.state.record_debug_steps_info else { return Err(Error::from("nothing recorded")); }; // Use the trace nodes to flatten the call trace let root = tracer.traces(); let steps = flatten_call_trace(0, root, record_info.start_node_idx); let debug_steps: Vec = steps.iter().map(|step| convert_call_trace_ctx_to_debug_step(step)).collect(); // Free up memory by clearing the steps if they are not recorded outside of cheatcode usage. if !record_info.original_tracer_config.record_steps { tracer.traces_mut().nodes_mut().iter_mut().for_each(|node| { node.trace.steps = Vec::new(); node.logs = Vec::new(); node.ordering = Vec::new(); }); } // Revert the tracer config to the one before recording tracer.update_config(|_config| record_info.original_tracer_config); // Clean up the recording info ccx.state.record_debug_steps_info = None; Ok(debug_steps.abi_encode()) } } impl Cheatcode for setEvmVersionCall { fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { evm } = self; let spec_id = evm_spec_id( EvmVersion::from_str(evm) .map_err(|_| Error::from(format!("invalid evm version {evm}")))?, ); ccx.state.execution_evm_version = Some(spec_id); Ok(Default::default()) } } impl Cheatcode for getEvmVersionCall { fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let spec: SpecId = ccx.ecx.cfg().spec().into(); Ok(spec.to_string().to_lowercase().abi_encode()) } } pub(super) fn get_nonce>( ccx: &mut CheatsCtxt<'_, CTX>, address: &Address, ) -> Result { let account = ccx.ecx.journal_mut().load_account(*address)?; Ok(account.data.info.nonce.abi_encode()) } fn inner_snapshot_state(ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let evm_env = EvmEnv { cfg_env: ccx.ecx.cfg_mut().clone(), block_env: ccx.ecx.block_mut().clone() }; let (db, inner) = ccx.ecx.db_journal_inner_mut(); let id = db.snapshot_state(inner, &evm_env); Ok(id.abi_encode()) } fn inner_revert_to_state( ccx: &mut CheatsCtxt<'_, CTX>, snapshot_id: U256, ) -> Result { let mut evm_env = ccx.ecx.evm_clone(); let mut tx_env = ccx.ecx.tx_clone(); let (db, inner) = ccx.ecx.db_journal_inner_mut(); if let Some(restored) = db.revert_state( snapshot_id, inner, &mut evm_env, &mut tx_env, RevertStateSnapshotAction::RevertKeep, ) { *inner = restored; ccx.ecx.set_evm(evm_env); ccx.ecx.set_tx(tx_env); Ok(true.abi_encode()) } else { Ok(false.abi_encode()) } } fn inner_revert_to_state_and_delete( ccx: &mut CheatsCtxt<'_, CTX>, snapshot_id: U256, ) -> Result { let mut evm_env = ccx.ecx.evm_clone(); let mut tx_env = ccx.ecx.tx_clone(); let (db, inner) = ccx.ecx.db_journal_inner_mut(); if let Some(restored) = db.revert_state( snapshot_id, inner, &mut evm_env, &mut tx_env, RevertStateSnapshotAction::RevertRemove, ) { *inner = restored; ccx.ecx.set_evm(evm_env); ccx.ecx.set_tx(tx_env); Ok(true.abi_encode()) } else { Ok(false.abi_encode()) } } fn inner_delete_state_snapshot>( ccx: &mut CheatsCtxt<'_, CTX>, snapshot_id: U256, ) -> Result { let result = ccx.ecx.db_mut().delete_state_snapshot(snapshot_id); Ok(result.abi_encode()) } fn inner_delete_state_snapshots>( ccx: &mut CheatsCtxt<'_, CTX>, ) -> Result { ccx.ecx.db_mut().delete_state_snapshots(); Ok(Default::default()) } fn inner_value_snapshot( ccx: &mut CheatsCtxt<'_, CTX>, group: Option, name: Option, value: String, ) -> Result { let (group, name) = derive_snapshot_name(ccx, group, name); ccx.state.gas_snapshots.entry(group).or_default().insert(name, value); Ok(Default::default()) } fn inner_last_gas_snapshot( ccx: &mut CheatsCtxt<'_, CTX>, group: Option, name: Option, value: u64, ) -> Result { let (group, name) = derive_snapshot_name(ccx, group, name); ccx.state.gas_snapshots.entry(group).or_default().insert(name, value.to_string()); Ok(value.abi_encode()) } fn inner_start_gas_snapshot( ccx: &mut CheatsCtxt<'_, CTX>, group: Option, name: Option, ) -> Result { // Revert if there is an active gas snapshot as we can only have one active snapshot at a time. if let Some((group, name)) = &ccx.state.gas_metering.active_gas_snapshot { bail!("gas snapshot was already started with group: {group} and name: {name}"); } let (group, name) = derive_snapshot_name(ccx, group, name); ccx.state.gas_metering.gas_records.push(GasRecord { group: group.clone(), name: name.clone(), gas_used: 0, depth: ccx.ecx.journal().depth(), }); ccx.state.gas_metering.active_gas_snapshot = Some((group, name)); ccx.state.gas_metering.start(); Ok(Default::default()) } fn inner_stop_gas_snapshot( ccx: &mut CheatsCtxt<'_, CTX>, group: Option, name: Option, ) -> Result { // If group and name are not provided, use the last snapshot group and name. let (group, name) = group .zip(name) .or_else(|| ccx.state.gas_metering.active_gas_snapshot.clone()) .ok_or_else(|| fmt_err!("no active gas snapshot; call `startGasSnapshot` first"))?; if let Some(record) = ccx .state .gas_metering .gas_records .iter_mut() .find(|record| record.group == group && record.name == name) { // Calculate the gas used since the snapshot was started. // We subtract 171 from the gas used to account for gas used by the snapshot itself. let value = record.gas_used.saturating_sub(171); ccx.state .gas_snapshots .entry(group.clone()) .or_default() .insert(name.clone(), value.to_string()); // Stop the gas metering. ccx.state.gas_metering.stop(); // Remove the gas record. ccx.state .gas_metering .gas_records .retain(|record| record.group != group && record.name != name); // Clear last snapshot cache if we have an exact match. if let Some((snapshot_group, snapshot_name)) = &ccx.state.gas_metering.active_gas_snapshot && snapshot_group == &group && snapshot_name == &name { ccx.state.gas_metering.active_gas_snapshot = None; } Ok(value.abi_encode()) } else { bail!("no gas snapshot was started with the name: {name} in group: {group}"); } } // Derives the snapshot group and name from the provided group and name or the running contract. fn derive_snapshot_name( ccx: &CheatsCtxt<'_, CTX>, group: Option, name: Option, ) -> (String, String) { let group = group.unwrap_or_else(|| { ccx.state.config.running_artifact.clone().expect("expected running contract").name }); let name = name.unwrap_or_else(|| "default".to_string()); (group, name) } /// Reads the current caller information and returns the current [CallerMode], `msg.sender` and /// `tx.origin`. /// /// Depending on the current caller mode, one of the following results will be returned: /// - If there is an active prank: /// - caller_mode will be equal to: /// - [CallerMode::Prank] if the prank has been set with `vm.prank(..)`. /// - [CallerMode::RecurrentPrank] if the prank has been set with `vm.startPrank(..)`. /// - `msg.sender` will be equal to the address set for the prank. /// - `tx.origin` will be equal to the default sender address unless an alternative one has been /// set when configuring the prank. /// /// - If there is an active broadcast: /// - caller_mode will be equal to: /// - [CallerMode::Broadcast] if the broadcast has been set with `vm.broadcast(..)`. /// - [CallerMode::RecurrentBroadcast] if the broadcast has been set with /// `vm.startBroadcast(..)`. /// - `msg.sender` and `tx.origin` will be equal to the address provided when setting the /// broadcast. /// /// - 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: usize) -> Result { let mut mode = CallerMode::None; let mut new_caller = default_sender; let mut new_origin = default_sender; if let Some(prank) = state.get_prank(call_depth) { mode = if prank.single_call { CallerMode::Prank } else { CallerMode::RecurrentPrank }; new_caller = &prank.new_caller; if let Some(new) = &prank.new_origin { new_origin = new; } } else if let Some(broadcast) = &state.broadcast { mode = if broadcast.single_call { CallerMode::Broadcast } else { CallerMode::RecurrentBroadcast }; new_caller = &broadcast.new_origin; new_origin = &broadcast.new_origin; } Ok((mode, new_caller, new_origin).abi_encode_params()) } /// Ensures the `Account` is loaded and touched. pub(super) fn journaled_account>( ecx: &mut CTX, addr: Address, ) -> Result<&mut Account> { ensure_loaded_account(ecx, addr)?; Ok(ecx.journal_mut().evm_state_mut().get_mut(&addr).expect("account is loaded")) } pub(super) fn ensure_loaded_account>( ecx: &mut CTX, addr: Address, ) -> Result<()> { ecx.journal_mut().load_account(addr)?; ecx.journal_mut().touch_account(addr); Ok(()) } /// Consumes recorded account accesses and returns them as an abi encoded /// array of [AccountAccess]. If there are no accounts were /// recorded as accessed, an abi encoded empty array is returned. /// /// In the case where `stopAndReturnStateDiff` is called at a lower /// depth than `startStateDiffRecording`, multiple `Vec` /// will be flattened, preserving the order of the accesses. fn get_state_diff(state: &mut Cheatcodes) -> Result { let res = state .recorded_account_diffs_stack .replace(Default::default()) .unwrap_or_default() .into_iter() .flatten() .collect::>(); Ok(res.abi_encode()) } /// Helper function that creates a `GenesisAccount` from a regular `Account`. fn genesis_account(account: &Account) -> GenesisAccount { GenesisAccount { nonce: Some(account.info.nonce), balance: account.info.balance, code: account.info.code.as_ref().map(|o| o.original_bytes()), storage: Some( account .storage .iter() .map(|(k, v)| (B256::from(*k), B256::from(v.present_value()))) .collect(), ), private_key: None, } } /// Helper function to returns state diffs recorded for each changed account. fn get_recorded_state_diffs>( ccx: &mut CheatsCtxt<'_, CTX>, ) -> BTreeMap { let mut state_diffs: BTreeMap = BTreeMap::default(); // First, collect all unique addresses we need to look up let mut addresses_to_lookup = HashSet::new(); if let Some(records) = &ccx.state.recorded_account_diffs_stack { for account_access in records.iter().flatten() { if !account_access.storageAccesses.is_empty() || account_access.oldBalance != account_access.newBalance { addresses_to_lookup.insert(account_access.account); for storage_access in &account_access.storageAccesses { if storage_access.isWrite && !storage_access.reverted { addresses_to_lookup.insert(storage_access.account); } } } } } // Look up contract names and storage layouts for all addresses let mut contract_names = HashMap::new(); let mut storage_layouts = HashMap::new(); for address in addresses_to_lookup { if let Some((artifact_id, contract_data)) = get_contract_data(ccx, address) { contract_names.insert(address, artifact_id.identifier()); // Also get storage layout if available if let Some(storage_layout) = &contract_data.storage_layout { storage_layouts.insert(address, storage_layout.clone()); } } } // Now process the records if let Some(records) = &ccx.state.recorded_account_diffs_stack { records .iter() .flatten() .filter(|account_access| { !account_access.storageAccesses.is_empty() || account_access.oldBalance != account_access.newBalance || account_access.oldNonce != account_access.newNonce }) .for_each(|account_access| { // Record account balance diffs. if account_access.oldBalance != account_access.newBalance { let account_diff = state_diffs.entry(account_access.account).or_insert_with(|| { AccountStateDiffs { label: ccx.state.labels.get(&account_access.account).cloned(), contract: contract_names.get(&account_access.account).cloned(), ..Default::default() } }); // Update balance diff. Do not overwrite the initial balance if already set. if let Some(diff) = &mut account_diff.balance_diff { diff.new_value = account_access.newBalance; } else { account_diff.balance_diff = Some(BalanceDiff { previous_value: account_access.oldBalance, new_value: account_access.newBalance, }); } } // Record account nonce diffs. if account_access.oldNonce != account_access.newNonce { let account_diff = state_diffs.entry(account_access.account).or_insert_with(|| { AccountStateDiffs { label: ccx.state.labels.get(&account_access.account).cloned(), contract: contract_names.get(&account_access.account).cloned(), ..Default::default() } }); // Update nonce diff. Do not overwrite the initial nonce if already set. if let Some(diff) = &mut account_diff.nonce_diff { diff.new_value = account_access.newNonce; } else { account_diff.nonce_diff = Some(NonceDiff { previous_value: account_access.oldNonce, new_value: account_access.newNonce, }); } } // Collect all storage accesses for this account let raw_changes_by_slot = account_access .storageAccesses .iter() .filter_map(|access| { (access.isWrite && !access.reverted) .then_some((access.slot, (access.previousValue, access.newValue))) }) .collect::>(); // Record account state diffs. for storage_access in &account_access.storageAccesses { if storage_access.isWrite && !storage_access.reverted { let account_diff = state_diffs .entry(storage_access.account) .or_insert_with(|| AccountStateDiffs { label: ccx.state.labels.get(&storage_access.account).cloned(), contract: contract_names.get(&storage_access.account).cloned(), ..Default::default() }); let layout = storage_layouts.get(&storage_access.account); // Update state diff. Do not overwrite the initial value if already set. let entry = match account_diff.state_diff.entry(storage_access.slot) { Entry::Vacant(slot_state_diff) => { // Get storage layout info for this slot // Include mapping slots if available for the account let mapping_slots = ccx .state .mapping_slots .as_ref() .and_then(|slots| slots.get(&storage_access.account)); let slot_info = layout.and_then(|layout| { let decoder = SlotIdentifier::new(layout.clone()); decoder.identify(&storage_access.slot, mapping_slots).or_else( || { // Create a map of new values for bytes/string // identification. These values are used to determine // the length of the data which helps determine how many // slots to search let current_base_slot_values = raw_changes_by_slot .iter() .map(|(slot, (_, new_val))| (*slot, *new_val)) .collect::>(); decoder.identify_bytes_or_string( &storage_access.slot, ¤t_base_slot_values, ) }, ) }); slot_state_diff.insert(SlotStateDiff { previous_value: storage_access.previousValue, new_value: storage_access.newValue, slot_info, }) } Entry::Occupied(slot_state_diff) => { let entry = slot_state_diff.into_mut(); entry.new_value = storage_access.newValue; entry } }; // Update decoded values if we have slot info if let Some(slot_info) = &mut entry.slot_info { slot_info.decode_values(entry.previous_value, storage_access.newValue); if slot_info.is_bytes_or_string() { slot_info.decode_bytes_or_string_values( &storage_access.slot, &raw_changes_by_slot, ); } } } } }); } state_diffs } /// EIP-1967 implementation storage slot const EIP1967_IMPL_SLOT: &str = "360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc"; /// EIP-1822 UUPS implementation storage slot: keccak256("PROXIABLE") const EIP1822_PROXIABLE_SLOT: &str = "c5f16f0fcc639fa48a6947836d9850f504798523bf8c9a3a87d5876cf622bcf7"; /// Helper function to get the contract data from the deployed code at an address. fn get_contract_data<'a, CTX: ContextTr>( ccx: &'a mut CheatsCtxt<'_, CTX>, address: Address, ) -> Option<(&'a foundry_compilers::ArtifactId, &'a foundry_common::contracts::ContractData)> { // Check if we have available artifacts to match against let artifacts = ccx.state.config.available_artifacts.as_ref()?; // Try to load the account and get its code let account = ccx.ecx.journal_mut().load_account(address).ok()?; let code = account.data.info.code.as_ref()?; // Skip if code is empty if code.is_empty() { return None; } // Try to find the artifact by deployed code let code_bytes = code.original_bytes(); // First check for proxy patterns let hex_str = hex::encode(&code_bytes); let find_by_suffix = |suffix: &str| artifacts.iter().find(|(a, _)| a.identifier().ends_with(suffix)); // Simple proxy detection based on storage slot patterns if hex_str.contains(EIP1967_IMPL_SLOT) && let Some(result) = find_by_suffix(":TransparentUpgradeableProxy") { return Some(result); } else if hex_str.contains(EIP1822_PROXIABLE_SLOT) && let Some(result) = find_by_suffix(":UUPSUpgradeable") { return Some(result); } // Try exact match if let Some(result) = artifacts.find_by_deployed_code_exact(&code_bytes) { return Some(result); } // Fallback to fuzzy matching if exact match fails artifacts.find_by_deployed_code(&code_bytes) } /// Helper function to set / unset cold storage slot of the target address. fn set_cold_slot>( ccx: &mut CheatsCtxt<'_, CTX>, target: Address, slot: U256, cold: bool, ) { if let Some(account) = ccx.ecx.journal_mut().evm_state_mut().get_mut(&target) && let Some(storage_slot) = account.storage.get_mut(&slot) { storage_slot.is_cold = cold; } } ================================================ FILE: crates/cheatcodes/src/fs.rs ================================================ //! Implementations of [`Filesystem`](spec::Group::Filesystem) cheatcodes. use super::string::parse; use crate::{ Cheatcode, Cheatcodes, CheatcodesExecutor, CheatsCtxt, EthCheatCtx, Result, Vm::*, inspector::exec_create, }; use alloy_dyn_abi::DynSolType; use alloy_json_abi::ContractObject; use alloy_network::{Ethereum, Network, ReceiptResponse}; use alloy_primitives::{Bytes, U256, hex, map::Entry}; use alloy_sol_types::SolValue; use dialoguer::{Input, Password}; use forge_script_sequence::{BroadcastReader, TransactionWithMetadata}; use foundry_common::fs; use foundry_config::fs_permissions::FsAccessKind; use revm::{ context::{Cfg, ContextTr, CreateScheme, JournalTr}, interpreter::CreateInputs, }; use revm_inspectors::tracing::types::CallKind; use semver::Version; use std::{ io::{BufRead, BufReader}, path::{Path, PathBuf}, process::Command, sync::mpsc, thread, time::{SystemTime, UNIX_EPOCH}, }; use walkdir::WalkDir; impl Cheatcode for existsCall { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { path } = self; let path = state.config.ensure_path_allowed(path, FsAccessKind::Read)?; Ok(path.exists().abi_encode()) } } impl Cheatcode for fsMetadataCall { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { path } = self; let path = state.config.ensure_path_allowed(path, FsAccessKind::Read)?; let metadata = path.metadata()?; // These fields not available on all platforms; default to 0 let [modified, accessed, created] = [metadata.modified(), metadata.accessed(), metadata.created()].map(|time| { time.unwrap_or(UNIX_EPOCH).duration_since(UNIX_EPOCH).unwrap_or_default().as_secs() }); Ok(FsMetadata { isDir: metadata.is_dir(), isSymlink: metadata.is_symlink(), length: U256::from(metadata.len()), readOnly: metadata.permissions().readonly(), modified: U256::from(modified), accessed: U256::from(accessed), created: U256::from(created), } .abi_encode()) } } impl Cheatcode for isDirCall { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { path } = self; let path = state.config.ensure_path_allowed(path, FsAccessKind::Read)?; Ok(path.is_dir().abi_encode()) } } impl Cheatcode for isFileCall { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { path } = self; let path = state.config.ensure_path_allowed(path, FsAccessKind::Read)?; Ok(path.is_file().abi_encode()) } } impl Cheatcode for projectRootCall { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self {} = self; Ok(state.config.root.display().to_string().abi_encode()) } } impl Cheatcode for currentFilePathCall { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self {} = self; let artifact = state .config .running_artifact .as_ref() .ok_or_else(|| fmt_err!("no running contract found"))?; let relative = artifact.source.strip_prefix(&state.config.root).unwrap_or(&artifact.source); Ok(relative.display().to_string().abi_encode()) } } impl Cheatcode for unixTimeCall { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self {} = self; let difference = SystemTime::now() .duration_since(UNIX_EPOCH) .map_err(|e| fmt_err!("failed getting Unix timestamp: {e}"))?; Ok(difference.as_millis().abi_encode()) } } impl Cheatcode for closeFileCall { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { path } = self; let path = state.config.ensure_path_allowed(path, FsAccessKind::Read)?; state.test_context.opened_read_files.remove(&path); Ok(Default::default()) } } impl Cheatcode for copyFileCall { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { from, to } = self; let from = state.config.ensure_path_allowed(from, FsAccessKind::Read)?; let to = state.config.ensure_path_allowed(to, FsAccessKind::Write)?; state.config.ensure_not_foundry_toml(&to)?; let n = fs::copy(from, to)?; Ok(n.abi_encode()) } } impl Cheatcode for createDirCall { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { path, recursive } = self; let path = state.config.ensure_path_allowed(path, FsAccessKind::Write)?; if *recursive { fs::create_dir_all(path) } else { fs::create_dir(path) }?; Ok(Default::default()) } } impl Cheatcode for readDir_0Call { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { path } = self; read_dir(state, path.as_ref(), 1, false) } } impl Cheatcode for readDir_1Call { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { path, maxDepth } = self; read_dir(state, path.as_ref(), *maxDepth, false) } } impl Cheatcode for readDir_2Call { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { path, maxDepth, followLinks } = self; read_dir(state, path.as_ref(), *maxDepth, *followLinks) } } impl Cheatcode for readFileCall { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { path } = self; let path = state.config.ensure_path_allowed(path, FsAccessKind::Read)?; Ok(fs::locked_read_to_string(path)?.abi_encode()) } } impl Cheatcode for readFileBinaryCall { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { path } = self; let path = state.config.ensure_path_allowed(path, FsAccessKind::Read)?; Ok(fs::locked_read(path)?.abi_encode()) } } impl Cheatcode for readLineCall { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { path } = self; 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.test_context.opened_read_files.entry(path.clone()) { Entry::Occupied(entry) => entry.into_mut(), Entry::Vacant(entry) => entry.insert(BufReader::new(fs::open(path)?)), }; let mut line: String = String::new(); reader.read_line(&mut line)?; // Remove trailing newline character, preserving others for cases where it may be important if line.ends_with('\n') { line.pop(); if line.ends_with('\r') { line.pop(); } } Ok(line.abi_encode()) } } impl Cheatcode for readLinkCall { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { linkPath: path } = self; let path = state.config.ensure_path_allowed(path, FsAccessKind::Read)?; let target = fs::read_link(path)?; Ok(target.display().to_string().abi_encode()) } } impl Cheatcode for removeDirCall { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { path, recursive } = self; let path = state.config.ensure_path_allowed(path, FsAccessKind::Write)?; if *recursive { fs::remove_dir_all(path) } else { fs::remove_dir(path) }?; Ok(Default::default()) } } impl Cheatcode for removeFileCall { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { path } = self; let path = state.config.ensure_path_allowed(path, FsAccessKind::Write)?; state.config.ensure_not_foundry_toml(&path)?; // also remove from the set if opened previously state.test_context.opened_read_files.remove(&path); if state.fs_commit { fs::remove_file(&path)?; } Ok(Default::default()) } } impl Cheatcode for writeFileCall { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { path, data } = self; write_file(state, path.as_ref(), data.as_bytes()) } } impl Cheatcode for writeFileBinaryCall { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { path, data } = self; write_file(state, path.as_ref(), data) } } impl Cheatcode for writeLineCall { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { path, data: line } = self; let path = state.config.ensure_path_allowed(path, FsAccessKind::Write)?; state.config.ensure_not_foundry_toml(&path)?; if state.fs_commit { fs::locked_write_line(path, line)?; } Ok(Default::default()) } } impl Cheatcode for getArtifactPathByCodeCall { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { code } = self; let (artifact_id, _) = state .config .available_artifacts .as_ref() .and_then(|artifacts| artifacts.find_by_creation_code(code)) .ok_or_else(|| fmt_err!("no matching artifact found"))?; Ok(artifact_id.path.to_string_lossy().abi_encode()) } } impl Cheatcode for getArtifactPathByDeployedCodeCall { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { deployedCode } = self; let (artifact_id, _) = state .config .available_artifacts .as_ref() .and_then(|artifacts| artifacts.find_by_deployed_code(deployedCode)) .ok_or_else(|| fmt_err!("no matching artifact found"))?; Ok(artifact_id.path.to_string_lossy().abi_encode()) } } impl Cheatcode for getCodeCall { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { artifactPath: path } = self; Ok(get_artifact_code(state, path, false)?.abi_encode()) } } impl Cheatcode for getDeployedCodeCall { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { artifactPath: path } = self; Ok(get_artifact_code(state, path, true)?.abi_encode()) } } impl Cheatcode for deployCode_0Call { fn apply_full( &self, ccx: &mut CheatsCtxt<'_, CTX>, executor: &mut dyn CheatcodesExecutor, ) -> Result { let Self { artifactPath: path } = self; deploy_code(ccx, executor, path, None, None, None) } } impl Cheatcode for deployCode_1Call { fn apply_full( &self, ccx: &mut CheatsCtxt<'_, CTX>, executor: &mut dyn CheatcodesExecutor, ) -> Result { let Self { artifactPath: path, constructorArgs: args } = self; deploy_code(ccx, executor, path, Some(args), None, None) } } impl Cheatcode for deployCode_2Call { fn apply_full( &self, ccx: &mut CheatsCtxt<'_, CTX>, executor: &mut dyn CheatcodesExecutor, ) -> Result { let Self { artifactPath: path, value } = self; deploy_code(ccx, executor, path, None, Some(*value), None) } } impl Cheatcode for deployCode_3Call { fn apply_full( &self, ccx: &mut CheatsCtxt<'_, CTX>, executor: &mut dyn CheatcodesExecutor, ) -> Result { let Self { artifactPath: path, constructorArgs: args, value } = self; deploy_code(ccx, executor, path, Some(args), Some(*value), None) } } impl Cheatcode for deployCode_4Call { fn apply_full( &self, ccx: &mut CheatsCtxt<'_, CTX>, executor: &mut dyn CheatcodesExecutor, ) -> Result { let Self { artifactPath: path, salt } = self; deploy_code(ccx, executor, path, None, None, Some((*salt).into())) } } impl Cheatcode for deployCode_5Call { fn apply_full( &self, ccx: &mut CheatsCtxt<'_, CTX>, executor: &mut dyn CheatcodesExecutor, ) -> Result { let Self { artifactPath: path, constructorArgs: args, salt } = self; deploy_code(ccx, executor, path, Some(args), None, Some((*salt).into())) } } impl Cheatcode for deployCode_6Call { fn apply_full( &self, ccx: &mut CheatsCtxt<'_, CTX>, executor: &mut dyn CheatcodesExecutor, ) -> Result { let Self { artifactPath: path, value, salt } = self; deploy_code(ccx, executor, path, None, Some(*value), Some((*salt).into())) } } impl Cheatcode for deployCode_7Call { fn apply_full( &self, ccx: &mut CheatsCtxt<'_, CTX>, executor: &mut dyn CheatcodesExecutor, ) -> Result { let Self { artifactPath: path, constructorArgs: args, value, salt } = self; deploy_code(ccx, executor, path, Some(args), Some(*value), Some((*salt).into())) } } /// Helper function to deploy contract from artifact code. /// Uses CREATE2 scheme if salt specified. fn deploy_code( ccx: &mut CheatsCtxt<'_, CTX>, executor: &mut dyn CheatcodesExecutor, path: &str, constructor_args: Option<&Bytes>, value: Option, salt: Option, ) -> Result { let mut bytecode = get_artifact_code(ccx.state, path, false)?.to_vec(); // If active broadcast then set flag to deploy from code. if let Some(broadcast) = &mut ccx.state.broadcast { broadcast.deploy_from_code = true; } if let Some(args) = constructor_args { bytecode.extend_from_slice(args); } let scheme = if let Some(salt) = salt { CreateScheme::Create2 { salt } } else { CreateScheme::Create }; // If prank active at current depth, then use it as caller for create input. let caller = ccx.state.get_prank(ccx.ecx.journal().depth()).map_or(ccx.caller, |prank| prank.new_caller); let outcome = exec_create( executor, CreateInputs::new( caller, scheme, value.unwrap_or(U256::ZERO), bytecode.into(), ccx.gas_limit, ), ccx, )?; if !outcome.result.result.is_ok() { return Err(crate::Error::from(outcome.result.output)); } let address = outcome.address.ok_or_else(|| fmt_err!("contract creation failed"))?; Ok(address.abi_encode()) } /// Returns the bytecode from a JSON artifact file. /// /// Can parse following input formats: /// - `path/to/artifact.json` /// - `path/to/contract.sol` /// - `path/to/contract.sol:ContractName` /// - `path/to/contract.sol:ContractName:0.8.23` /// - `path/to/contract.sol:0.8.23` /// - `ContractName` /// - `ContractName:0.8.23` /// /// This function is safe to use with contracts that have library dependencies. /// `alloy_json_abi::ContractObject` validates bytecode during JSON parsing and will /// reject artifacts with unlinked library placeholders. fn get_artifact_code(state: &Cheatcodes, path: &str, deployed: bool) -> Result { let path = if path.ends_with(".json") { PathBuf::from(path) } else { let mut parts = path.split(':'); let mut file = None; let mut contract_name = None; let mut version = None; let path_or_name = parts.next().unwrap(); if path_or_name.contains('.') { file = Some(PathBuf::from(path_or_name)); if let Some(name_or_version) = parts.next() { if name_or_version.contains('.') { version = Some(name_or_version); } else { contract_name = Some(name_or_version); version = parts.next(); } } } else { contract_name = Some(path_or_name); version = parts.next(); } let version = if let Some(version) = version { Some(Version::parse(version).map_err(|e| fmt_err!("failed parsing version: {e}"))?) } else { None }; // Use available artifacts list if present if let Some(artifacts) = &state.config.available_artifacts { let filtered = artifacts .iter() .filter(|(id, _)| { // name might be in the form of "Counter.0.8.23" let id_name = id.name.split('.').next().unwrap(); if let Some(path) = &file && !id.source.ends_with(path) { return false; } if let Some(name) = contract_name && id_name != name { return false; } if let Some(ref version) = version && (id.version.minor != version.minor || id.version.major != version.major || id.version.patch != version.patch) { return false; } true }) .collect::>(); let artifact = match &filtered[..] { [] => None, [artifact] => Some(Ok(*artifact)), filtered => { let mut filtered = filtered.to_vec(); // If we know the current script/test contract solc version, try to filter by it Some( state .config .running_artifact .as_ref() .and_then(|running| { // Firstly filter by version filtered.retain(|(id, _)| id.version == running.version); // Return artifact if only one matched if filtered.len() == 1 { return Some(filtered[0]); } // Try filtering by profile as well filtered.retain(|(id, _)| id.profile == running.profile); if filtered.len() == 1 { Some(filtered[0]) } else { None } }) .ok_or_else(|| fmt_err!("multiple matching artifacts found")), ) } }; if let Some(artifact) = artifact { let artifact = artifact?; let maybe_bytecode = if deployed { artifact.1.deployed_bytecode().cloned() } else { artifact.1.bytecode().cloned() }; return maybe_bytecode.ok_or_else(|| { fmt_err!("no bytecode for contract; is it abstract or unlinked?") }); } } // Fallback: construct path manually when no artifacts list or no match found let path_in_artifacts = match (file.map(|f| f.to_string_lossy().to_string()), contract_name) { (Some(file), Some(contract_name)) => { PathBuf::from(format!("{file}/{contract_name}.json")) } (None, Some(contract_name)) => { PathBuf::from(format!("{contract_name}.sol/{contract_name}.json")) } (Some(file), None) => { let name = file.replace(".sol", ""); PathBuf::from(format!("{file}/{name}.json")) } _ => bail!("invalid artifact path"), }; state.config.paths.artifacts.join(path_in_artifacts) }; let path = state.config.ensure_path_allowed(path, FsAccessKind::Read)?; let data = fs::read_to_string(path).map_err(|e| { if state.config.available_artifacts.is_some() { fmt_err!("no matching artifact found") } else { e.into() } })?; let artifact = serde_json::from_str::(&data)?; let maybe_bytecode = if deployed { artifact.deployed_bytecode } else { artifact.bytecode }; maybe_bytecode.ok_or_else(|| fmt_err!("no bytecode for contract; is it abstract or unlinked?")) } impl Cheatcode for ffiCall { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { commandInput: input } = self; let output = ffi(state, input)?; // Check the exit code of the command. if output.exitCode != 0 { // If the command failed, return an error with the exit code and stderr. return Err(fmt_err!( "ffi command {:?} exited with code {}. stderr: {}", input, output.exitCode, String::from_utf8_lossy(&output.stderr) )); } // If the command succeeded but still wrote to stderr, log it as a warning. if !output.stderr.is_empty() { let stderr = String::from_utf8_lossy(&output.stderr); warn!(target: "cheatcodes", ?input, ?stderr, "ffi command wrote to stderr"); } // We already hex-decoded the stdout in the `ffi` helper function. Ok(output.stdout.abi_encode()) } } impl Cheatcode for tryFfiCall { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { commandInput: input } = self; ffi(state, input).map(|res| res.abi_encode()) } } impl Cheatcode for promptCall { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { promptText: text } = self; prompt(state, text, prompt_input).map(|res| res.abi_encode()) } } impl Cheatcode for promptSecretCall { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { promptText: text } = self; prompt(state, text, prompt_password).map(|res| res.abi_encode()) } } impl Cheatcode for promptSecretUintCall { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { promptText: text } = self; parse(&prompt(state, text, prompt_password)?, &DynSolType::Uint(256)) } } impl Cheatcode for promptAddressCall { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { promptText: text } = self; parse(&prompt(state, text, prompt_input)?, &DynSolType::Address) } } impl Cheatcode for promptUintCall { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { promptText: text } = self; parse(&prompt(state, text, prompt_input)?, &DynSolType::Uint(256)) } } pub(super) fn write_file(state: &Cheatcodes, path: &Path, contents: &[u8]) -> Result { let path = state.config.ensure_path_allowed(path, FsAccessKind::Write)?; // write access to foundry.toml is not allowed state.config.ensure_not_foundry_toml(&path)?; if state.fs_commit { fs::locked_write(path, contents)?; } Ok(Default::default()) } fn read_dir(state: &Cheatcodes, path: &Path, max_depth: u64, follow_links: bool) -> Result { let root = state.config.ensure_path_allowed(path, FsAccessKind::Read)?; let paths: Vec = WalkDir::new(root) .min_depth(1) .max_depth(max_depth.try_into().unwrap_or(usize::MAX)) .follow_links(follow_links) .contents_first(false) .same_file_system(true) .sort_by_file_name() .into_iter() .map(|entry| match entry { Ok(entry) => DirEntry { errorMessage: String::new(), path: entry.path().display().to_string(), depth: entry.depth() as u64, isDir: entry.file_type().is_dir(), isSymlink: entry.path_is_symlink(), }, Err(e) => DirEntry { errorMessage: e.to_string(), path: e.path().map(|p| p.display().to_string()).unwrap_or_default(), depth: e.depth() as u64, isDir: false, isSymlink: false, }, }) .collect(); Ok(paths.abi_encode()) } fn ffi(state: &Cheatcodes, input: &[String]) -> Result { ensure!( state.config.ffi, "FFI is disabled; add the `--ffi` flag to allow tests to call external commands" ); ensure!(!input.is_empty() && !input[0].is_empty(), "can't execute empty command"); let mut cmd = Command::new(&input[0]); cmd.args(&input[1..]); debug!(target: "cheatcodes", ?cmd, "invoking ffi"); let output = cmd .current_dir(&state.config.root) .output() .map_err(|err| fmt_err!("failed to execute command {cmd:?}: {err}"))?; // The stdout might be encoded on valid hex, or it might just be a string, // so we need to determine which it is to avoid improperly encoding later. let trimmed_stdout = String::from_utf8(output.stdout)?; let trimmed_stdout = trimmed_stdout.trim(); let encoded_stdout = if let Ok(hex) = hex::decode(trimmed_stdout) { hex } else { trimmed_stdout.as_bytes().to_vec() }; Ok(FfiResult { exitCode: output.status.code().unwrap_or(69), stdout: encoded_stdout.into(), stderr: output.stderr.into(), }) } fn prompt_input(prompt_text: &str) -> Result { Input::new().allow_empty(true).with_prompt(prompt_text).interact_text() } fn prompt_password(prompt_text: &str) -> Result { Password::new().with_prompt(prompt_text).interact() } fn prompt( state: &Cheatcodes, prompt_text: &str, input: fn(&str) -> Result, ) -> Result { let text_clone = prompt_text.to_string(); let timeout = state.config.prompt_timeout; let (tx, rx) = mpsc::channel(); thread::spawn(move || { let _ = tx.send(input(&text_clone)); }); match rx.recv_timeout(timeout) { Ok(res) => res.map_err(|err| { let _ = sh_println!(); err.to_string().into() }), Err(_) => { let _ = sh_eprintln!(); Err("Prompt timed out".into()) } } } impl Cheatcode for getBroadcastCall { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { contractName, chainId, txType } = self; let latest_broadcast = latest_broadcast( contractName, *chainId, &state.config.broadcast, vec![map_broadcast_tx_type(*txType)], )?; Ok(latest_broadcast.abi_encode()) } } impl Cheatcode for getBroadcasts_0Call { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { contractName, chainId, txType } = self; let reader = BroadcastReader::new(contractName.clone(), *chainId, &state.config.broadcast)? .with_tx_type(map_broadcast_tx_type(*txType)); let broadcasts = reader.read::()?; let summaries = broadcasts .into_iter() .flat_map(|broadcast| { let results = reader.into_tx_receipts(broadcast); parse_broadcast_results(results) }) .collect::>(); Ok(summaries.abi_encode()) } } impl Cheatcode for getBroadcasts_1Call { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { contractName, chainId } = self; let reader = BroadcastReader::new(contractName.clone(), *chainId, &state.config.broadcast)?; let broadcasts = reader.read::()?; let summaries = broadcasts .into_iter() .flat_map(|broadcast| { let results = reader.into_tx_receipts(broadcast); parse_broadcast_results(results) }) .collect::>(); Ok(summaries.abi_encode()) } } impl Cheatcode for getDeployment_0Call { fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { contractName } = self; let chain_id = ccx.ecx.cfg().chain_id(); let latest_broadcast = latest_broadcast( contractName, chain_id, &ccx.state.config.broadcast, vec![CallKind::Create, CallKind::Create2], )?; Ok(latest_broadcast.contractAddress.abi_encode()) } } impl Cheatcode for getDeployment_1Call { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { contractName, chainId } = self; let latest_broadcast = latest_broadcast( contractName, *chainId, &state.config.broadcast, vec![CallKind::Create, CallKind::Create2], )?; Ok(latest_broadcast.contractAddress.abi_encode()) } } impl Cheatcode for getDeploymentsCall { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { contractName, chainId } = self; let reader = BroadcastReader::new(contractName.clone(), *chainId, &state.config.broadcast)? .with_tx_type(CallKind::Create) .with_tx_type(CallKind::Create2); let broadcasts = reader.read::()?; let summaries = broadcasts .into_iter() .flat_map(|broadcast| { let results = reader.into_tx_receipts(broadcast); parse_broadcast_results(results) }) .collect::>(); let deployed_addresses = summaries.into_iter().map(|summary| summary.contractAddress).collect::>(); Ok(deployed_addresses.abi_encode()) } } fn map_broadcast_tx_type(tx_type: BroadcastTxType) -> CallKind { match tx_type { BroadcastTxType::Call => CallKind::Call, BroadcastTxType::Create => CallKind::Create, BroadcastTxType::Create2 => CallKind::Create2, _ => unreachable!("invalid tx type"), } } fn parse_broadcast_results( results: Vec<(TransactionWithMetadata, N::ReceiptResponse)>, ) -> Vec { results .into_iter() .map(|(tx, receipt)| BroadcastTxSummary { txHash: receipt.transaction_hash(), blockNumber: receipt.block_number().unwrap_or_default(), txType: match tx.opcode { CallKind::Call => BroadcastTxType::Call, CallKind::Create => BroadcastTxType::Create, CallKind::Create2 => BroadcastTxType::Create2, _ => unreachable!("invalid tx type"), }, contractAddress: tx.contract_address.unwrap_or_default(), success: receipt.status(), }) .collect() } fn latest_broadcast( contract_name: &String, chain_id: u64, broadcast_path: &Path, filters: Vec, ) -> Result { let mut reader = BroadcastReader::new(contract_name.clone(), chain_id, broadcast_path)?; for filter in filters { reader = reader.with_tx_type(filter); } let broadcast = reader.read_latest::()?; let results = reader.into_tx_receipts(broadcast); let summaries = parse_broadcast_results(results); summaries .first() .ok_or_else(|| fmt_err!("no deployment found for {contract_name} on chain {chain_id}")) .cloned() } #[cfg(test)] mod tests { use super::*; use crate::CheatsConfig; use std::sync::Arc; fn cheats() -> Cheatcodes { let config = CheatsConfig { ffi: true, root: PathBuf::from(&env!("CARGO_MANIFEST_DIR")), ..Default::default() }; Cheatcodes::new(Arc::new(config)) } #[test] fn test_ffi_hex() { let msg = b"gm"; let cheats = cheats(); let args = ["echo".to_string(), hex::encode(msg)]; let output = ffi(&cheats, &args).unwrap(); assert_eq!(output.stdout, Bytes::from(msg)); } #[test] fn test_ffi_string() { let msg = "gm"; let cheats = cheats(); let args = ["echo".to_string(), msg.to_string()]; let output = ffi(&cheats, &args).unwrap(); assert_eq!(output.stdout, Bytes::from(msg.as_bytes())); } #[test] fn test_ffi_fails_on_error_code() { let mut cheats = cheats(); // Use a command that is guaranteed to fail with a non-zero exit code on any platform. #[cfg(unix)] let args = vec!["false".to_string()]; #[cfg(windows)] let args = vec!["cmd".to_string(), "/c".to_string(), "exit 1".to_string()]; let result = Cheatcode::apply(&ffiCall { commandInput: args }, &mut cheats); // Assert that the cheatcode returned an error. assert!(result.is_err(), "Expected ffi cheatcode to fail, but it succeeded"); // Assert that the error message contains the expected information. let err_msg = result.unwrap_err().to_string(); assert!( err_msg.contains("exited with code 1"), "Error message did not contain exit code: {err_msg}" ); } #[test] fn test_artifact_parsing() { let s = include_str!("../../evm/test-data/solc-obj.json"); let artifact: ContractObject = serde_json::from_str(s).unwrap(); assert!(artifact.bytecode.is_some()); let artifact: ContractObject = serde_json::from_str(s).unwrap(); assert!(artifact.deployed_bytecode.is_some()); } #[test] fn test_alloy_json_abi_rejects_unlinked_bytecode() { let artifact_json = r#"{ "abi": [], "bytecode": "0x73__$987e73aeca5e61ce83e4cb0814d87beda9$__63baf2f868" }"#; let result: Result = serde_json::from_str(artifact_json); assert!(result.is_err(), "should reject unlinked bytecode with placeholders"); let err = result.unwrap_err().to_string(); assert!(err.contains("expected bytecode, found unlinked bytecode with placeholder")); } } ================================================ FILE: crates/cheatcodes/src/inspector/analysis.rs ================================================ //! Cheatcode information, extracted from the syntactic and semantic analysis of the sources. use foundry_common::fmt::{StructDefinitions, TypeDefMap}; use solar::sema::{self, Compiler, Gcx, hir}; use std::sync::{Arc, OnceLock}; use thiserror::Error; /// Represents a failure in one of the lazy analysis steps. #[derive(Debug, Clone, PartialEq, Eq, Error)] pub enum AnalysisError { /// Indicates that the resolution of struct definitions failed. #[error("unable to resolve struct definitions")] StructDefinitionsResolutionFailed, } /// Provides cached, on-demand syntactic and semantic analysis of a completed `Compiler` instance. /// /// This struct acts as a facade over the `Compiler`, offering lazy-loaded analysis for tools like /// cheatcode inspectors. It assumes the compiler has already completed parsing and lowering. /// /// # Adding with new analyses types /// /// To add support for a new type of cached analysis, follow this pattern: /// /// 1. Add a new `pub OnceCell>` field to `CheatcodeAnalysis`, where `T` is /// the type of the data that you are adding support for. /// /// 2. Implement a getter method for the new field. Inside the getter, use /// `self.field.get_or_init()` to compute and cache the value on the first call. /// /// 3. Inside the closure passed to `get_or_init()`, create a dedicated visitor to traverse the HIR /// using `self.compiler.enter()` and collect the required data. /// /// This ensures all analyses remain lazy, efficient, and consistent with the existing design. #[derive(Clone)] pub struct CheatcodeAnalysis { /// A shared, thread-safe reference to solar's `Compiler` instance. pub compiler: Arc, /// Cached struct definitions in the sources. /// Used to keep field order when parsing JSON values. struct_defs: OnceLock>, } impl std::fmt::Debug for CheatcodeAnalysis { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("CheatcodeAnalysis") .field("compiler", &"") .field("struct_defs", &self.struct_defs) .finish() } } impl CheatcodeAnalysis { pub fn new(compiler: Arc) -> Self { Self { compiler, struct_defs: OnceLock::new() } } /// Lazily initializes and returns the struct definitions. pub fn struct_defs(&self) -> Result<&StructDefinitions, &AnalysisError> { self.struct_defs .get_or_init(|| { self.compiler.enter(|compiler| { let gcx = compiler.gcx(); StructDefinitionResolver::new(gcx).process() }) }) .as_ref() } } // -- STRUCT DEFINITIONS ------------------------------------------------------- /// Generates a map of all struct definitions from the HIR using the resolved `Ty` system. struct StructDefinitionResolver<'gcx> { gcx: Gcx<'gcx>, struct_defs: TypeDefMap, } impl<'gcx> StructDefinitionResolver<'gcx> { /// Constructs a new generator. pub fn new(gcx: Gcx<'gcx>) -> Self { Self { gcx, struct_defs: TypeDefMap::new() } } /// Processes the HIR to generate all the struct definitions. pub fn process(mut self) -> Result { for id in self.hir().strukt_ids() { self.resolve_struct_definition(id)?; } Ok(self.struct_defs.into()) } #[inline] fn hir(&self) -> &'gcx hir::Hir<'gcx> { &self.gcx.hir } /// The recursive core of the generator. Resolves a single struct and adds it to the cache. fn resolve_struct_definition(&mut self, id: hir::StructId) -> Result<(), AnalysisError> { let qualified_name = self.get_fully_qualified_name(id); if self.struct_defs.contains_key(&qualified_name) { return Ok(()); } let hir = self.hir(); let strukt = hir.strukt(id); let mut fields = Vec::with_capacity(strukt.fields.len()); for &field_id in strukt.fields { let var = hir.variable(field_id); let name = var.name.ok_or(AnalysisError::StructDefinitionsResolutionFailed)?.to_string(); if let Some(ty_str) = self.ty_to_string(self.gcx.type_of_hir_ty(&var.ty)) { fields.push((name, ty_str)); } } // Only insert if there are fields, to avoid adding empty entries if !fields.is_empty() { self.struct_defs.insert(qualified_name, fields); } Ok(()) } /// Converts a resolved `Ty` into its canonical string representation. fn ty_to_string(&mut self, ty: sema::Ty<'gcx>) -> Option { let ty = ty.peel_refs(); let res = match ty.kind { sema::ty::TyKind::Elementary(e) => e.to_string(), sema::ty::TyKind::Array(ty, size) => { let inner_type = self.ty_to_string(ty)?; format!("{inner_type}[{size}]") } sema::ty::TyKind::DynArray(ty) => { let inner_type = self.ty_to_string(ty)?; format!("{inner_type}[]") } sema::ty::TyKind::Struct(id) => { // Ensure the nested struct is resolved before proceeding. self.resolve_struct_definition(id).ok()?; self.get_fully_qualified_name(id) } sema::ty::TyKind::Udvt(ty, _) => self.ty_to_string(ty)?, // For now, map enums to `uint8` sema::ty::TyKind::Enum(_) => "uint8".to_string(), // For now, map contracts to `address` sema::ty::TyKind::Contract(_) => "address".to_string(), // Explicitly disallow unsupported types _ => return None, }; Some(res) } /// Helper to get the fully qualified name `Contract.Struct`. fn get_fully_qualified_name(&self, id: hir::StructId) -> String { let hir = self.hir(); let strukt = hir.strukt(id); if let Some(contract_id) = strukt.contract { format!("{}.{}", hir.contract(contract_id).name.as_str(), strukt.name.as_str()) } else { strukt.name.as_str().into() } } } ================================================ FILE: crates/cheatcodes/src/inspector/utils.rs ================================================ use crate::inspector::Cheatcodes; use alloy_primitives::{Address, Bytes, U256}; use foundry_evm_core::backend::DatabaseExt; use revm::{ context::ContextTr, inspector::JournalExt, interpreter::{CreateInputs, CreateScheme}, }; /// Common behaviour of legacy and EOF create inputs. pub(crate) trait CommonCreateInput { fn caller(&self) -> Address; fn gas_limit(&self) -> u64; fn value(&self) -> U256; fn init_code(&self) -> Bytes; 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: &mut CTX, ) -> Address; } impl CommonCreateInput for &mut CreateInputs { fn caller(&self) -> Address { CreateInputs::caller(self) } fn gas_limit(&self) -> u64 { CreateInputs::gas_limit(self) } fn value(&self) -> U256 { CreateInputs::value(self) } fn init_code(&self) -> Bytes { CreateInputs::init_code(self).clone() } fn scheme(&self) -> Option { Some(CreateInputs::scheme(self)) } fn set_caller(&mut self, caller: Address) { CreateInputs::set_call(self, caller); } fn log_debug(&self, cheatcode: &mut Cheatcodes, scheme: &CreateScheme) { 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: &mut CTX, ) -> Address { let caller = CreateInputs::caller(self); let old_nonce = ecx.journal().evm_state().get(&caller).map(|acc| acc.info.nonce).unwrap_or_default(); let created_address = self.created_address(old_nonce); cheatcodes.allow_cheatcodes_on_create(ecx, caller, created_address); created_address } } ================================================ FILE: crates/cheatcodes/src/inspector.rs ================================================ //! Cheatcode EVM inspector. use crate::{ Cheatcode, CheatsConfig, CheatsCtxt, Error, Result, Vm::{self, AccountAccess}, evm::{ DealRecord, GasRecord, RecordAccess, journaled_account, mock::{MockCallDataContext, MockCallReturnData}, prank::Prank, }, inspector::utils::CommonCreateInput, script::{Broadcast, Wallets}, test::{ assume::AssumeNoRevert, expect::{ self, ExpectedCallData, ExpectedCallTracker, ExpectedCallType, ExpectedCreate, ExpectedEmitTracker, ExpectedRevert, ExpectedRevertKind, }, revert_handlers, }, utils::IgnoredTraces, }; use alloy_consensus::BlobTransactionSidecarVariant; use alloy_primitives::{ Address, B256, Bytes, Log, TxKind, U256, hex, map::{AddressHashMap, HashMap, HashSet}, }; use alloy_rpc_types::AccessList; use alloy_sol_types::{SolCall, SolInterface, SolValue}; use foundry_common::{ SELECTOR_LEN, mapping_slots::{MappingSlots, step as mapping_step}, }; use foundry_evm_core::{ Breakpoints, EthCheatCtx, EvmEnv, FoundryInspectorExt, FoundryTransaction, abi::Vm::stopExpectSafeMemoryCall, backend::{DatabaseError, DatabaseExt, RevertDiagnostic}, constants::{CHEATCODE_ADDRESS, HARDHAT_CONSOLE_ADDRESS, MAGIC_ASSUME}, env::FoundryContextExt, evm::{NestedEvm, NestedEvmClosure, new_eth_evm_with_inspector, with_cloned_context}, }; use foundry_evm_traces::{ TracingInspector, TracingInspectorConfig, identifier::SignaturesIdentifier, }; use foundry_wallets::wallet_multi::MultiWallet; use itertools::Itertools; use proptest::test_runner::{RngAlgorithm, TestRng, TestRunner}; use rand::Rng; use revm::{ Inspector, bytecode::opcode as op, context::{ BlockEnv, Cfg, ContextTr, JournalTr, Transaction, TransactionType, result::EVMError, }, context_interface::{CreateScheme, transaction::SignedAuthorization}, handler::FrameResult, inspector::JournalExt, interpreter::{ CallInputs, CallOutcome, CallScheme, CreateInputs, CreateOutcome, FrameInput, Gas, InstructionResult, Interpreter, InterpreterAction, InterpreterResult, interpreter_types::{Jumps, LoopControl, MemoryTr}, }, primitives::hardfork::SpecId, }; use serde_json::Value; use std::{ cmp::max, collections::{BTreeMap, VecDeque}, fmt::Debug, fs::File, io::BufReader, ops::Range, path::PathBuf, sync::{Arc, OnceLock}, }; mod utils; pub mod analysis; pub use analysis::CheatcodeAnalysis; /// Helper trait for running nested EVM operations from inside cheatcode implementations. pub trait CheatcodesExecutor { /// Runs a closure with a nested EVM built from the current context. /// The inspector is assembled internally — never exposed to the caller. fn with_nested_evm( &mut self, cheats: &mut Cheatcodes, ecx: &mut CTX, f: NestedEvmClosure<'_, CTX::Block, CTX::Tx, ::Spec>, ) -> Result<(), EVMError>; /// Replays a historical transaction on the database. Inspector is assembled internally. fn transact_on_db( &mut self, cheats: &mut Cheatcodes, ecx: &mut CTX, fork_id: Option, transaction: B256, ) -> eyre::Result<()>; /// Executes a `TransactionRequest` on the database. Inspector is assembled internally. fn transact_from_tx_on_db( &mut self, cheats: &mut Cheatcodes, ecx: &mut CTX, tx: &CTX::Tx, ) -> eyre::Result<()>; /// Runs a closure with a fresh nested EVM built from a raw database and environment. /// Unlike `with_nested_evm`, this does NOT clone from `ecx` and does NOT write back. /// The caller is responsible for state merging. Used by `executeTransactionCall`. fn with_fresh_nested_evm( &mut self, cheats: &mut Cheatcodes, db: &mut dyn DatabaseExt::Spec>, evm_env: EvmEnv<::Spec, CTX::Block>, tx_env: CTX::Tx, f: NestedEvmClosure<'_, CTX::Block, CTX::Tx, ::Spec>, ) -> Result<(), EVMError>; /// Simulates `console.log` invocation. fn console_log(&mut self, cheats: &mut Cheatcodes, msg: &str); /// Returns a mutable reference to the tracing inspector if it is available. fn tracing_inspector(&mut self) -> Option<&mut TracingInspector> { None } /// Marks that the next EVM frame is an "inner context" so that isolation mode does not /// trigger a nested `transact_inner`. `original_origin` is stored for the existing /// inner-context adjustment logic that restores `tx.origin`. fn set_in_inner_context(&mut self, _enabled: bool, _original_origin: Option
) {} } /// Builds a sub-EVM from the current context and executes the given CREATE frame. pub(crate) fn exec_create( executor: &mut dyn CheatcodesExecutor, inputs: CreateInputs, ccx: &mut CheatsCtxt<'_, CTX>, ) -> std::result::Result> { let mut inputs = Some(inputs); let mut outcome = None; executor.with_nested_evm(ccx.state, ccx.ecx, &mut |evm| { let inputs = inputs.take().unwrap(); evm.journal_inner_mut().depth += 1; let frame = FrameInput::Create(Box::new(inputs)); let result = match evm.run_execution(frame)? { FrameResult::Call(_) => unreachable!(), FrameResult::Create(create) => create, }; evm.journal_inner_mut().depth -= 1; outcome = Some(result); Ok(()) })?; Ok(outcome.unwrap()) } /// Basic implementation of [CheatcodesExecutor] that simply returns the [Cheatcodes] instance as an /// inspector. #[derive(Debug, Default, Clone, Copy)] struct TransparentCheatcodesExecutor; impl CheatcodesExecutor for TransparentCheatcodesExecutor { fn with_nested_evm( &mut self, cheats: &mut Cheatcodes, ecx: &mut CTX, f: NestedEvmClosure<'_, CTX::Block, CTX::Tx, ::Spec>, ) -> Result<(), EVMError> { with_cloned_context(ecx, |db, evm_env, tx_env, journal_inner| { let mut evm = new_eth_evm_with_inspector(db, evm_env, tx_env, cheats); *evm.journal_inner_mut() = journal_inner; f(&mut evm)?; let sub_evm_env = evm.to_evm_env(); let sub_inner = evm.journaled_state.inner.clone(); Ok((sub_evm_env, sub_inner)) }) } fn with_fresh_nested_evm( &mut self, cheats: &mut Cheatcodes, db: &mut dyn DatabaseExt::Spec>, evm_env: EvmEnv<::Spec, CTX::Block>, tx_env: CTX::Tx, f: NestedEvmClosure<'_, CTX::Block, CTX::Tx, ::Spec>, ) -> Result<(), EVMError> { let mut evm = new_eth_evm_with_inspector(db, evm_env, tx_env, cheats); f(&mut evm) } fn transact_on_db( &mut self, cheats: &mut Cheatcodes, ecx: &mut CTX, fork_id: Option, transaction: B256, ) -> eyre::Result<()> { let evm_env = ecx.evm_clone(); let tx_env = ecx.tx_clone(); let (db, inner) = ecx.db_journal_inner_mut(); db.transact(fork_id, transaction, evm_env, tx_env, inner, cheats) } fn transact_from_tx_on_db( &mut self, cheats: &mut Cheatcodes, ecx: &mut CTX, tx: &CTX::Tx, ) -> eyre::Result<()> { let evm_env = ecx.evm_clone(); let (db, inner) = ecx.db_journal_inner_mut(); db.transact_from_tx(tx, evm_env, inner, cheats) } fn console_log(&mut self, _cheats: &mut Cheatcodes, _msg: &str) {} } macro_rules! try_or_return { ($e:expr) => { match $e { Ok(v) => v, Err(_) => return, } }; } /// Contains additional, test specific resources that should be kept for the duration of the test #[derive(Debug, Default)] 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 TestContext { fn clone(&self) -> Self { Default::default() } } impl TestContext { /// Clears the context. pub fn clear(&mut self) { self.opened_read_files.clear(); } } /// Helps collecting transactions from different forks. /// /// This type is intentionally network-agnostic — it stores raw EVM data (from, to, value, input, /// etc.) without requiring a `Network` type parameter. Conversion to network-specific types /// (e.g. `TransactionRequest`, `TxEnvelope`) happens later in the script layer. #[derive(Clone, Debug)] pub struct BroadcastableTransaction { /// The optional RPC URL. pub rpc: Option, /// Sender address. pub from: Address, /// Recipient (None for contract creation). pub to: Option, /// Ether value. pub value: U256, /// Calldata or init code. pub input: Bytes, /// Sender nonce. pub nonce: u64, /// Gas limit, if explicitly set. pub gas: Option, /// Whether this transaction was pre-signed or needs signing. pub kind: BroadcastKind, } /// Distinguishes between unsigned transactions (from `startBroadcast`) that need signing, and /// pre-signed transactions (from `broadcastRawTransaction`) that carry raw RLP-encoded bytes. #[derive(Clone, Debug)] pub enum BroadcastKind { /// Unsigned transaction collected during `startBroadcast`. Needs signing before broadcast. Unsigned { chain_id: Option, blob_sidecar: Option, authorization_list: Option>, }, /// Pre-signed transaction from `broadcastRawTransaction`. Contains raw RLP-encoded bytes. Signed(Bytes), } impl BroadcastKind { /// Creates an unsigned broadcast with no chain-specific fields set. pub fn unsigned() -> Self { Self::Unsigned { chain_id: None, blob_sidecar: None, authorization_list: None } } } #[derive(Clone, Debug, Copy)] pub struct RecordDebugStepInfo { /// The debug trace node index when the recording starts. pub start_node_idx: usize, /// The original tracer config when the recording starts. pub original_tracer_config: TracingInspectorConfig, } /// Holds gas metering state. #[derive(Clone, Debug, Default)] pub struct GasMetering { /// True if gas metering is paused. pub paused: bool, /// True if gas metering was resumed or reset during the test. /// Used to reconcile gas when frame ends (if spent less than refunded). pub touched: bool, /// True if gas metering should be reset to frame limit. pub reset: bool, /// Stores paused gas frames. pub paused_frames: Vec, /// The group and name of the active snapshot. pub active_gas_snapshot: Option<(String, String)>, /// Cache of the amount of gas used in previous call. /// This is used by the `lastCallGas` cheatcode. pub last_call_gas: Option, /// True if gas recording is enabled. pub recording: bool, /// The gas used in the last frame. pub last_gas_used: u64, /// Gas records for the active snapshots. pub gas_records: Vec, } impl GasMetering { /// Start the gas recording. pub fn start(&mut self) { self.recording = true; } /// Stop the gas recording. pub fn stop(&mut self) { self.recording = false; } /// Resume paused gas metering. pub fn resume(&mut self) { if self.paused { self.paused = false; self.touched = true; } self.paused_frames.clear(); } /// Reset gas to limit. pub fn reset(&mut self) { self.paused = false; self.touched = true; self.reset = true; self.paused_frames.clear(); } } /// Holds data about arbitrary storage. #[derive(Clone, Debug, Default)] pub struct ArbitraryStorage { /// Mapping of arbitrary storage addresses to generated values (slot, arbitrary value). /// (SLOADs return random value if storage slot wasn't accessed). /// Changed values are recorded and used to copy storage to different addresses. pub values: HashMap>, /// Mapping of address with storage copied to arbitrary storage address source. pub copies: HashMap, /// Address with storage slots that should be overwritten even if previously set. pub overwrites: HashSet
, } impl ArbitraryStorage { /// Marks an address with arbitrary storage. pub fn mark_arbitrary(&mut self, address: &Address, overwrite: bool) { self.values.insert(*address, HashMap::default()); if overwrite { self.overwrites.insert(*address); } else { self.overwrites.remove(address); } } /// Maps an address that copies storage with the arbitrary storage address. pub fn mark_copy(&mut self, from: &Address, to: &Address) { if self.values.contains_key(from) { self.copies.insert(*to, *from); } } /// 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: &mut CTX, address: Address, slot: U256, data: U256, ) { self.values.get_mut(&address).expect("missing arbitrary address entry").insert(slot, data); if ecx.journal_mut().load_account(address).is_ok() { ecx.journal_mut() .sstore(address, slot, data) .expect("could not set arbitrary storage value"); } } /// Copies arbitrary storage value from source address to the given target address: /// - if a value is present in arbitrary values cache, then update target storage and return /// 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: &mut CTX, 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) { Some(value) => *value, None => { storage_cache.insert(slot, new_value); // Update source storage with new value. if ecx.journal_mut().load_account(*source).is_ok() { ecx.journal_mut() .sstore(*source, slot, new_value) .expect("could not copy arbitrary storage value"); } new_value } }; // Update target storage with new value. if ecx.journal_mut().load_account(target).is_ok() { ecx.journal_mut().sstore(target, slot, value).expect("could not set storage"); } value } } /// List of transactions that can be broadcasted. pub type BroadcastableTransactions = VecDeque; /// An EVM inspector that handles calls to various cheatcodes, each with their own behavior. /// /// Cheatcodes can be called by contracts during execution to modify the VM environment, such as /// mocking addresses, signatures and altering call reverts. /// /// Executing cheatcodes can be very powerful. Most cheatcodes are limited to evm internals, but /// there are also cheatcodes like `ffi` which can execute arbitrary commands or `writeFile` and /// `readFile` which can manipulate files of the filesystem. Therefore, several restrictions are /// implemented for these cheatcodes: /// - `ffi`, and file cheatcodes are _always_ opt-in (via foundry config) and never enabled by /// default: all respective cheatcode handlers implement the appropriate checks /// - File cheatcodes require explicit permissions which paths are allowed for which operation, see /// `Config.fs_permission` /// - Only permitted accounts are allowed to execute cheatcodes in forking mode, this ensures no /// contract deployed on the live network is able to execute cheatcodes by simply calling the /// cheatcode address: by default, the caller, test contract and newly deployed contracts are /// allowed to execute cheatcodes #[derive(Clone, Debug)] pub struct Cheatcodes { /// Solar compiler instance, to grant syntactic and semantic analysis capabilities pub analysis: Option, /// The block environment /// /// Used in the cheatcode handler to overwrite the block environment separately from the /// execution block environment. pub block: Option, /// 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_delegations: Vec, /// The active EIP-4844 blob that will be attached to the next call. pub active_blob_sidecar: Option, /// The gas price. /// /// Used in the cheatcode handler to overwrite the gas price separately from the gas price /// in the execution environment. pub gas_price: Option, /// Address labels pub labels: AddressHashMap, /// Prank information, mapped to the call depth where pranks were added. pub pranks: BTreeMap, /// Expected revert information pub expected_revert: Option, /// Assume next call can revert and discard fuzz run if it does. pub assume_no_revert: Option, /// Additional diagnostic for reverts pub fork_revert_diagnostic: Option, /// Recorded storage reads and writes pub accesses: RecordAccess, /// Whether storage access recording is currently active pub recording_accesses: bool, /// Recorded account accesses (calls, creates) organized by relative call depth, where the /// topmost vector corresponds to accesses at the depth at which account access recording /// began. Each vector in the matrix represents a list of accesses at a specific call /// depth. Once that call context has ended, the last vector is removed from the matrix and /// merged into the previous vector. pub recorded_account_diffs_stack: Option>>, /// The information of the debug step recording. pub record_debug_steps_info: Option, /// Recorded logs pub recorded_logs: Option>, /// Mocked calls // **Note**: inner must a BTreeMap because of special `Ord` impl for `MockCallDataContext` pub mocked_calls: HashMap>>, /// Mocked functions. Maps target address to be mocked to pair of (calldata, mock address). pub mocked_functions: HashMap>, /// Expected calls pub expected_calls: ExpectedCallTracker, /// Expected emits pub expected_emits: ExpectedEmitTracker, /// Expected creates pub expected_creates: Vec, /// Map of context depths to memory offset ranges that may be written to within the call depth. pub allowed_mem_writes: HashMap>>, /// Current broadcasting information pub broadcast: Option, /// Scripting based transactions pub broadcastable_transactions: BroadcastableTransactions, /// Current EIP-2930 access lists. pub access_list: Option, /// Additional, user configurable context this Inspector has access to when inspecting a call. pub config: Arc, /// Test-scoped context holding data that needs to be reset every test run 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. pub fs_commit: bool, /// Serialized JSON values. // **Note**: both must a BTreeMap to ensure the order of the keys is deterministic. pub serialized_jsons: BTreeMap>, /// All recorded ETH `deal`s. pub eth_deals: Vec, /// Gas metering state. pub gas_metering: GasMetering, /// Contains gas snapshots made over the course of a test suite. // **Note**: both must a BTreeMap to ensure the order of the keys is deterministic. pub gas_snapshots: BTreeMap>, /// Mapping slots. pub mapping_slots: Option>, /// The current program counter. pub pc: usize, /// Breakpoints supplied by the `breakpoint` cheatcode. /// `char -> (address, pc)` pub breakpoints: Breakpoints, /// Whether the next contract creation should be intercepted to return its initcode. pub intercept_next_create_call: bool, /// Optional cheatcodes `TestRunner`. Used for generating random values from uint and int /// strategies. test_runner: Option, /// Ignored traces. pub ignored_traces: IgnoredTraces, /// Addresses with arbitrary storage. pub arbitrary_storage: Option, /// Deprecated cheatcodes mapped to the reason. Used to report warnings on test results. pub deprecated: HashMap<&'static str, Option<&'static str>>, /// Unlocked wallets used in scripts and testing of scripts. pub wallets: Option, /// Signatures identifier for decoding events and functions signatures_identifier: OnceLock>, /// Used to determine whether the broadcasted call has dynamic gas limit. pub dynamic_gas_limit: bool, // Custom execution evm version. pub execution_evm_version: Option, } // This is not derived because calling this in `fn new` with `..Default::default()` creates a second // `CheatsConfig` which is unused, and inside it `ProjectPathsConfig` is relatively expensive to // create. impl Default for Cheatcodes { fn default() -> Self { Self::new(Arc::default()) } } impl Cheatcodes { /// Creates a new `Cheatcodes` with the given settings. pub fn new(config: Arc) -> Self { Self { analysis: None, fs_commit: true, labels: config.labels.clone(), config, block: Default::default(), active_delegations: Default::default(), active_blob_sidecar: Default::default(), gas_price: Default::default(), pranks: Default::default(), expected_revert: Default::default(), assume_no_revert: Default::default(), fork_revert_diagnostic: Default::default(), accesses: Default::default(), recording_accesses: Default::default(), recorded_account_diffs_stack: Default::default(), recorded_logs: Default::default(), record_debug_steps_info: Default::default(), mocked_calls: Default::default(), mocked_functions: Default::default(), expected_calls: Default::default(), expected_emits: Default::default(), expected_creates: Default::default(), allowed_mem_writes: Default::default(), broadcast: Default::default(), broadcastable_transactions: Default::default(), access_list: Default::default(), test_context: Default::default(), serialized_jsons: Default::default(), eth_deals: Default::default(), gas_metering: Default::default(), gas_snapshots: Default::default(), mapping_slots: Default::default(), pc: Default::default(), breakpoints: Default::default(), intercept_next_create_call: Default::default(), test_runner: Default::default(), ignored_traces: Default::default(), arbitrary_storage: Default::default(), deprecated: Default::default(), wallets: Default::default(), signatures_identifier: Default::default(), dynamic_gas_limit: Default::default(), execution_evm_version: None, } } /// Enables cheatcode analysis capabilities by providing a solar compiler instance. pub fn set_analysis(&mut self, analysis: CheatcodeAnalysis) { self.analysis = Some(analysis); } /// 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: usize) -> Option<&Prank> { self.pranks.range(..=depth).last().map(|(_, prank)| prank) } /// Returns the configured wallets if available, else creates a new instance. pub fn wallets(&mut self) -> &Wallets { self.wallets.get_or_insert_with(|| Wallets::new(MultiWallet::default(), None)) } /// Sets the unlocked wallets. pub fn set_wallets(&mut self, wallets: Wallets) { 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); } /// Returns the signatures identifier. pub fn signatures_identifier(&self) -> Option<&SignaturesIdentifier> { self.signatures_identifier.get_or_init(|| SignaturesIdentifier::new(true).ok()).as_ref() } /// Decodes the input data and applies the cheatcode. fn apply_cheatcode( &mut self, ecx: &mut CTX, call: &CallInputs, executor: &mut dyn CheatcodesExecutor, ) -> Result { // decode the cheatcode call 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}; \ you may have a mismatch between the `Vm` interface (likely in `forge-std`) \ and the `forge` version" ); return alloy_sol_types::Error::Other(std::borrow::Cow::Owned(msg)); } e })?; let caller = call.caller; // ensure the caller is allowed to execute cheatcodes, // but only if the backend is in forking mode ecx.db_mut().ensure_cheatcode_access_forking_mode(&caller)?; apply_dispatch( &decoded, &mut CheatsCtxt { state: self, ecx, gas_limit: call.gas_limit, caller }, executor, ) } /// Grants cheat code access for new contracts if the caller also has /// cheatcode access or the new contract is created in top most call. /// /// 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: &mut CTX, caller: Address, created_address: Address, ) { if ecx.journal().depth() <= 1 || ecx.db().has_cheatcode_access(&caller) { ecx.db_mut().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: &mut CTX) { if let Some(access_list) = &self.access_list { ecx.tx_mut().set_access_list(access_list.clone()); if ecx.tx().tx_type() == TransactionType::Legacy as u8 { ecx.tx_mut().set_tx_type(TransactionType::Eip2930 as u8); } } } /// Called when there was a revert. /// /// Cleanup any previously applied cheatcodes that altered the state in such a way that revm's /// revert would run into issues. pub fn on_revert>(&mut self, ecx: &mut CTX) { trace!(deals=?self.eth_deals.len(), "rolling back deals"); // Delay revert clean up until expected revert is handled, if set. if self.expected_revert.is_some() { return; } // we only want to apply cleanup top level if ecx.journal().depth() > 0 { return; } // Roll back all previously applied deals // This will prevent overflow issues in revm's [`JournaledState::journal_revert`] routine // which rolls back any transfers. while let Some(record) = self.eth_deals.pop() { if let Some(acc) = ecx.journal_mut().evm_state_mut().get_mut(&record.address) { acc.info.balance = record.old_balance; } } } pub fn call_with_executor( &mut self, ecx: &mut CTX, call: &mut CallInputs, executor: &mut dyn CheatcodesExecutor, ) -> Option { // Apply custom execution evm version. if let Some(spec_id) = self.execution_evm_version { ecx.cfg_mut().set_spec(spec_id); } let gas = Gas::new(call.gas_limit); let curr_depth = ecx.journal().depth(); // At the root call to test function or script `run()`/`setUp()` functions, we are // decreasing sender nonce to ensure that it matches on-chain nonce once we start // broadcasting. if curr_depth == 0 { let sender = ecx.tx().caller(); let account = match super::evm::journaled_account(ecx, sender) { Ok(account) => account, Err(err) => { return Some(CallOutcome { result: InterpreterResult { result: InstructionResult::Revert, output: err.abi_encode().into(), gas, }, memory_offset: call.return_memory_offset.clone(), was_precompile_called: false, precompile_call_logs: vec![], }); } }; let prev = account.info.nonce; account.info.nonce = prev.saturating_sub(1); trace!(target: "cheatcodes", %sender, nonce=account.info.nonce, prev, "corrected nonce"); } if call.target_address == CHEATCODE_ADDRESS { return match self.apply_cheatcode(ecx, call, executor) { Ok(retdata) => Some(CallOutcome { result: InterpreterResult { result: InstructionResult::Return, output: retdata.into(), gas, }, memory_offset: call.return_memory_offset.clone(), was_precompile_called: true, precompile_call_logs: vec![], }), Err(err) => Some(CallOutcome { result: InterpreterResult { result: InstructionResult::Revert, output: err.abi_encode().into(), gas, }, memory_offset: call.return_memory_offset.clone(), was_precompile_called: false, precompile_call_logs: vec![], }), }; } if call.target_address == HARDHAT_CONSOLE_ADDRESS { return None; } // `expectRevert`: track max call depth. This is also done in `initialize_interp`, but // precompile calls don't create an interpreter frame so we must also track it here. // The callee executes at `curr_depth + 1`. if let Some(expected) = &mut self.expected_revert { expected.max_depth = max(curr_depth + 1, expected.max_depth); } // Handle expected calls // Grab the different calldatas expected. if let Some(expected_calls_for_target) = self.expected_calls.get_mut(&call.bytecode_address) { // Match every partial/full calldata for (calldata, (expected, actual_count)) in expected_calls_for_target { // Increment actual times seen if... // 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.bytes(ecx)[..calldata.len()] && // The value matches, if provided expected .value.is_none_or(|value| Some(value) == call.transfer_value()) && // The gas matches, if provided expected.gas.is_none_or(|gas| gas == call.gas_limit) && // The minimum gas matches, if provided expected.min_gas.is_none_or(|min_gas| min_gas <= call.gas_limit) { *actual_count += 1; } } } // Handle mocked calls if let Some(mocks) = self.mocked_calls.get_mut(&call.bytecode_address) { 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), } && 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()) } else { // Else, we pop the front element return_data_queue.pop_front() } { return Some(CallOutcome { result: InterpreterResult { result: return_data.ret_type, output: return_data.data, gas, }, memory_offset: call.return_memory_offset.clone(), was_precompile_called: true, precompile_call_logs: vec![], }); } } // Apply our prank if let Some(prank) = &self.get_prank(curr_depth) { // Apply delegate call, `call.caller`` will not equal `prank.prank_caller` if prank.delegate_call && curr_depth == prank.depth && let CallScheme::DelegateCall = call.scheme { call.target_address = prank.new_caller; call.caller = prank.new_caller; if let Some(new_origin) = prank.new_origin { ecx.tx_mut().set_caller(new_origin); } } if curr_depth >= prank.depth && call.caller == prank.prank_caller { let mut prank_applied = false; // At the target depth we set `msg.sender` if curr_depth == prank.depth { // Ensure new caller is loaded and touched let _ = journaled_account(ecx, prank.new_caller); call.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_mut().set_caller(new_origin); prank_applied = true; } // If prank applied for first time, then update if prank_applied && 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 { // Additional check as transfers in forge scripts seem to be estimated at 2300 // by revm leading to "Intrinsic gas too low" failure when simulated on chain. let is_fixed_gas_limit = call.gas_limit >= 21_000 && !self.dynamic_gas_limit; self.dynamic_gas_limit = false; // We only apply a broadcast *to a specific depth*. // // We do this because any subsequent contract calls *must* exist on chain and // we only want to grab *this* call, not internal ones if curr_depth == broadcast.depth && call.caller == broadcast.original_caller { // 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.tx_mut().set_caller(broadcast.new_origin); call.caller = broadcast.new_origin; // Add a `legacy` transaction to the VecDeque. We use a legacy transaction here // because we only need the from, to, value, and data. We can later change this // 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.journal_mut().load_account(broadcast.new_origin) { return Some(CallOutcome { result: InterpreterResult { result: InstructionResult::Revert, output: Error::encode(err), gas, }, memory_offset: call.return_memory_offset.clone(), was_precompile_called: false, precompile_call_logs: vec![], }); } let input = call.input.bytes(ecx); let chain_id = ecx.cfg().chain_id(); let rpc = ecx.db().active_fork_url(); let account = ecx.journal_mut().evm_state_mut().get_mut(&broadcast.new_origin).unwrap(); let blob_sidecar = self.active_blob_sidecar.take(); let active_delegations = std::mem::take(&mut self.active_delegations); // Ensure blob and delegation are not set for the same tx. if blob_sidecar.is_some() && !active_delegations.is_empty() { let msg = "both delegation and blob are active; `attachBlob` and `attachDelegation` are not compatible"; return Some(CallOutcome { result: InterpreterResult { result: InstructionResult::Revert, output: Error::encode(msg), gas, }, memory_offset: call.return_memory_offset.clone(), was_precompile_called: false, precompile_call_logs: vec![], }); } // Capture nonce before delegation bump (delegations increment the // account nonce but the transaction itself uses the pre-bump nonce). let nonce = account.info.nonce; // Apply active EIP-7702 delegations, if any. let authorization_list = 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; } } Some(active_delegations) } else { None }; self.broadcastable_transactions.push_back(BroadcastableTransaction { rpc, from: broadcast.new_origin, to: Some(TxKind::from(Some(call.target_address))), value: call.transfer_value().unwrap_or_default(), input, nonce, gas: if is_fixed_gas_limit { Some(call.gas_limit) } else { None }, kind: BroadcastKind::Unsigned { chain_id: Some(chain_id), blob_sidecar, authorization_list, }, }); debug!(target: "cheatcodes", tx=?self.broadcastable_transactions.back().unwrap(), "broadcastable call"); // Explicitly increment nonce if calls are not isolated. if !self.config.evm_opts.isolate { let prev = account.info.nonce; account.info.nonce += 1; debug!(target: "cheatcodes", address=%broadcast.new_origin, nonce=prev+1, prev, "incremented nonce"); } } else if broadcast.single_call { let msg = "`staticcall`s are not allowed after `broadcast`; use `startBroadcast` instead"; return Some(CallOutcome { result: InterpreterResult { result: InstructionResult::Revert, output: Error::encode(msg), gas, }, memory_offset: call.return_memory_offset.clone(), was_precompile_called: false, precompile_call_logs: vec![], }); } } } // Record called accounts if `startStateDiffRecording` has been called if let Some(recorded_account_diffs_stack) = &mut self.recorded_account_diffs_stack { // Determine if account is "initialized," ie, it has a non-zero balance, a non-zero // nonce, a non-zero KECCAK_EMPTY codehash, or non-empty code let initialized; let old_balance; let old_nonce; if let Ok(acc) = ecx.journal_mut().load_account(call.target_address) { initialized = acc.data.info.exists(); old_balance = acc.data.info.balance; old_nonce = acc.data.info.nonce; } else { initialized = false; old_balance = U256::ZERO; old_nonce = 0; } let kind = match call.scheme { CallScheme::Call => crate::Vm::AccountAccessKind::Call, CallScheme::CallCode => crate::Vm::AccountAccessKind::CallCode, CallScheme::DelegateCall => crate::Vm::AccountAccessKind::DelegateCall, CallScheme::StaticCall => crate::Vm::AccountAccessKind::StaticCall, }; // Record this call by pushing it to a new pending vector; all subsequent calls at // that depth will be pushed to the same vector. When the call ends, the // RecordedAccountAccess (and all subsequent RecordedAccountAccesses) will be // updated with the revert status of this call, since the EVM does not mark accounts // 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.cfg().chain_id()), }, accessor: call.caller, account: call.bytecode_address, kind, initialized, oldBalance: old_balance, newBalance: U256::ZERO, // updated on call_end oldNonce: old_nonce, newNonce: 0, // updated on call_end value: call.call_value(), data: call.input.bytes(ecx), reverted: false, deployedCode: Bytes::new(), storageAccesses: vec![], // updated on step depth: ecx.journal().depth().try_into().expect("journaled state depth exceeds u64"), }]); } None } pub fn rng(&mut self) -> &mut impl Rng { self.test_runner().rng() } pub fn test_runner(&mut self) -> &mut TestRunner { self.test_runner.get_or_insert_with(|| match self.config.seed { Some(seed) => TestRunner::new_with_rng( proptest::test_runner::Config::default(), TestRng::from_seed(RngAlgorithm::ChaCha, &seed.to_be_bytes::<32>()), ), None => TestRunner::new(proptest::test_runner::Config::default()), }) } 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 { self.arbitrary_storage.get_or_insert_with(ArbitraryStorage::default) } /// Whether the given address has arbitrary storage. pub fn has_arbitrary_storage(&self, address: &Address) -> bool { match &self.arbitrary_storage { Some(storage) => storage.values.contains_key(address), None => false, } } /// Whether the given slot of address with arbitrary storage should be overwritten. /// True if address is marked as and overwrite and if no value was previously generated for /// given slot. pub fn should_overwrite_arbitrary_storage( &self, address: &Address, storage_slot: U256, ) -> bool { match &self.arbitrary_storage { Some(storage) => { storage.overwrites.contains(address) && storage .values .get(address) .and_then(|arbitrary_values| arbitrary_values.get(&storage_slot)) .is_none() } None => false, } } /// Whether the given address is a copy of an address with arbitrary storage. pub fn is_arbitrary_storage_copy(&self, address: &Address) -> bool { match &self.arbitrary_storage { Some(storage) => storage.copies.contains_key(address), None => false, } } /// Returns struct definitions from the analysis, if available. pub fn struct_defs(&self) -> Option<&foundry_common::fmt::StructDefinitions> { self.analysis.as_ref().and_then(|analysis| analysis.struct_defs().ok()) } } impl Inspector for Cheatcodes { fn initialize_interp(&mut self, interpreter: &mut Interpreter, ecx: &mut CTX) { // 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.set_block(block); } if let Some(gas_price) = self.gas_price.take() { ecx.tx_mut().set_gas_price(gas_price); } // Record gas for current frame. if self.gas_metering.paused { self.gas_metering.paused_frames.push(interpreter.gas); } // `expectRevert`: track the max call depth during `expectRevert` if let Some(expected) = &mut self.expected_revert { expected.max_depth = max(ecx.journal().depth(), expected.max_depth); } } fn step(&mut self, interpreter: &mut Interpreter, ecx: &mut CTX) { self.pc = interpreter.bytecode.pc(); if self.broadcast.is_some() { self.set_gas_limit_type(interpreter); } // `pauseGasMetering`: pause / resume interpreter gas. if self.gas_metering.paused { self.meter_gas(interpreter); } // `resetGasMetering`: reset interpreter gas. if self.gas_metering.reset { self.meter_gas_reset(interpreter); } // `record`: record storage reads and writes. if self.recording_accesses { self.record_accesses(interpreter); } // `startStateDiffRecording`: record granular ordered storage accesses. if self.recorded_account_diffs_stack.is_some() { self.record_state_diffs(interpreter, ecx); } // `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.journal().depth().try_into().expect("journaled state depth exceeds u64"), ); } // `startMappingRecording`: record SSTORE and KECCAK256. if let Some(mapping_slots) = &mut self.mapping_slots { mapping_step(mapping_slots, interpreter); } // `snapshotGas*`: take a snapshot of the current gas. if self.gas_metering.recording { self.meter_gas_record(interpreter, ecx); } } fn step_end(&mut self, interpreter: &mut Interpreter, ecx: &mut CTX) { if self.gas_metering.paused { self.meter_gas_end(interpreter); } if self.gas_metering.touched { self.meter_gas_check(interpreter); } // `setArbitraryStorage` and `copyStorage`: add arbitrary values to storage. if self.arbitrary_storage.is_some() { self.arbitrary_storage_end(interpreter, ecx); } } fn log(&mut self, _ecx: &mut CTX, log: Log) { if !self.expected_emits.is_empty() && let Some(err) = expect::handle_expect_emit(self, &log, None) { // Because we do not have access to the interpreter here, we cannot fail the test // immediately. In most cases the failure will still be caught on `call_end`. // In the rare case it is not, we log the error here. let _ = sh_err!("{err:?}"); } // `recordLogs` record_logs(&mut self.recorded_logs, &log); } fn log_full(&mut self, interpreter: &mut Interpreter, _ecx: &mut CTX, log: Log) { if !self.expected_emits.is_empty() { expect::handle_expect_emit(self, &log, Some(interpreter)); } // `recordLogs` record_logs(&mut self.recorded_logs, &log); } fn call(&mut self, ecx: &mut CTX, inputs: &mut CallInputs) -> Option { Self::call_with_executor(self, ecx, inputs, &mut TransparentCheatcodesExecutor) } fn call_end(&mut self, ecx: &mut CTX, call: &CallInputs, outcome: &mut CallOutcome) { let cheatcode_call = call.target_address == CHEATCODE_ADDRESS || call.target_address == HARDHAT_CONSOLE_ADDRESS; // Clean up pranks/broadcasts if it's not a cheatcode call end. We shouldn't do // it for cheatcode calls because they are not applied for cheatcodes in the `call` hook. // This should be placed before the revert handling, because we might exit early there if !cheatcode_call { // Clean up pranks let curr_depth = ecx.journal().depth(); if let Some(prank) = &self.get_prank(curr_depth) && curr_depth == prank.depth { ecx.tx_mut().set_caller(prank.prank_origin); // Clean single-call prank once we have returned to the original depth if prank.single_call { self.pranks.remove(&curr_depth); } } // Clean up broadcast if let Some(broadcast) = &self.broadcast && curr_depth == broadcast.depth { ecx.tx_mut().set_caller(broadcast.original_origin); // Clean single-call broadcast once we have returned to the original depth if broadcast.single_call { let _ = self.broadcast.take(); } } } // Handle assume no revert cheatcode. if let Some(assume_no_revert) = &mut self.assume_no_revert { // Record current reverter address before processing the expect revert if call reverted, // expect revert is set with expected reverter address and no actual reverter set yet. 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 let curr_depth = ecx.journal().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() { let assume_no_revert = std::mem::take(&mut self.assume_no_revert).unwrap(); return match revert_handlers::handle_assume_no_revert( &assume_no_revert, outcome.result.result, &outcome.result.output, &self.config.available_artifacts, ) { // if result is Ok, it was an anticipated revert; return an "assume" error // to reject this run Ok(_) => { outcome.result.output = Error::from(MAGIC_ASSUME).abi_encode().into(); } // if result is Error, it was an unanticipated revert; should revert // normally Err(error) => { 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(); } }; } else { // Call didn't revert, reset `assume_no_revert` state. self.assume_no_revert = None; } } } // Handle expected reverts. if let Some(expected_revert) = &mut self.expected_revert { // Record current reverter address and call scheme before processing the expect revert // if call reverted. if outcome.result.is_revert() { // Record current reverter address if expect revert is set with expected reverter // address and no actual reverter was set yet or if we're expecting more than one // revert. if expected_revert.reverter.is_some() && (expected_revert.reverted_by.is_none() || expected_revert.count > 1) { expected_revert.reverted_by = Some(call.target_address); } } let curr_depth = ecx.journal().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 // `vm.expectCheatcodeRevert` and shouldn't expect revert here ExpectedRevertKind::Cheatcode { pending_processing } => { cheatcode_call && !pending_processing } }; if needs_processing { let mut expected_revert = std::mem::take(&mut self.expected_revert).unwrap(); return match revert_handlers::handle_expect_revert( cheatcode_call, false, self.config.internal_expect_revert, &expected_revert, outcome.result.result, outcome.result.output.clone(), &self.config.available_artifacts, ) { Err(error) => { trace!(expected=?expected_revert, ?error, status=?outcome.result.result, "Expected revert mismatch"); outcome.result.result = InstructionResult::Revert; outcome.result.output = error.abi_encode().into(); } Ok((_, retdata)) => { expected_revert.actual_count += 1; if expected_revert.actual_count < expected_revert.count { self.expected_revert = Some(expected_revert); } outcome.result.result = InstructionResult::Return; outcome.result.output = retdata; } }; } // Flip `pending_processing` flag for cheatcode revert expectations, marking that // we've exited the `expectCheatcodeRevert` call scope if let ExpectedRevertKind::Cheatcode { pending_processing } = &mut self.expected_revert.as_mut().unwrap().kind { *pending_processing = false; } } } // Exit early for calls to cheatcodes as other logic is not relevant for cheatcode // invocations if cheatcode_call { return; } // Record the gas usage of the call, this allows the `lastCallGas` cheatcode to // retrieve the gas usage of the last call. let gas = outcome.result.gas; self.gas_metering.last_call_gas = Some(crate::Vm::Gas { gasLimit: gas.limit(), gasTotalUsed: gas.spent(), gasMemoryUsed: 0, gasRefunded: gas.refunded(), gasRemaining: gas.remaining(), }); // 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 ecx.journal().depth() > 0 && let Some(mut last_recorded_depth) = 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_recorded_depth.iter_mut().for_each(|element| { element.reverted = true; element .storageAccesses .iter_mut() .for_each(|storage_access| storage_access.reverted = true); }) } if let Some(call_access) = last_recorded_depth.first_mut() { // Assert that we're at the correct depth before recording post-call state // 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. let curr_depth = ecx.journal().depth(); if call_access.depth == curr_depth as u64 && let Ok(acc) = ecx.journal_mut().load_account(call.target_address) { debug_assert!(access_is_call(call_access.kind)); call_access.newBalance = acc.data.info.balance; call_access.newNonce = acc.data.info.nonce; } // 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.extend(last_recorded_depth); } else { recorded_account_diffs_stack.push(last_recorded_depth); } } } } // At the end of the call, // we need to check if we've found all the emits. // We know we've found all the expected emits in the right order // if the queue is fully matched. // If it's not fully matched, then either: // 1. Not enough events were emitted (we'll know this because the amount of times we // inspected events will be less than the size of the queue) 2. The wrong events // were emitted (The inspected events should match the size of the queue, but still some // events will not be matched) // First, check that we're at the call depth where the emits were declared from. let should_check_emits = self .expected_emits .iter() .any(|(expected, _)| { let curr_depth = ecx.journal().depth(); expected.depth == curr_depth }) && // Ignore staticcalls !call.is_static; if should_check_emits { let expected_counts = self .expected_emits .iter() .filter_map(|(expected, count_map)| { let count = match expected.address { Some(emitter) => match count_map.get(&emitter) { Some(log_count) => expected .log .as_ref() .map(|l| log_count.count(l)) .unwrap_or_else(|| log_count.count_unchecked()), None => 0, }, None => match &expected.log { Some(log) => count_map.values().map(|logs| logs.count(log)).sum(), None => count_map.values().map(|logs| logs.count_unchecked()).sum(), }, }; if count != expected.count { Some((expected, count)) } else { None } }) .collect::>(); // Revert if not all emits expected were matched. if let Some((expected, _)) = self .expected_emits .iter() .find(|(expected, _)| !expected.found && expected.count > 0) { outcome.result.result = InstructionResult::Revert; let error_msg = expected.mismatch_error.as_deref().unwrap_or("log != expected log"); outcome.result.output = error_msg.abi_encode().into(); return; } if !expected_counts.is_empty() { let msg = if outcome.result.is_ok() { let (expected, count) = expected_counts.first().unwrap(); format!("log emitted {count} times, expected {}", expected.count) } else { "expected an emit, but the call reverted instead. \ ensure you're testing the happy path when using `expectEmit`" .to_string() }; outcome.result.result = InstructionResult::Revert; outcome.result.output = Error::encode(msg); return; } // All emits were found, we're good. // Clear the queue, as we expect the user to declare more events for the next call // if they wanna match further events. self.expected_emits.clear() } // this will ensure we don't have false positives when trying to diagnose reverts in fork // mode let diag = self.fork_revert_diagnostic.take(); // if there's a revert and a previous call was diagnosed as fork related revert then we can // return a better error here if outcome.result.is_revert() && let Some(err) = diag { outcome.result.output = Error::encode(err.to_error_msg(&self.labels)); 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.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() && outcome.result.result == InstructionResult::Stop && call.target_address != test_contract { self.fork_revert_diagnostic = ecx.db().diagnose_revert(call.target_address, ecx.journal().evm_state()); } } // If the depth is 0, then this is the root call terminating if ecx.journal().depth() == 0 { // If we already have a revert, we shouldn't run the below logic as it can obfuscate an // earlier error that happened first with unrelated information about // another error when using cheatcodes. if outcome.result.is_revert() { return; } // If there's not a revert, we can continue on to run the last logic for expect* // cheatcodes. // Match expected calls for (address, calldatas) in &self.expected_calls { // Loop over each address, and for each address, loop over each calldata it expects. for (calldata, (expected, actual_count)) in calldatas { // Grab the values we expect to see let ExpectedCallData { gas, min_gas, value, count, call_type } = expected; let failed = match call_type { // If the cheatcode was called with a `count` argument, // we must check that the EVM performed a CALL with this calldata exactly // `count` times. ExpectedCallType::Count => *count != *actual_count, // If the cheatcode was called without a `count` argument, // we must check that the EVM performed a CALL with this calldata at least // `count` times. The amount of times to check was // the amount of time the cheatcode was called. ExpectedCallType::NonCount => *count > *actual_count, }; if failed { let expected_values = [ Some(format!("data {}", hex::encode_prefixed(calldata))), value.as_ref().map(|v| format!("value {v}")), gas.map(|g| format!("gas {g}")), min_gas.map(|g| format!("minimum gas {g}")), ] .into_iter() .flatten() .join(", "); let but = if outcome.result.is_ok() { let s = if *actual_count == 1 { "" } else { "s" }; format!("was called {actual_count} time{s}") } else { "the call reverted instead; \ ensure you're testing the happy path when using `expectCall`" .to_string() }; let s = if *count == 1 { "" } else { "s" }; let msg = format!( "expected call to {address} with {expected_values} \ to be called {count} time{s}, but {but}" ); outcome.result.result = InstructionResult::Revert; outcome.result.output = Error::encode(msg); return; } } } // Check if we have any leftover expected emits // First, if any emits were found at the root call, then we its ok and we remove them. // For count=0 expectations, NOT being found is success, so mark them as found for (expected, _) in &mut self.expected_emits { if expected.count == 0 && !expected.found { expected.found = true; } } self.expected_emits.retain(|(expected, _)| !expected.found); // If not empty, we got mismatched emits if !self.expected_emits.is_empty() { let msg = if outcome.result.is_ok() { "expected an emit, but no logs were emitted afterwards. \ you might have mismatched events or not enough events were emitted" } else { "expected an emit, but the call reverted instead. \ ensure you're testing the happy path when using `expectEmit`" }; outcome.result.result = InstructionResult::Revert; outcome.result.output = Error::encode(msg); return; } // Check for leftover expected creates if let Some(expected_create) = self.expected_creates.first() { let msg = format!( "expected {} call by address {} for bytecode {} but not found", expected_create.create_scheme, hex::encode_prefixed(expected_create.deployer), hex::encode_prefixed(&expected_create.bytecode), ); outcome.result.result = InstructionResult::Revert; outcome.result.output = Error::encode(msg); } } } fn create(&mut self, ecx: &mut CTX, mut input: &mut CreateInputs) -> Option { // Apply custom execution evm version. if let Some(spec_id) = self.execution_evm_version { ecx.cfg_mut().set_spec(spec_id); } let gas = Gas::new(input.gas_limit()); // 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 }, address: None, }); } let curr_depth = ecx.journal().depth(); // Apply our prank if let Some(prank) = &self.get_prank(curr_depth) && 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 { // Ensure new caller is loaded and touched let _ = journaled_account(ecx, prank.new_caller); 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_mut().set_caller(new_origin); prank_applied = true; } // If prank applied for first time, then update if prank_applied && 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) = &mut self.broadcast && curr_depth >= broadcast.depth && input.caller() == broadcast.original_caller { if let Err(err) = ecx.journal_mut().load_account(broadcast.new_origin) { return Some(CreateOutcome { result: InterpreterResult { result: InstructionResult::Revert, output: Error::encode(err), gas, }, address: None, }); } ecx.tx_mut().set_caller(broadcast.new_origin); if curr_depth == broadcast.depth || broadcast.deploy_from_code { // Reset deploy from code flag for upcoming calls; broadcast.deploy_from_code = false; input.set_caller(broadcast.new_origin); let rpc = ecx.db().active_fork_url(); let account = &ecx.journal().evm_state()[&broadcast.new_origin]; self.broadcastable_transactions.push_back(BroadcastableTransaction { rpc, from: broadcast.new_origin, to: None, value: input.value(), input: input.init_code(), nonce: account.info.nonce, gas: None, kind: BroadcastKind::unsigned(), }); 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.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 oldNonce: 0, // new contract starts with nonce 0 newNonce: 1, // updated on create_end (contracts start with nonce 1) 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 create_end(&mut self, ecx: &mut CTX, call: &CreateInputs, outcome: &mut CreateOutcome) { let call = Some(call); let curr_depth = ecx.journal().depth(); // Clean up pranks if let Some(prank) = &self.get_prank(curr_depth) && curr_depth == prank.depth { ecx.tx_mut().set_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 && curr_depth == broadcast.depth { ecx.tx_mut().set_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 && 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 && 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.journal().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 && let Ok(created_acc) = ecx.journal_mut().load_account(address) { create_access.newBalance = created_acc.data.info.balance; create_access.newNonce = created_acc.data.info.nonce; create_access.deployedCode = created_acc .data .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() && let (Some(address), Some(call)) = (outcome.address, call) && let Ok(created_acc) = ecx.journal_mut().load_account(address) { let bytecode = created_acc.data.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 FoundryInspectorExt for Cheatcodes { fn should_use_create2_factory(&mut self, depth: usize, inputs: &CreateInputs) -> bool { if let CreateScheme::Create2 { .. } = inputs.scheme() { let target_depth = if let Some(prank) = &self.get_prank(depth) { prank.depth } else if let Some(broadcast) = &self.broadcast { broadcast.depth } else { 1 }; depth == target_depth && (self.broadcast.is_some() || self.config.always_use_create_2_factory) } else { false } } fn create2_deployer(&self) -> Address { self.config.evm_opts.create2_deployer } } impl Cheatcodes { #[cold] fn meter_gas(&mut self, interpreter: &mut Interpreter) { if let Some(paused_gas) = self.gas_metering.paused_frames.last() { // Keep gas constant if paused. // Make sure we record the memory changes so that memory expansion is not paused. let memory = *interpreter.gas.memory(); interpreter.gas = *paused_gas; interpreter.gas.memory_mut().words_num = memory.words_num; interpreter.gas.memory_mut().expansion_cost = memory.expansion_cost; } else { // Record frame paused gas. self.gas_metering.paused_frames.push(interpreter.gas); } } #[cold] fn meter_gas_record(&mut self, interpreter: &mut Interpreter, ecx: &mut CTX) { if interpreter.bytecode.action.as_ref().and_then(|i| i.instruction_result()).is_none() { self.gas_metering.gas_records.iter_mut().for_each(|record| { let curr_depth = ecx.journal().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); 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(); } }); } } #[cold] fn meter_gas_end(&mut self, interpreter: &mut Interpreter) { // Remove recorded gas if we exit frame. if let Some(interpreter_action) = interpreter.bytecode.action.as_ref() && will_exit(interpreter_action) { self.gas_metering.paused_frames.pop(); } } #[cold] fn meter_gas_reset(&mut self, interpreter: &mut Interpreter) { let mut gas = Gas::new(interpreter.gas.limit()); gas.memory_mut().words_num = interpreter.gas.memory().words_num; gas.memory_mut().expansion_cost = interpreter.gas.memory().expansion_cost; interpreter.gas = gas; self.gas_metering.reset = false; } #[cold] fn meter_gas_check(&mut self, interpreter: &mut Interpreter) { if let Some(interpreter_action) = interpreter.bytecode.action.as_ref() && will_exit(interpreter_action) { // 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() { interpreter.gas = Gas::new(interpreter.gas.limit()); } } } /// Generates or copies arbitrary values for storage slots. /// Invoked in inspector `step_end` (when the current opcode is not executed), if current opcode /// to execute is `SLOAD` and storage slot is cold. /// Ensures that in next step (when `SLOAD` opcode is executed) an arbitrary value is returned: /// - copies the existing arbitrary storage value (or the new generated one if no value in /// cache) from mapped source address to the target address. /// - generates arbitrary value and saves it in target address storage. #[cold] fn arbitrary_storage_end( &mut self, interpreter: &mut Interpreter, ecx: &mut CTX, ) { let (key, target_address) = if interpreter.bytecode.opcode() == op::SLOAD { (try_or_return!(interpreter.stack.peek(0)), interpreter.input.target_address) } else { return; }; let Some(value) = ecx.sload(target_address, key) else { return; }; if (value.is_cold && value.data.is_zero()) || self.should_overwrite_arbitrary_storage(&target_address, key) { if self.has_arbitrary_storage(&target_address) { let arbitrary_value = self.rng().random(); self.arbitrary_storage.as_mut().unwrap().save( ecx, target_address, key, arbitrary_value, ); } else if self.is_arbitrary_storage_copy(&target_address) { let arbitrary_value = self.rng().random(); self.arbitrary_storage.as_mut().unwrap().copy( ecx, target_address, key, arbitrary_value, ); } } } /// Records storage slots reads and writes. #[cold] fn record_accesses(&mut self, interpreter: &mut Interpreter) { let access = &mut self.accesses; match interpreter.bytecode.opcode() { op::SLOAD => { 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.input.target_address, key); } _ => {} } } #[cold] fn record_state_diffs>( &mut self, interpreter: &mut Interpreter, ecx: &mut CTX, ) { let Some(account_accesses) = &mut self.recorded_account_diffs_stack else { return }; 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, nonce and initialized status of the target account let target = try_or_return!(interpreter.stack.peek(0)); let target = Address::from_word(B256::from(target)); let (initialized, old_balance, old_nonce) = ecx .journal_mut() .load_account(target) .map(|account| { ( account.data.info.exists(), account.data.info.balance, account.data.info.nonce, ) }) .unwrap_or_default(); // load balance of this account let value = ecx .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.cfg().chain_id()), }, accessor: interpreter.input.target_address, account: target, kind: crate::Vm::AccountAccessKind::SelfDestruct, initialized, oldBalance: old_balance, newBalance: old_balance + value, oldNonce: old_nonce, newNonce: old_nonce, // nonce doesn't change on selfdestruct value, data: Bytes::new(), reverted: false, deployedCode: Bytes::new(), storageAccesses: vec![], depth: ecx .journal() .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.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.journal_mut().load_account(address).is_ok() && let Some(previous) = ecx.sload(address, key) { present_value = previous.data; } let access = crate::Vm::StorageAccess { account: interpreter.input.target_address, slot: key.into(), isWrite: false, previousValue: present_value.into(), newValue: present_value.into(), reverted: false, }; let curr_depth = ecx.journal().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.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.journal_mut().load_account(address).is_ok() && let Some(previous) = ecx.sload(address, key) { previous_value = previous.data; } let access = crate::Vm::StorageAccess { account: address, slot: key.into(), isWrite: true, previousValue: previous_value.into(), newValue: value.into(), reverted: false, }; let curr_depth = ecx.journal().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.bytecode.opcode() { op::EXTCODECOPY => crate::Vm::AccountAccessKind::Extcodecopy, op::EXTCODESIZE => crate::Vm::AccountAccessKind::Extcodesize, op::EXTCODEHASH => crate::Vm::AccountAccessKind::Extcodehash, op::BALANCE => crate::Vm::AccountAccessKind::Balance, _ => unreachable!(), }; let address = Address::from_word(B256::from(try_or_return!(interpreter.stack.peek(0)))); let initialized; let balance; let nonce; if let Ok(acc) = ecx.journal_mut().load_account(address) { initialized = acc.data.info.exists(); balance = acc.data.info.balance; nonce = acc.data.info.nonce; } else { initialized = false; balance = U256::ZERO; nonce = 0; } let curr_depth = ecx.journal().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.cfg().chain_id()), }, accessor: interpreter.input.target_address, account: address, kind, initialized, oldBalance: balance, newBalance: balance, oldNonce: nonce, newNonce: nonce, // EXT* operations don't change nonce value: U256::ZERO, data: Bytes::new(), reverted: false, deployedCode: Bytes::new(), storageAccesses: vec![], 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) if let Some(last) = account_accesses.last_mut() { last.push(account_access); } else { account_accesses.push(vec![account_access]); } } _ => {} } } /// Checks to see if the current opcode can either mutate directly or expand memory. /// /// If the opcode at the current program counter is a match, check if the modified memory lies /// within the allowed ranges. If not, revert and fail the test. #[cold] fn check_mem_opcodes(&self, interpreter: &mut Interpreter, depth: u64) { let Some(ranges) = self.allowed_mem_writes.get(&depth) else { return; }; // The `mem_opcode_match` macro is used to match the current opcode against a list of // opcodes that can mutate memory (either directly or expansion via reading). If the // opcode is a match, the memory offsets that are being written to are checked to be // within the allowed ranges. If not, the test is failed and the transaction is // reverted. For all opcodes that can mutate memory aside from MSTORE, // MSTORE8, and MLOAD, the size and destination offset are on the stack, and // the macro expands all of these cases. For MSTORE, MSTORE8, and MLOAD, the // 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.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::(); // If none of the allowed ranges contain [offset, offset + 32), memory has been // unexpectedly mutated. if !ranges.iter().any(|range| { range.contains(&offset) && range.contains(&(offset + 31)) }) { // SPECIAL CASE: When the compiler attempts to store the selector for // `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>(); if value[..SELECTOR_LEN] == stopExpectSafeMemoryCall::SELECTOR { return } disallowed_mem_write(offset, 32, interpreter, ranges); return } } 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::(); // If none of the allowed ranges contain the offset, memory has been // unexpectedly mutated. if !ranges.iter().any(|range| range.contains(&offset)) { disallowed_mem_write(offset, 1, interpreter, ranges); return } } //////////////////////////////////////////////////////////////// // OPERATIONS THAT CAN EXPAND MEMORY BY READING // //////////////////////////////////////////////////////////////// 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::(); // 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.memory.size() as u64 && !ranges.iter().any(|range| { range.contains(&offset) && range.contains(&(offset + 31)) }) { disallowed_mem_write(offset, 32, interpreter, ranges); return } } //////////////////////////////////////////////////////////////// // OPERATIONS WITH OFFSET AND SIZE ON STACK // //////////////////////////////////////////////////////////////// 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::(); // 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::(); // If none of the allowed ranges contain [dest_offset, dest_offset + size), // memory outside of the expected ranges has been touched. If the opcode // only reads from memory, this is okay as long as the memory is not expanded. let fail_cond = !ranges.iter().any(|range| { range.contains(&dest_offset) && range.contains(&(dest_offset + size.saturating_sub(1))) }); // If the failure condition is met, set the output buffer to a revert string // that gives information about the allowed ranges and revert. if fail_cond { // 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()); 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.memory.slice_len(args_offset, args_size); if memory_word[..SELECTOR_LEN] == stopExpectSafeMemoryCall::SELECTOR { return } } disallowed_mem_write(dest_offset, size, interpreter, ranges); return } } $(op::$opcode => { // The destination offset of the operation. 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::(); // If none of the allowed ranges contain [dest_offset, dest_offset + size), // memory outside of the expected ranges has been touched. If the opcode // only reads from memory, this is okay as long as the memory is not expanded. let fail_cond = !ranges.iter().any(|range| { range.contains(&dest_offset) && range.contains(&(dest_offset + size.saturating_sub(1))) }) && ($writes || [dest_offset, (dest_offset + size).saturating_sub(1)].into_iter().any(|offset| { offset >= interpreter.memory.size() as u64 }) ); // If the failure condition is met, set the output buffer to a revert string // that gives information about the allowed ranges and revert. if fail_cond { disallowed_mem_write(dest_offset, size, interpreter, ranges); return } })* _ => {} } } } // Check if the current opcode can write to memory, and if so, check if the memory // being written to is registered as safe to modify. mem_opcode_match!( (CALLDATACOPY, 0, 2, true), (CODECOPY, 0, 2, true), (RETURNDATACOPY, 0, 2, true), (EXTCODECOPY, 1, 3, true), (CALLCODE, 5, 6, true), (STATICCALL, 4, 5, true), (DELEGATECALL, 4, 5, true), (KECCAK256, 0, 1, false), (LOG0, 0, 1, false), (LOG1, 0, 1, false), (LOG2, 0, 1, false), (LOG3, 0, 1, false), (LOG4, 0, 1, false), (CREATE, 1, 2, false), (CREATE2, 1, 2, false), (RETURN, 0, 1, false), (REVERT, 0, 1, false), ); } #[cold] fn set_gas_limit_type(&mut self, interpreter: &mut Interpreter) { match interpreter.bytecode.opcode() { op::CREATE2 => self.dynamic_gas_limit = true, op::CALL => { // If first element of the stack is close to current remaining gas then assume // dynamic gas limit. self.dynamic_gas_limit = try_or_return!(interpreter.stack.peek(0)) >= interpreter.gas.remaining() - 100 } _ => self.dynamic_gas_limit = false, } } } /// Helper that expands memory, stores a revert string pertaining to a disallowed memory write, /// and sets the return range to the revert string's location in memory. /// /// This will set the interpreter's next action to a return with the revert string as the output. /// And trigger a revert. fn disallowed_mem_write( dest_offset: u64, size: u64, interpreter: &mut Interpreter, ranges: &[Range], ) { let revert_string = format!( "memory write at offset 0x{:02X} of size 0x{:02X} not allowed; safe range: {}", dest_offset, size, ranges.iter().map(|r| format!("(0x{:02X}, 0x{:02X}]", r.start, r.end)).join(" U ") ); interpreter.bytecode.set_action(InterpreterAction::new_return( InstructionResult::Revert, Bytes::from(revert_string.into_bytes()), interpreter.gas, )); } /// Returns true if the kind of account access is a call. fn access_is_call(kind: crate::Vm::AccountAccessKind) -> bool { matches!( kind, crate::Vm::AccountAccessKind::Call | crate::Vm::AccountAccessKind::StaticCall | crate::Vm::AccountAccessKind::CallCode | crate::Vm::AccountAccessKind::DelegateCall ) } /// Records a log into the recorded logs vector, if it exists. fn record_logs(recorded_logs: &mut Option>, log: &Log) { if let Some(storage_recorded_logs) = recorded_logs { storage_recorded_logs.push(Vm::Log { topics: log.data.topics().to_vec(), data: log.data.data.clone(), emitter: log.address, }); } } /// Appends an AccountAccess that resumes the recording of the current context. fn append_storage_access( last: &mut Vec, storage_access: crate::Vm::StorageAccess, storage_depth: u64, ) { // Assert that there's an existing record for the current context. if !last.is_empty() && last.first().unwrap().depth < storage_depth { // Three cases to consider: // 1. If there hasn't been a context switch since the start of this context, then add the // storage access to the current context record. // 2. If there's an existing Resume record, then add the storage access to it. // 3. Otherwise, create a new Resume record based on the current context. if last.len() == 1 { last.first_mut().unwrap().storageAccesses.push(storage_access); } else { let last_record = last.last_mut().unwrap(); if last_record.kind as u8 == crate::Vm::AccountAccessKind::Resume as u8 { last_record.storageAccesses.push(storage_access); } else { let entry = last.first().unwrap(); let resume_record = crate::Vm::AccountAccess { chainInfo: crate::Vm::ChainInfo { forkId: entry.chainInfo.forkId, chainId: entry.chainInfo.chainId, }, accessor: entry.accessor, account: entry.account, kind: crate::Vm::AccountAccessKind::Resume, initialized: entry.initialized, storageAccesses: vec![storage_access], reverted: entry.reverted, // The remaining fields are defaults oldBalance: U256::ZERO, newBalance: U256::ZERO, oldNonce: 0, newNonce: 0, value: U256::ZERO, data: Bytes::new(), deployedCode: Bytes::new(), depth: entry.depth, }; last.push(resume_record); } } } } /// Returns the [`spec::Cheatcode`] definition for a given [`spec::CheatcodeDef`] implementor. fn cheatcode_of(_: &T) -> &'static spec::Cheatcode<'static> { T::CHEATCODE } fn cheatcode_name(cheat: &spec::Cheatcode<'static>) -> &'static str { cheat.func.signature.split('(').next().unwrap() } fn cheatcode_id(cheat: &spec::Cheatcode<'static>) -> &'static str { cheat.func.id } fn cheatcode_signature(cheat: &spec::Cheatcode<'static>) -> &'static str { cheat.func.signature } /// Dispatches the cheatcode call to the appropriate function. fn apply_dispatch( calls: &Vm::VmCalls, ccx: &mut CheatsCtxt<'_, CTX>, executor: &mut dyn CheatcodesExecutor, ) -> Result { // Extract metadata for logging/deprecation via CheatcodeDef. macro_rules! get_cheatcode { ($($variant:ident),*) => { match calls { $(Vm::VmCalls::$variant(cheat) => cheatcode_of(cheat),)* } }; } let cheat = vm_calls!(get_cheatcode); let _guard = debug_span!(target: "cheatcodes", "apply", id = %cheatcode_id(cheat)).entered(); trace!(target: "cheatcodes", cheat = %cheatcode_signature(cheat), "applying"); if let spec::Status::Deprecated(replacement) = cheat.status { ccx.state.deprecated.insert(cheatcode_signature(cheat), replacement); } // Monomorphized dispatch: calls apply_full directly, no trait objects. macro_rules! dispatch { ($($variant:ident),*) => { match calls { $(Vm::VmCalls::$variant(cheat) => Cheatcode::apply_full(cheat, ccx, executor),)* } }; } let mut result = vm_calls!(dispatch); // Format the error message to include the cheatcode name. if let Err(e) = &mut result && e.is_str() { let name = cheatcode_name(cheat); // Skip showing the cheatcode name for: // - assertions: too verbose, and can already be inferred from the error message // - `rpcUrl`: forge-std relies on it in `getChainWithUpdatedRpcUrl` if !name.contains("assert") && name != "rpcUrl" { *e = fmt_err!("vm.{name}: {e}"); } } trace!( target: "cheatcodes", return = %match &result { Ok(b) => hex::encode(b), Err(e) => e.to_string(), } ); result } /// Helper function to check if frame execution will exit. fn will_exit(action: &InterpreterAction) -> bool { match action { InterpreterAction::Return(result) => { result.result.is_ok_or_revert() || result.result.is_error() } _ => false, } } ================================================ FILE: crates/cheatcodes/src/json.rs ================================================ //! Implementations of [`Json`](spec::Group::Json) cheatcodes. use crate::{Cheatcode, Cheatcodes, Result, Vm::*, string}; use alloy_dyn_abi::{DynSolType, DynSolValue, Resolver, eip712_parser::EncodeType}; use alloy_primitives::{Address, B256, I256, U256, hex}; use alloy_sol_types::SolValue; use foundry_common::{fmt::StructDefinitions, fs}; use foundry_config::fs_permissions::FsAccessKind; use serde_json::{Map, Value}; use std::{ borrow::Cow, collections::{BTreeMap, BTreeSet}, }; impl Cheatcode for keyExistsCall { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { json, key } = self; check_json_key_exists(json, key) } } impl Cheatcode for keyExistsJsonCall { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { json, key } = self; check_json_key_exists(json, key) } } impl Cheatcode for parseJson_0Call { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { json } = self; parse_json(json, "$", state.struct_defs()) } } impl Cheatcode for parseJson_1Call { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { json, key } = self; parse_json(json, key, state.struct_defs()) } } impl Cheatcode for parseJsonUintCall { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { json, key } = self; parse_json_coerce(json, key, &DynSolType::Uint(256)) } } impl Cheatcode for parseJsonUintArrayCall { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { json, key } = self; parse_json_coerce(json, key, &DynSolType::Array(Box::new(DynSolType::Uint(256)))) } } impl Cheatcode for parseJsonIntCall { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { json, key } = self; parse_json_coerce(json, key, &DynSolType::Int(256)) } } impl Cheatcode for parseJsonIntArrayCall { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { json, key } = self; parse_json_coerce(json, key, &DynSolType::Array(Box::new(DynSolType::Int(256)))) } } impl Cheatcode for parseJsonBoolCall { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { json, key } = self; parse_json_coerce(json, key, &DynSolType::Bool) } } impl Cheatcode for parseJsonBoolArrayCall { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { json, key } = self; parse_json_coerce(json, key, &DynSolType::Array(Box::new(DynSolType::Bool))) } } impl Cheatcode for parseJsonAddressCall { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { json, key } = self; parse_json_coerce(json, key, &DynSolType::Address) } } impl Cheatcode for parseJsonAddressArrayCall { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { json, key } = self; parse_json_coerce(json, key, &DynSolType::Array(Box::new(DynSolType::Address))) } } impl Cheatcode for parseJsonStringCall { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { json, key } = self; parse_json_coerce(json, key, &DynSolType::String) } } impl Cheatcode for parseJsonStringArrayCall { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { json, key } = self; parse_json_coerce(json, key, &DynSolType::Array(Box::new(DynSolType::String))) } } impl Cheatcode for parseJsonBytesCall { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { json, key } = self; parse_json_coerce(json, key, &DynSolType::Bytes) } } impl Cheatcode for parseJsonBytesArrayCall { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { json, key } = self; parse_json_coerce(json, key, &DynSolType::Array(Box::new(DynSolType::Bytes))) } } impl Cheatcode for parseJsonBytes32Call { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { json, key } = self; parse_json_coerce(json, key, &DynSolType::FixedBytes(32)) } } impl Cheatcode for parseJsonBytes32ArrayCall { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { json, key } = self; parse_json_coerce(json, key, &DynSolType::Array(Box::new(DynSolType::FixedBytes(32)))) } } impl Cheatcode for parseJsonType_0Call { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { json, typeDescription } = self; parse_json_coerce(json, "$", &resolve_type(typeDescription, state.struct_defs())?) .map(|v| v.abi_encode()) } } impl Cheatcode for parseJsonType_1Call { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { json, key, typeDescription } = self; parse_json_coerce(json, key, &resolve_type(typeDescription, state.struct_defs())?) .map(|v| v.abi_encode()) } } impl Cheatcode for parseJsonTypeArrayCall { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { json, key, typeDescription } = self; let ty = resolve_type(typeDescription, state.struct_defs())?; parse_json_coerce(json, key, &DynSolType::Array(Box::new(ty))).map(|v| v.abi_encode()) } } impl Cheatcode for parseJsonKeysCall { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { json, key } = self; parse_json_keys(json, key) } } impl Cheatcode for serializeJsonCall { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { objectKey, value } = self; *state.serialized_jsons.entry(objectKey.into()).or_default() = serde_json::from_str(value)?; Ok(value.abi_encode()) } } impl Cheatcode for serializeBool_0Call { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { objectKey, valueKey, value } = self; serialize_json(state, objectKey, valueKey, (*value).into()) } } impl Cheatcode for serializeUint_0Call { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { objectKey, valueKey, value } = self; serialize_json(state, objectKey, valueKey, (*value).into()) } } impl Cheatcode for serializeInt_0Call { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { objectKey, valueKey, value } = self; serialize_json(state, objectKey, valueKey, (*value).into()) } } impl Cheatcode for serializeAddress_0Call { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { objectKey, valueKey, value } = self; serialize_json(state, objectKey, valueKey, (*value).into()) } } impl Cheatcode for serializeBytes32_0Call { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { objectKey, valueKey, value } = self; serialize_json(state, objectKey, valueKey, DynSolValue::FixedBytes(*value, 32)) } } impl Cheatcode for serializeString_0Call { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { objectKey, valueKey, value } = self; serialize_json(state, objectKey, valueKey, value.clone().into()) } } impl Cheatcode for serializeBytes_0Call { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { objectKey, valueKey, value } = self; serialize_json(state, objectKey, valueKey, value.to_vec().into()) } } impl Cheatcode for serializeBool_1Call { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { objectKey, valueKey, values } = self; serialize_json( state, objectKey, valueKey, DynSolValue::Array(values.iter().copied().map(DynSolValue::Bool).collect()), ) } } impl Cheatcode for serializeUint_1Call { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { objectKey, valueKey, values } = self; serialize_json( state, objectKey, valueKey, DynSolValue::Array(values.iter().map(|v| DynSolValue::Uint(*v, 256)).collect()), ) } } impl Cheatcode for serializeInt_1Call { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { objectKey, valueKey, values } = self; serialize_json( state, objectKey, valueKey, DynSolValue::Array(values.iter().map(|v| DynSolValue::Int(*v, 256)).collect()), ) } } impl Cheatcode for serializeAddress_1Call { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { objectKey, valueKey, values } = self; serialize_json( state, objectKey, valueKey, DynSolValue::Array(values.iter().copied().map(DynSolValue::Address).collect()), ) } } impl Cheatcode for serializeBytes32_1Call { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { objectKey, valueKey, values } = self; serialize_json( state, objectKey, valueKey, DynSolValue::Array(values.iter().map(|v| DynSolValue::FixedBytes(*v, 32)).collect()), ) } } impl Cheatcode for serializeString_1Call { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { objectKey, valueKey, values } = self; serialize_json( state, objectKey, valueKey, DynSolValue::Array(values.iter().cloned().map(DynSolValue::String).collect()), ) } } impl Cheatcode for serializeBytes_1Call { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { objectKey, valueKey, values } = self; serialize_json( state, objectKey, valueKey, DynSolValue::Array( values.iter().cloned().map(Into::into).map(DynSolValue::Bytes).collect(), ), ) } } impl Cheatcode for serializeJsonType_0Call { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { typeDescription, value } = self; let ty = resolve_type(typeDescription, state.struct_defs())?; let value = ty.abi_decode(value)?; let value = foundry_common::fmt::serialize_value_as_json(value, state.struct_defs())?; Ok(value.to_string().abi_encode()) } } impl Cheatcode for serializeJsonType_1Call { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { objectKey, valueKey, typeDescription, value } = self; let ty = resolve_type(typeDescription, state.struct_defs())?; let value = ty.abi_decode(value)?; serialize_json(state, objectKey, valueKey, value) } } impl Cheatcode for serializeUintToHexCall { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { objectKey, valueKey, value } = self; let hex = format!("0x{value:x}"); serialize_json(state, objectKey, valueKey, hex.into()) } } impl Cheatcode for writeJson_0Call { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { json, path } = self; let json = serde_json::from_str(json).unwrap_or_else(|_| Value::String(json.to_owned())); let json_string = serde_json::to_string_pretty(&json)?; super::fs::write_file(state, path.as_ref(), json_string.as_bytes()) } } impl Cheatcode for writeJson_1Call { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { json: value, path, valueKey } = self; // Read, parse, and update the JSON object. // If the file doesn't exist, start with an empty JSON object so the file is created. let data_path = state.config.ensure_path_allowed(path, FsAccessKind::Read)?; let mut data = if data_path.exists() { let data_string = fs::locked_read_to_string(&data_path)?; serde_json::from_str(&data_string).unwrap_or_else(|_| Value::String(data_string)) } else { Value::Object(Default::default()) }; upsert_json_value(&mut data, value, valueKey)?; // Write the updated content back to the file let json_string = serde_json::to_string_pretty(&data)?; super::fs::write_file(state, path.as_ref(), json_string.as_bytes()) } } pub(super) fn check_json_key_exists(json: &str, key: &str) -> Result { let json = parse_json_str(json)?; let values = select(&json, key)?; let exists = !values.is_empty(); Ok(exists.abi_encode()) } pub(super) fn parse_json(json: &str, path: &str, defs: Option<&StructDefinitions>) -> Result { let value = parse_json_str(json)?; let selected = select(&value, path)?; let sol = json_to_sol(defs, &selected)?; Ok(encode(sol)) } pub(super) fn parse_json_coerce(json: &str, path: &str, ty: &DynSolType) -> Result { let json = parse_json_str(json)?; let [value] = select(&json, path)?[..] else { bail!("path {path:?} must return exactly one JSON value"); }; parse_json_as(value, ty).map(|v| v.abi_encode()) } /// Parses given [serde_json::Value] as a [DynSolValue]. pub(super) fn parse_json_as(value: &Value, ty: &DynSolType) -> Result { let to_string = |v: &Value| { let mut s = v.to_string(); s.retain(|c: char| c != '"'); s }; match (value, ty) { (Value::Array(array), ty) => parse_json_array(array, ty), (Value::Object(object), ty) => parse_json_map(object, ty), (Value::String(s), DynSolType::String) => Ok(DynSolValue::String(s.clone())), (Value::String(s), DynSolType::Uint(_) | DynSolType::Int(_)) => string::parse_value(s, ty), _ => string::parse_value(&to_string(value), ty), } } pub(super) fn parse_json_array(array: &[Value], ty: &DynSolType) -> Result { match ty { DynSolType::Tuple(types) => { ensure!(array.len() == types.len(), "array length mismatch"); let values = array .iter() .zip(types) .map(|(e, ty)| parse_json_as(e, ty)) .collect::>>()?; Ok(DynSolValue::Tuple(values)) } DynSolType::Array(inner) => { let values = array.iter().map(|e| parse_json_as(e, inner)).collect::>>()?; Ok(DynSolValue::Array(values)) } DynSolType::FixedArray(inner, len) => { ensure!(array.len() == *len, "array length mismatch"); let values = array.iter().map(|e| parse_json_as(e, inner)).collect::>>()?; Ok(DynSolValue::FixedArray(values)) } _ => bail!("expected {ty}, found array"), } } pub(super) fn parse_json_map(map: &Map, ty: &DynSolType) -> Result { let Some((name, fields, types)) = ty.as_custom_struct() else { bail!("expected {ty}, found JSON object"); }; let mut values = Vec::with_capacity(fields.len()); for (field, ty) in fields.iter().zip(types.iter()) { let Some(value) = map.get(field) else { bail!("field {field:?} not found in JSON object") }; values.push(parse_json_as(value, ty)?); } Ok(DynSolValue::CustomStruct { name: name.to_string(), prop_names: fields.to_vec(), tuple: values, }) } pub(super) fn parse_json_keys(json: &str, key: &str) -> Result { let json = parse_json_str(json)?; let values = select(&json, key)?; let [value] = values[..] else { bail!("key {key:?} must return exactly one JSON object"); }; let Value::Object(object) = value else { bail!("JSON value at {key:?} is not an object"); }; let keys = object.keys().collect::>(); Ok(keys.abi_encode()) } fn parse_json_str(json: &str) -> Result { serde_json::from_str(json).map_err(|e| fmt_err!("failed parsing JSON: {e}")) } fn json_to_sol(defs: Option<&StructDefinitions>, json: &[&Value]) -> Result> { let mut sol = Vec::with_capacity(json.len()); for value in json { sol.push(json_value_to_token(value, defs)?); } Ok(sol) } fn select<'a>(value: &'a Value, mut path: &str) -> Result> { // Handle the special case of the root key if path == "." { path = "$"; } // format error with debug string because json_path errors may contain newlines jsonpath_lib::select(value, &canonicalize_json_path(path)) .map_err(|e| fmt_err!("failed selecting from JSON: {:?}", e.to_string())) } fn encode(values: Vec) -> Vec { // Double `abi_encode` is intentional let bytes = match &values[..] { [] => Vec::new(), [one] => one.abi_encode(), _ => DynSolValue::Array(values).abi_encode(), }; bytes.abi_encode() } /// Canonicalize a json path key to always start from the root of the document. /// Read more about json path syntax: pub(super) fn canonicalize_json_path(path: &str) -> Cow<'_, str> { if !path.starts_with('$') { format!("${path}").into() } else { path.into() } } /// Converts a JSON [`Value`] to a [`DynSolValue`] by trying to guess encoded type. For safer /// decoding, use [`parse_json_as`]. /// /// The function is designed to run recursively, so that in case of an object /// it will call itself to convert each of it's value and encode the whole as a /// Tuple #[instrument(target = "cheatcodes", level = "trace", ret)] pub(super) fn json_value_to_token( value: &Value, defs: Option<&StructDefinitions>, ) -> Result { if let Some(defs) = defs { _json_value_to_token(value, defs) } else { _json_value_to_token(value, &StructDefinitions::default()) } } fn _json_value_to_token(value: &Value, defs: &StructDefinitions) -> Result { match value { Value::Null => Ok(DynSolValue::FixedBytes(B256::ZERO, 32)), Value::Bool(boolean) => Ok(DynSolValue::Bool(*boolean)), Value::Array(array) => array .iter() .map(|v| _json_value_to_token(v, defs)) .collect::>() .map(DynSolValue::Array), Value::Object(map) => { // Try to find a struct definition that matches the object keys. let keys: BTreeSet<_> = map.keys().map(|s| s.as_str()).collect(); let matching_def = defs.values().find(|fields| { fields.len() == keys.len() && fields.iter().map(|(name, _)| name.as_str()).collect::>() == keys }); if let Some(fields) = matching_def { // Found a struct with matching field names, use the order from the definition. fields .iter() .map(|(name, _)| { // unwrap is safe because we know the key exists. _json_value_to_token(map.get(name).unwrap(), defs) }) .collect::>() .map(DynSolValue::Tuple) } else { // Fallback to alphabetical sorting if no matching struct is found. // See: [#3647](https://github.com/foundry-rs/foundry/pull/3647) let ordered_object: BTreeMap<_, _> = map.iter().map(|(k, v)| (k.clone(), v.clone())).collect(); ordered_object .values() .map(|value| _json_value_to_token(value, defs)) .collect::>() .map(DynSolValue::Tuple) } } Value::Number(number) => { if let Some(f) = number.as_f64() { // Check if the number has decimal digits because the EVM does not support floating // point math if f.fract() == 0.0 { // Use the string representation of the `serde_json` Number type instead of // calling f.to_string(), because some numbers are wrongly rounded up after // being convented to f64. // Example: 18446744073709551615 becomes 18446744073709552000 after parsing it // to f64. let s = number.to_string(); // Coerced to scientific notation, so short-circuit to using fallback. // This will not have a problem with hex numbers, as for parsing these // We'd need to prefix this with 0x. // See also if s.contains('e') { // Calling Number::to_string with powers of ten formats the number using // scientific notation and causes from_dec_str to fail. Using format! with // f64 keeps the full number representation. // Example: 100000000000000000000 becomes 1e20 when Number::to_string is // used. let fallback_s = f.to_string(); if let Ok(n) = fallback_s.parse() { return Ok(DynSolValue::Uint(n, 256)); } if let Ok(n) = I256::from_dec_str(&fallback_s) { return Ok(DynSolValue::Int(n, 256)); } } if let Ok(n) = s.parse() { return Ok(DynSolValue::Uint(n, 256)); } if let Ok(n) = s.parse() { return Ok(DynSolValue::Int(n, 256)); } } } Err(fmt_err!("unsupported JSON number: {number}")) } Value::String(string) => { // Hanfl hex strings if let Some(mut val) = string.strip_prefix("0x") { let s; if val.len() == 39 { return Err(format!("Cannot parse \"{val}\" as an address. If you want to specify address, prepend zero to the value.").into()); } if !val.len().is_multiple_of(2) { s = format!("0{val}"); val = &s[..]; } if let Ok(bytes) = hex::decode(val) { return Ok(match bytes.len() { 20 => DynSolValue::Address(Address::from_slice(&bytes)), 32 => DynSolValue::FixedBytes(B256::from_slice(&bytes), 32), _ => DynSolValue::Bytes(bytes), }); } } // Handle large numbers that were potentially encoded as strings because they exceed the // capacity of a 64-bit integer. // Note that number-like strings that *could* fit in an `i64`/`u64` will fall through // and be treated as literal strings. if let Ok(n) = string.parse::() && i64::try_from(n).is_err() { return Ok(DynSolValue::Int(n, 256)); } else if let Ok(n) = string.parse::() && u64::try_from(n).is_err() { return Ok(DynSolValue::Uint(n, 256)); } // Otherwise, treat as a regular string Ok(DynSolValue::String(string.to_owned())) } } } /// Serializes a key:value pair to a specific object. If the key is valueKey, the value is /// expected to be an object, which will be set as the root object for the provided object key, /// overriding the whole root object if the object key already exists. By calling this function /// multiple times, the user can serialize multiple KV pairs to the same object. The value can be of /// any type, even a new object in itself. The function will return a stringified version of the /// object, so that the user can use that as a value to a new invocation of the same function with a /// new object key. This enables the user to reuse the same function to crate arbitrarily complex /// object structures (JSON). fn serialize_json( state: &mut Cheatcodes, object_key: &str, value_key: &str, value: DynSolValue, ) -> Result { let value = foundry_common::fmt::serialize_value_as_json(value, state.struct_defs())?; let map = state.serialized_jsons.entry(object_key.into()).or_default(); map.insert(value_key.into(), value); let stringified = serde_json::to_string(map).unwrap(); Ok(stringified.abi_encode()) } /// Resolves a [DynSolType] from user input. pub(super) fn resolve_type( type_description: &str, struct_defs: Option<&StructDefinitions>, ) -> Result { let ordered_ty = |ty| -> Result { if let Some(defs) = struct_defs { reorder_type(ty, defs) } else { Ok(ty) } }; if let Ok(ty) = DynSolType::parse(type_description) { return ordered_ty(ty); }; if let Ok(encoded) = EncodeType::parse(type_description) { let main_type = encoded.types[0].type_name; let mut resolver = Resolver::default(); for t in &encoded.types { resolver.ingest(t.to_owned()); } // Get the alphabetically-sorted type from the resolver, and reorder if necessary. return ordered_ty(resolver.resolve(main_type)?); } bail!("type description should be a valid Solidity type or a EIP712 `encodeType` string") } /// Upserts a value into a JSON object based on a dot-separated key. /// /// This function navigates through a mutable `serde_json::Value` object using a /// path-like key. It creates nested JSON objects if they do not exist along the path. /// The value is inserted at the final key in the path. /// /// # Arguments /// /// * `data` - A mutable reference to the `serde_json::Value` to be modified. /// * `value` - The string representation of the value to upsert. This string is first parsed as /// JSON, and if that fails, it's treated as a plain JSON string. /// * `key` - A dot-separated string representing the path to the location for upserting. pub(super) fn upsert_json_value(data: &mut Value, value: &str, key: &str) -> Result<()> { // Parse the path key into segments. let canonical_key = canonicalize_json_path(key); let parts: Vec<&str> = canonical_key .strip_prefix("$.") .unwrap_or(key) .split('.') .filter(|s| !s.is_empty()) .collect(); if parts.is_empty() { return Err(fmt_err!("'valueKey' cannot be empty or just '$'")); } // Separate the final key from the path. // Traverse the objects, creating intermediary ones if necessary. if let Some((key_to_insert, path_to_parent)) = parts.split_last() { let mut current_level = data; for segment in path_to_parent { if !current_level.is_object() { return Err(fmt_err!("path segment '{segment}' does not resolve to an object.")); } current_level = current_level .as_object_mut() .unwrap() .entry(segment.to_string()) .or_insert(Value::Object(Map::new())); } // Upsert the new value if let Some(parent_obj) = current_level.as_object_mut() { parent_obj.insert( key_to_insert.to_string(), serde_json::from_str(value).unwrap_or_else(|_| Value::String(value.to_owned())), ); } else { return Err(fmt_err!("final destination is not an object, cannot insert key.")); } } Ok(()) } /// Recursively traverses a `DynSolType` and reorders the fields of any /// `CustomStruct` variants according to the provided `StructDefinitions`. /// /// This is necessary because the EIP-712 resolver sorts struct fields alphabetically, /// but we want to respect the order defined in the Solidity source code. fn reorder_type(ty: DynSolType, struct_defs: &StructDefinitions) -> Result { match ty { DynSolType::CustomStruct { name, prop_names, tuple } => { if let Some(def) = struct_defs.get(&name)? { // The incoming `prop_names` and `tuple` are alphabetically sorted. let type_map: std::collections::HashMap = prop_names.into_iter().zip(tuple).collect(); let mut sorted_props = Vec::with_capacity(def.len()); let mut sorted_tuple = Vec::with_capacity(def.len()); for (field_name, _) in def { sorted_props.push(field_name.clone()); if let Some(field_ty) = type_map.get(field_name) { sorted_tuple.push(reorder_type(field_ty.clone(), struct_defs)?); } else { bail!( "mismatch between struct definition and type description: field '{field_name}' not found in provided type for struct '{name}'" ); } } Ok(DynSolType::CustomStruct { name, prop_names: sorted_props, tuple: sorted_tuple }) } else { // No definition found, so we can't reorder. However, we still reorder its children // in case they have known structs. let new_tuple = tuple .into_iter() .map(|t| reorder_type(t, struct_defs)) .collect::>>()?; Ok(DynSolType::CustomStruct { name, prop_names, tuple: new_tuple }) } } DynSolType::Array(inner) => { Ok(DynSolType::Array(Box::new(reorder_type(*inner, struct_defs)?))) } DynSolType::FixedArray(inner, len) => { Ok(DynSolType::FixedArray(Box::new(reorder_type(*inner, struct_defs)?), len)) } DynSolType::Tuple(inner) => Ok(DynSolType::Tuple( inner.into_iter().map(|t| reorder_type(t, struct_defs)).collect::>>()?, )), _ => Ok(ty), } } #[cfg(test)] mod tests { use super::*; use alloy_primitives::FixedBytes; use foundry_common::fmt::{TypeDefMap, serialize_value_as_json}; use proptest::{arbitrary::any, prop_oneof, strategy::Strategy}; use std::collections::HashSet; fn valid_value(value: &DynSolValue) -> bool { (match value { DynSolValue::String(s) if s == "{}" => false, DynSolValue::Tuple(_) | DynSolValue::CustomStruct { .. } => false, DynSolValue::Array(v) | DynSolValue::FixedArray(v) => v.iter().all(valid_value), _ => true, }) && value.as_type().is_some() } /// [DynSolValue::Bytes] of length 32 and 20 are converted to [DynSolValue::FixedBytes] and /// [DynSolValue::Address] respectively. Thus, we can't distinguish between address and bytes of /// length 20 during decoding. Because of that, there are issues with handling of arrays of /// those types. fn fixup_guessable(value: DynSolValue) -> DynSolValue { match value { DynSolValue::Array(mut v) | DynSolValue::FixedArray(mut v) => { if let Some(DynSolValue::Bytes(_)) = v.first() { v.retain(|v| { let len = v.as_bytes().unwrap().len(); len != 32 && len != 20 }) } DynSolValue::Array(v.into_iter().map(fixup_guessable).collect()) } DynSolValue::FixedBytes(v, _) => DynSolValue::FixedBytes(v, 32), DynSolValue::Bytes(v) if v.len() == 32 => { DynSolValue::FixedBytes(FixedBytes::from_slice(&v), 32) } DynSolValue::Bytes(v) if v.len() == 20 => DynSolValue::Address(Address::from_slice(&v)), _ => value, } } fn guessable_types() -> impl proptest::strategy::Strategy { any::().prop_map(fixup_guessable).prop_filter("invalid value", valid_value) } /// A proptest strategy for generating a (simple) `DynSolValue::CustomStruct` /// and its corresponding `StructDefinitions` object. fn custom_struct_strategy() -> impl Strategy { // Define a strategy for basic field names and values. let field_name_strat = "[a-z]{4,12}"; let field_value_strat = prop_oneof![ any::().prop_map(DynSolValue::Bool), any::().prop_map(|v| DynSolValue::Uint(U256::from(v), 256)), any::<[u8; 20]>().prop_map(Address::from).prop_map(DynSolValue::Address), any::<[u8; 32]>().prop_map(B256::from).prop_map(|b| DynSolValue::FixedBytes(b, 32)), ".*".prop_map(DynSolValue::String), ]; // Combine them to create a list of unique fields that preserve the random order. let fields_strat = proptest::collection::vec((field_name_strat, field_value_strat), 1..8) .prop_map(|fields| { let mut unique_fields = Vec::with_capacity(fields.len()); let mut seen_names = HashSet::new(); for (name, value) in fields { if seen_names.insert(name.clone()) { unique_fields.push((name, value)); } } unique_fields }); // Generate the `CustomStruct` and its definition. ("[A-Z][a-z]{4,8}", fields_strat).prop_map(|(struct_name, fields)| { let (prop_names, tuple): (Vec, Vec) = fields.clone().into_iter().unzip(); let def_fields: Vec<(String, String)> = fields .iter() .map(|(name, value)| (name.clone(), value.as_type().unwrap().to_string())) .collect(); let mut defs_map = TypeDefMap::default(); defs_map.insert(struct_name.clone(), def_fields); (defs_map.into(), DynSolValue::CustomStruct { name: struct_name, prop_names, tuple }) }) } // Tests to ensure that conversion [DynSolValue] -> [serde_json::Value] -> [DynSolValue] proptest::proptest! { #[test] fn test_json_roundtrip_guessed(v in guessable_types()) { let json = serialize_value_as_json(v.clone(), None).unwrap(); let value = json_value_to_token(&json, None).unwrap(); // do additional abi_encode -> abi_decode to avoid zero signed integers getting decoded as unsigned and causing assert_eq to fail. let decoded = v.as_type().unwrap().abi_decode(&value.abi_encode()).unwrap(); assert_eq!(decoded, v); } #[test] fn test_json_roundtrip(v in any::().prop_filter("filter out values without type", |v| v.as_type().is_some())) { let json = serialize_value_as_json(v.clone(), None).unwrap(); let value = parse_json_as(&json, &v.as_type().unwrap()).unwrap(); assert_eq!(value, v); } #[test] fn test_json_roundtrip_with_struct_defs((struct_defs, v) in custom_struct_strategy()) { let json = serialize_value_as_json(v.clone(), Some(&struct_defs)).unwrap(); let sol_type = v.as_type().unwrap(); let parsed_value = parse_json_as(&json, &sol_type).unwrap(); assert_eq!(parsed_value, v); } } #[test] fn test_resolve_type_with_definitions() -> Result<()> { // Define a struct with fields in a specific order (not alphabetical) let mut struct_defs = TypeDefMap::new(); struct_defs.insert( "Apple".to_string(), vec![ ("color".to_string(), "string".to_string()), ("sweetness".to_string(), "uint8".to_string()), ("sourness".to_string(), "uint8".to_string()), ], ); struct_defs.insert( "FruitStall".to_string(), vec![ ("name".to_string(), "string".to_string()), ("apples".to_string(), "Apple[]".to_string()), ], ); // Simulate resolver output: type string, using alphabetical order for fields. let ty_desc = "FruitStall(Apple[] apples,string name)Apple(string color,uint8 sourness,uint8 sweetness)"; // Resolve type and ensure struct definition order is preserved. let ty = resolve_type(ty_desc, Some(&struct_defs.into())).unwrap(); if let DynSolType::CustomStruct { name, prop_names, tuple } = ty { assert_eq!(name, "FruitStall"); assert_eq!(prop_names, vec!["name", "apples"]); assert_eq!(tuple.len(), 2); assert_eq!(tuple[0], DynSolType::String); if let DynSolType::Array(apple_ty_boxed) = &tuple[1] && let DynSolType::CustomStruct { name, prop_names, tuple } = &**apple_ty_boxed { assert_eq!(*name, "Apple"); // Check that the inner struct's fields are also in definition order. assert_eq!(*prop_names, vec!["color", "sweetness", "sourness"]); assert_eq!( *tuple, vec![DynSolType::String, DynSolType::Uint(8), DynSolType::Uint(8)] ); return Ok(()); } } panic!("Expected FruitStall and Apple to be CustomStruct"); } #[test] fn test_resolve_type_without_definitions() -> Result<()> { // Simulate resolver output: type string, using alphabetical order for fields. let ty_desc = "Person(bool active,uint256 age,string name)"; // Resolve the type without providing any struct definitions and ensure that original // (alphabetical) order is unchanged. let ty = resolve_type(ty_desc, None).unwrap(); if let DynSolType::CustomStruct { name, prop_names, tuple } = ty { assert_eq!(name, "Person"); assert_eq!(prop_names, vec!["active", "age", "name"]); assert_eq!(tuple.len(), 3); assert_eq!(tuple, vec![DynSolType::Bool, DynSolType::Uint(256), DynSolType::String]); return Ok(()); } panic!("Expected Person to be CustomStruct"); } #[test] fn test_resolve_type_for_array_of_structs() -> Result<()> { // Define a struct with fields in a specific, non-alphabetical order. let mut struct_defs = TypeDefMap::new(); struct_defs.insert( "Item".to_string(), vec![ ("name".to_string(), "string".to_string()), ("price".to_string(), "uint256".to_string()), ("id".to_string(), "uint256".to_string()), ], ); // Simulate resolver output: type string, using alphabetical order for fields. let ty_desc = "Item(uint256 id,string name,uint256 price)"; // Resolve type and ensure struct definition order is preserved. let ty = resolve_type(ty_desc, Some(&struct_defs.into())).unwrap(); let array_ty = DynSolType::Array(Box::new(ty)); if let DynSolType::Array(item_ty) = array_ty && let DynSolType::CustomStruct { name, prop_names, tuple } = *item_ty { assert_eq!(name, "Item"); assert_eq!(prop_names, vec!["name", "price", "id"]); assert_eq!( tuple, vec![DynSolType::String, DynSolType::Uint(256), DynSolType::Uint(256)] ); return Ok(()); } panic!("Expected CustomStruct in array"); } #[test] fn test_parse_json_missing_field() { // Define a struct with a specific field order. let mut struct_defs = TypeDefMap::new(); struct_defs.insert( "Person".to_string(), vec![ ("name".to_string(), "string".to_string()), ("age".to_string(), "uint256".to_string()), ], ); // JSON missing the "age" field let json_str = r#"{ "name": "Alice" }"#; // Simulate resolver output: type string, using alphabetical order for fields. let type_description = "Person(uint256 age,string name)"; let ty = resolve_type(type_description, Some(&struct_defs.into())).unwrap(); // Now, attempt to parse the incomplete JSON using the ordered type. let json_value: Value = serde_json::from_str(json_str).unwrap(); let result = parse_json_as(&json_value, &ty); // Should fail with a missing field error because `parse_json_map` requires all fields. assert!(result.is_err()); assert!(result.unwrap_err().to_string().contains("field \"age\" not found in JSON object")); } #[test] fn test_serialize_json_with_struct_def_order() { // Define a struct with a specific, non-alphabetical field order. let mut struct_defs = TypeDefMap::new(); struct_defs.insert( "Item".to_string(), vec![ ("name".to_string(), "string".to_string()), ("id".to_string(), "uint256".to_string()), ("active".to_string(), "bool".to_string()), ], ); // Create a DynSolValue instance for the struct. let item_struct = DynSolValue::CustomStruct { name: "Item".to_string(), prop_names: vec!["name".to_string(), "id".to_string(), "active".to_string()], tuple: vec![ DynSolValue::String("Test Item".to_string()), DynSolValue::Uint(U256::from(123), 256), DynSolValue::Bool(true), ], }; // Serialize the value to JSON and verify that the order is preserved. let json_value = serialize_value_as_json(item_struct, Some(&struct_defs.into())).unwrap(); let json_string = serde_json::to_string(&json_value).unwrap(); assert_eq!(json_string, r#"{"name":"Test Item","id":123,"active":true}"#); } #[test] fn test_json_full_cycle_typed_with_struct_defs() { // Define a struct with a specific, non-alphabetical field order. let mut struct_defs = TypeDefMap::new(); struct_defs.insert( "Wallet".to_string(), vec![ ("owner".to_string(), "address".to_string()), ("balance".to_string(), "uint256".to_string()), ("id".to_string(), "bytes32".to_string()), ], ); // Create the "original" DynSolValue instance. let owner_address = Address::from([1; 20]); let wallet_id = B256::from([2; 32]); let original_wallet = DynSolValue::CustomStruct { name: "Wallet".to_string(), prop_names: vec!["owner".to_string(), "balance".to_string(), "id".to_string()], tuple: vec![ DynSolValue::Address(owner_address), DynSolValue::Uint(U256::from(5000), 256), DynSolValue::FixedBytes(wallet_id, 32), ], }; // Serialize it. The resulting JSON should respect the struct definition order. let json_value = serialize_value_as_json(original_wallet.clone(), Some(&struct_defs.clone().into())) .unwrap(); let json_string = serde_json::to_string(&json_value).unwrap(); assert_eq!( json_string, format!(r#"{{"owner":"{owner_address}","balance":5000,"id":"{wallet_id}"}}"#) ); // Resolve the type, which should also respect the struct definition order. let type_description = "Wallet(uint256 balance,bytes32 id,address owner)"; let resolved_type = resolve_type(type_description, Some(&struct_defs.into())).unwrap(); // Parse the JSON using the correctly ordered resolved type. Ensure that it is identical to // the original one. let parsed_value = parse_json_as(&json_value, &resolved_type).unwrap(); assert_eq!(parsed_value, original_wallet); } } ================================================ FILE: crates/cheatcodes/src/lib.rs ================================================ //! # foundry-cheatcodes //! //! Foundry cheatcodes implementations. #![cfg_attr(not(test), warn(unused_crate_dependencies))] #![cfg_attr(docsrs, feature(doc_cfg))] #![allow(elided_lifetimes_in_paths)] // Cheats context uses 3 lifetimes #[macro_use] extern crate foundry_common; #[macro_use] pub extern crate foundry_cheatcodes_spec as spec; #[macro_use] extern crate tracing; use alloy_primitives::Address; use foundry_evm_core::backend::DatabaseExt; use revm::context::{ContextTr, JournalTr}; pub use Vm::ForgeContext; pub use config::CheatsConfig; pub use error::{Error, ErrorKind, Result}; pub use foundry_evm_core::{EthCheatCtx, evm::NestedEvmClosure}; pub use inspector::{ BroadcastKind, BroadcastableTransaction, BroadcastableTransactions, Cheatcodes, CheatcodesExecutor, }; pub use spec::{CheatcodeDef, Vm}; #[macro_use] mod error; mod base64; mod config; mod crypto; mod version; mod env; pub use env::set_execution_context; mod evm; mod fs; mod inspector; pub use inspector::CheatcodeAnalysis; mod json; mod script; pub use script::{Wallets, WalletsInner}; mod string; mod test; pub use test::expect::ExpectedCallTracker; mod toml; mod utils; /// Cheatcode implementation. pub(crate) trait Cheatcode: CheatcodeDef { /// Applies this cheatcode to the given state. /// /// Implement this function if you don't need access to the EVM data. fn apply(&self, state: &mut Cheatcodes) -> Result { let _ = state; unimplemented!("{}", Self::CHEATCODE.func.id) } /// Applies this cheatcode to the given context. /// /// Implement this function if you need access to the EVM data. #[inline(always)] fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { self.apply(ccx.state) } /// Applies this cheatcode to the given context and executor. /// /// Implement this function if you need access to the executor. #[inline(always)] fn apply_full( &self, ccx: &mut CheatsCtxt<'_, CTX>, executor: &mut dyn CheatcodesExecutor, ) -> Result { let _ = executor; self.apply_stateful(ccx) } } /// The cheatcode context. pub struct CheatsCtxt<'a, CTX> { /// The cheatcodes inspector state. pub(crate) state: &'a mut Cheatcodes, /// The EVM context. pub(crate) ecx: &'a mut CTX, /// The original `msg.sender`. pub(crate) caller: Address, /// Gas limit of the current cheatcode call. pub(crate) gas_limit: u64, } impl std::ops::Deref for CheatsCtxt<'_, CTX> { type Target = CTX; #[inline(always)] fn deref(&self) -> &Self::Target { self.ecx } } impl std::ops::DerefMut for CheatsCtxt<'_, CTX> { #[inline(always)] fn deref_mut(&mut self) -> &mut Self::Target { self.ecx } } impl CheatsCtxt<'_, CTX> { pub(crate) fn ensure_not_precompile(&self, address: &Address) -> Result<()> { if self.is_precompile(address) { Err(precompile_error(address)) } else { Ok(()) } } pub(crate) fn is_precompile(&self, address: &Address) -> bool { self.ecx.journal().precompile_addresses().contains(address) } } #[cold] fn precompile_error(address: &Address) -> Error { fmt_err!("cannot use precompile {address} as an argument") } ================================================ FILE: crates/cheatcodes/src/script.rs ================================================ //! Implementations of [`Scripting`](spec::Group::Scripting) cheatcodes. use crate::{Cheatcode, CheatsCtxt, Result, Vm::*, evm::journaled_account}; use alloy_consensus::{SidecarBuilder, SimpleCoder}; use alloy_primitives::{Address, B256, U256, Uint}; use alloy_rpc_types::Authorization; use alloy_signer::SignerSync; use alloy_signer_local::PrivateKeySigner; use alloy_sol_types::SolValue; use foundry_evm_core::backend::DatabaseExt; use foundry_wallets::{WalletSigner, wallet_multi::MultiWallet}; use parking_lot::Mutex; use revm::{ bytecode::Bytecode, context::{Cfg, ContextTr, JournalTr, Transaction}, context_interface::transaction::SignedAuthorization, inspector::JournalExt, primitives::{KECCAK_EMPTY, hardfork::SpecId}, }; use std::sync::Arc; impl Cheatcode for broadcast_0Call { fn apply_stateful>( &self, ccx: &mut CheatsCtxt<'_, CTX>, ) -> Result { let Self {} = self; broadcast(ccx, None, true) } } impl Cheatcode for broadcast_1Call { fn apply_stateful>( &self, ccx: &mut CheatsCtxt<'_, CTX>, ) -> Result { let Self { signer } = self; broadcast(ccx, Some(signer), true) } } impl Cheatcode for broadcast_2Call { fn apply_stateful>( &self, ccx: &mut CheatsCtxt<'_, CTX>, ) -> Result { let Self { privateKey } = self; broadcast_key(ccx, privateKey, true) } } impl Cheatcode for attachDelegation_0Call { fn apply_stateful>( &self, ccx: &mut CheatsCtxt<'_, CTX>, ) -> Result { let Self { signedDelegation } = self; attach_delegation(ccx, signedDelegation, false) } } impl Cheatcode for attachDelegation_1Call { fn apply_stateful>( &self, ccx: &mut CheatsCtxt<'_, CTX>, ) -> Result { let Self { signedDelegation, crossChain } = self; attach_delegation(ccx, signedDelegation, *crossChain) } } impl Cheatcode for signDelegation_0Call { fn apply_stateful>( &self, ccx: &mut CheatsCtxt<'_, CTX>, ) -> Result { let Self { implementation, privateKey } = *self; sign_delegation(ccx, privateKey, implementation, None, false, false) } } impl Cheatcode for signDelegation_1Call { fn apply_stateful>( &self, ccx: &mut CheatsCtxt<'_, CTX>, ) -> Result { let Self { implementation, privateKey, nonce } = *self; sign_delegation(ccx, privateKey, implementation, Some(nonce), false, false) } } impl Cheatcode for signDelegation_2Call { fn apply_stateful>( &self, ccx: &mut CheatsCtxt<'_, CTX>, ) -> 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<'_, CTX>, ) -> Result { let Self { implementation, privateKey } = *self; sign_delegation(ccx, privateKey, implementation, None, false, true) } } impl Cheatcode for signAndAttachDelegation_1Call { fn apply_stateful>( &self, ccx: &mut CheatsCtxt<'_, CTX>, ) -> Result { let Self { implementation, privateKey, nonce } = *self; sign_delegation(ccx, privateKey, implementation, Some(nonce), false, true) } } impl Cheatcode for signAndAttachDelegation_2Call { fn apply_stateful>( &self, ccx: &mut CheatsCtxt<'_, CTX>, ) -> 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<'_, CTX>, 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>( ccx: &mut CheatsCtxt<'_, CTX>, 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 account_nonce = { let authority_acc = ccx.ecx.journal_mut().load_account(signer.address())?; authority_acc.info.nonce }; // Calculate next nonce considering existing active delegations next_delegation_nonce( &ccx.state.active_delegations, signer.address(), &ccx.state.broadcast, account_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.add_delegation(signed_auth); } Ok(SignedDelegation { v: sig.v() as u8, r: sig.r().into(), s: sig.s().into(), nonce, implementation, } .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<'_, CTX>, auth: SignedAuthorization, ) -> Result<()> { let authority = auth.recover_authority().map_err(|e| format!("{e}"))?; let account_nonce = { let authority_acc = ccx.ecx.journal_mut().load_account(authority)?; authority_acc.info.nonce }; let expected_nonce = next_delegation_nonce( &ccx.state.active_delegations, authority, &ccx.state.broadcast, account_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.journal_mut().set_code_with_hash(authority, Bytecode::default(), KECCAK_EMPTY); } else { let bytecode = Bytecode::new_eip7702(*auth.address()); ccx.ecx.journal_mut().set_code(authority, bytecode); } Ok(()) } impl Cheatcode for attachBlobCall { fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { blob } = self; ensure!( ccx.ecx.cfg().spec().into() >= SpecId::CANCUN, "`attachBlob` is not supported before the Cancun hard fork; \ see EIP-4844: https://eips.ethereum.org/EIPS/eip-4844" ); let sidecar: SidecarBuilder = SidecarBuilder::from_slice(blob); let sidecar_variant = if ccx.ecx.cfg().spec().into() < SpecId::OSAKA { sidecar.build_4844().map_err(|e| format!("{e}"))?.into() } else { sidecar.build_7594().map_err(|e| format!("{e}"))?.into() }; ccx.state.active_blob_sidecar = Some(sidecar_variant); Ok(Default::default()) } } impl Cheatcode for startBroadcast_0Call { fn apply_stateful>( &self, ccx: &mut CheatsCtxt<'_, CTX>, ) -> Result { let Self {} = self; broadcast(ccx, None, false) } } impl Cheatcode for startBroadcast_1Call { fn apply_stateful>( &self, ccx: &mut CheatsCtxt<'_, CTX>, ) -> Result { let Self { signer } = self; broadcast(ccx, Some(signer), false) } } impl Cheatcode for startBroadcast_2Call { fn apply_stateful>( &self, ccx: &mut CheatsCtxt<'_, CTX>, ) -> Result { let Self { privateKey } = self; broadcast_key(ccx, privateKey, false) } } impl Cheatcode for stopBroadcastCall { fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self {} = self; let Some(broadcast) = ccx.state.broadcast.take() else { bail!("no broadcast in progress to stop"); }; debug!(target: "cheatcodes", ?broadcast, "stopped"); Ok(Default::default()) } } impl Cheatcode for getWalletsCall { fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let wallets = ccx.state.wallets().signers().unwrap_or_default(); Ok(wallets.abi_encode()) } } #[derive(Clone, Debug, Default)] pub struct Broadcast { /// Address of the transaction origin pub new_origin: Address, /// Original caller pub original_caller: Address, /// Original `tx.origin` pub original_origin: Address, /// Depth of the broadcast pub depth: usize, /// Whether the prank stops by itself after the next call pub single_call: bool, /// Whether `vm.deployCode` cheatcode is used to deploy from code. pub deploy_from_code: bool, } /// Contains context for wallet management. #[derive(Debug)] pub struct WalletsInner { /// All signers in scope of the script. pub multi_wallet: MultiWallet, /// Optional signer provided as `--sender` flag. pub provided_sender: Option
, } /// Cloneable wrapper around [`WalletsInner`]. #[derive(Debug, Clone)] pub struct Wallets { /// Inner data. pub inner: Arc>, } impl Wallets { #[expect(missing_docs)] pub fn new(multi_wallet: MultiWallet, provided_sender: Option
) -> Self { Self { inner: Arc::new(Mutex::new(WalletsInner { multi_wallet, provided_sender })) } } /// Consumes [Wallets] and returns [MultiWallet]. /// /// Panics if [Wallets] is still in use. pub fn into_multi_wallet(self) -> MultiWallet { Arc::into_inner(self.inner) .map(|m| m.into_inner().multi_wallet) .unwrap_or_else(|| panic!("not all instances were dropped")) } /// Locks inner Mutex and adds a signer to the [MultiWallet]. pub fn add_local_signer(&self, wallet: PrivateKeySigner) { self.inner.lock().multi_wallet.add_signer(WalletSigner::Local(wallet)); } /// Locks inner Mutex and returns all signer addresses in the [MultiWallet]. pub fn signers(&self) -> Result> { Ok(self.inner.lock().multi_wallet.signers()?.keys().copied().collect()) } /// Number of signers in the [MultiWallet]. pub fn len(&self) -> usize { let mut inner = self.inner.lock(); inner.multi_wallet.signers().map_or(0, |signers| signers.len()) } /// Whether the [MultiWallet] is empty. pub fn is_empty(&self) -> bool { self.len() == 0 } } /// Sets up broadcasting from a script using `new_origin` as the sender. fn broadcast>( ccx: &mut CheatsCtxt<'_, CTX>, new_origin: Option<&Address>, single_call: bool, ) -> Result { let depth = ccx.ecx.journal().depth(); ensure!( ccx.state.get_prank(depth).is_none(), "you have an active prank; broadcasting and pranks are not compatible" ); ensure!(ccx.state.broadcast.is_none(), "a broadcast is active already"); let mut new_origin = new_origin.copied(); if new_origin.is_none() { let mut wallets = ccx.state.wallets().inner.lock(); if let Some(provided_sender) = wallets.provided_sender { new_origin = Some(provided_sender); } else { let signers = wallets.multi_wallet.signers()?; if signers.len() == 1 { let address = signers.keys().next().unwrap(); new_origin = Some(*address); } } } let new_origin = new_origin.unwrap_or(ccx.ecx.tx().caller()); // Ensure new origin is loaded and touched. let _ = journaled_account(ccx.ecx, new_origin)?; let broadcast = Broadcast { new_origin, original_caller: ccx.caller, original_origin: ccx.ecx.tx().caller(), depth, single_call, deploy_from_code: false, }; debug!(target: "cheatcodes", ?broadcast, "started"); ccx.state.broadcast = Some(broadcast); Ok(Default::default()) } /// Sets up broadcasting from a script with the sender derived from `private_key`. /// Adds this private key to `state`'s `wallets` vector to later be used for signing /// if broadcast is successful. fn broadcast_key>( ccx: &mut CheatsCtxt<'_, CTX>, private_key: &U256, single_call: bool, ) -> Result { let wallet = super::crypto::parse_wallet(private_key)?; let new_origin = wallet.address(); let result = broadcast(ccx, Some(&new_origin), single_call); if result.is_ok() { let wallets = ccx.state.wallets(); wallets.add_local_signer(wallet); } result } ================================================ FILE: crates/cheatcodes/src/string.rs ================================================ //! Implementations of [`String`](spec::Group::String) cheatcodes. use crate::{Cheatcode, Cheatcodes, Result, Vm::*}; use alloy_dyn_abi::{DynSolType, DynSolValue}; use alloy_primitives::{U256, hex}; use alloy_sol_types::SolValue; // address impl Cheatcode for toString_0Call { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { value } = self; Ok(value.to_string().abi_encode()) } } // bytes impl Cheatcode for toString_1Call { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { value } = self; Ok(value.to_string().abi_encode()) } } // bytes32 impl Cheatcode for toString_2Call { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { value } = self; Ok(value.to_string().abi_encode()) } } // bool impl Cheatcode for toString_3Call { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { value } = self; Ok(value.to_string().abi_encode()) } } // uint256 impl Cheatcode for toString_4Call { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { value } = self; Ok(value.to_string().abi_encode()) } } // int256 impl Cheatcode for toString_5Call { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { value } = self; Ok(value.to_string().abi_encode()) } } impl Cheatcode for parseBytesCall { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { stringifiedValue } = self; parse(stringifiedValue, &DynSolType::Bytes) } } impl Cheatcode for parseAddressCall { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { stringifiedValue } = self; parse(stringifiedValue, &DynSolType::Address) } } impl Cheatcode for parseUintCall { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { stringifiedValue } = self; parse(stringifiedValue, &DynSolType::Uint(256)) } } impl Cheatcode for parseIntCall { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { stringifiedValue } = self; parse(stringifiedValue, &DynSolType::Int(256)) } } impl Cheatcode for parseBytes32Call { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { stringifiedValue } = self; parse(stringifiedValue, &DynSolType::FixedBytes(32)) } } impl Cheatcode for parseBoolCall { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { stringifiedValue } = self; parse(stringifiedValue, &DynSolType::Bool) } } impl Cheatcode for toLowercaseCall { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { input } = self; Ok(input.to_lowercase().abi_encode()) } } impl Cheatcode for toUppercaseCall { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { input } = self; Ok(input.to_uppercase().abi_encode()) } } impl Cheatcode for trimCall { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { input } = self; Ok(input.trim().abi_encode()) } } impl Cheatcode for replaceCall { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { input, from, to } = self; Ok(input.replace(from, to).abi_encode()) } } impl Cheatcode for splitCall { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { input, delimiter } = self; let parts: Vec<&str> = input.split(delimiter).collect(); Ok(parts.abi_encode()) } } impl Cheatcode for indexOfCall { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { input, key } = self; Ok(input.find(key).map(U256::from).unwrap_or(U256::MAX).abi_encode()) } } impl Cheatcode for containsCall { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { subject, search } = self; Ok(subject.contains(search).abi_encode()) } } pub(super) fn parse(s: &str, ty: &DynSolType) -> Result { parse_value(s, ty).map(|v| v.abi_encode()) } pub(super) fn parse_array(values: I, ty: &DynSolType) -> Result where I: IntoIterator, S: AsRef, { let mut values = values.into_iter(); match values.next() { Some(first) if !first.as_ref().is_empty() => std::iter::once(first) .chain(values) .map(|s| parse_value(s.as_ref(), ty)) .collect::, _>>() .map(|vec| DynSolValue::Array(vec).abi_encode()), // return the empty encoded Bytes when values is empty or the first element is empty _ => Ok("".abi_encode()), } } pub(super) fn parse_value(s: &str, ty: &DynSolType) -> Result { match ty.coerce_str(s) { Ok(value) => Ok(value), Err(e) => match parse_value_fallback(s, ty) { Some(Ok(value)) => Ok(value), Some(Err(e2)) => Err(fmt_err!("failed parsing {s:?} as type `{ty}`: {e2}")), None => Err(fmt_err!("failed parsing {s:?} as type `{ty}`: {e}")), }, } } // More lenient parsers than `coerce_str`. fn parse_value_fallback(s: &str, ty: &DynSolType) -> Option> { match ty { DynSolType::Bool => { let b = match s { "1" => true, "0" => false, s if s.eq_ignore_ascii_case("true") => true, s if s.eq_ignore_ascii_case("false") => false, _ => return None, }; return Some(Ok(DynSolValue::Bool(b))); } DynSolType::Int(_) | DynSolType::Uint(_) | DynSolType::FixedBytes(_) | DynSolType::Bytes if !s.starts_with("0x") && hex::check_raw(s) => { return Some(Err("missing hex prefix (\"0x\") for hex string")); } _ => {} } None } ================================================ FILE: crates/cheatcodes/src/test/assert.rs ================================================ use crate::{CheatcodesExecutor, CheatsCtxt, EthCheatCtx, Result, Vm::*}; use alloy_primitives::{I256, U256, U512}; use foundry_evm_core::{ abi::console::{format_units_int, format_units_uint}, backend::{DatabaseExt, GLOBAL_FAIL_SLOT}, constants::CHEATCODE_ADDRESS, }; use itertools::Itertools; use revm::context::{ContextTr, JournalTr}; use std::{borrow::Cow, fmt}; const EQ_REL_DELTA_RESOLUTION: U256 = U256::from_limbs([18, 0, 0, 0]); struct ComparisonAssertionError<'a, T> { kind: AssertionKind, left: &'a T, right: &'a T, } #[derive(Clone, Copy)] enum AssertionKind { Eq, Ne, Gt, Ge, Lt, Le, } impl AssertionKind { fn inverse(self) -> Self { match self { Self::Eq => Self::Ne, Self::Ne => Self::Eq, Self::Gt => Self::Le, Self::Ge => Self::Lt, Self::Lt => Self::Ge, Self::Le => Self::Gt, } } fn to_str(self) -> &'static str { match self { Self::Eq => "==", Self::Ne => "!=", Self::Gt => ">", Self::Ge => ">=", Self::Lt => "<", Self::Le => "<=", } } } impl ComparisonAssertionError<'_, T> { fn format_values(&self, f: impl Fn(&T) -> D) -> String { format!("{} {} {}", f(self.left), self.kind.inverse().to_str(), f(self.right)) } } impl ComparisonAssertionError<'_, T> { fn format_for_values(&self) -> String { self.format_values(T::to_string) } } impl ComparisonAssertionError<'_, Vec> { fn format_for_arrays(&self) -> String { self.format_values(|v| format!("[{}]", v.iter().format(", "))) } } impl ComparisonAssertionError<'_, U256> { fn format_with_decimals(&self, decimals: &U256) -> String { self.format_values(|v| format_units_uint(v, decimals)) } } impl ComparisonAssertionError<'_, I256> { fn format_with_decimals(&self, decimals: &U256) -> String { self.format_values(|v| format_units_int(v, decimals)) } } #[derive(thiserror::Error, Debug)] #[error("{left} !~= {right} (max delta: {max_delta}, real delta: {real_delta})")] struct EqAbsAssertionError { left: T, right: T, max_delta: D, real_delta: D, } impl EqAbsAssertionError { fn format_with_decimals(&self, decimals: &U256) -> String { format!( "{} !~= {} (max delta: {}, real delta: {})", format_units_uint(&self.left, decimals), format_units_uint(&self.right, decimals), format_units_uint(&self.max_delta, decimals), format_units_uint(&self.real_delta, decimals), ) } } impl EqAbsAssertionError { fn format_with_decimals(&self, decimals: &U256) -> String { format!( "{} !~= {} (max delta: {}, real delta: {})", format_units_int(&self.left, decimals), format_units_int(&self.right, decimals), format_units_uint(&self.max_delta, decimals), format_units_uint(&self.real_delta, decimals), ) } } fn format_delta_percent(delta: &U256) -> String { format!("{}%", format_units_uint(delta, &(EQ_REL_DELTA_RESOLUTION - U256::from(2)))) } #[derive(Debug)] enum EqRelDelta { Defined(U256), Undefined, } impl fmt::Display for EqRelDelta { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::Defined(delta) => write!(f, "{}", format_delta_percent(delta)), Self::Undefined => write!(f, "undefined"), } } } #[derive(thiserror::Error, Debug)] #[error( "{left} !~= {right} (max delta: {}, real delta: {})", format_delta_percent(max_delta), real_delta )] struct EqRelAssertionFailure { left: T, right: T, max_delta: U256, real_delta: EqRelDelta, } #[derive(thiserror::Error, Debug)] enum EqRelAssertionError { #[error(transparent)] Failure(Box>), #[error("overflow in delta calculation")] Overflow, } impl EqRelAssertionError { fn format_with_decimals(&self, decimals: &U256) -> String { match self { Self::Failure(f) => format!( "{} !~= {} (max delta: {}, real delta: {})", format_units_uint(&f.left, decimals), format_units_uint(&f.right, decimals), format_delta_percent(&f.max_delta), &f.real_delta, ), Self::Overflow => self.to_string(), } } } impl EqRelAssertionError { fn format_with_decimals(&self, decimals: &U256) -> String { match self { Self::Failure(f) => format!( "{} !~= {} (max delta: {}, real delta: {})", format_units_int(&f.left, decimals), format_units_int(&f.right, decimals), format_delta_percent(&f.max_delta), &f.real_delta, ), Self::Overflow => self.to_string(), } } } type ComparisonResult<'a, T> = Result<(), ComparisonAssertionError<'a, T>>; #[cold] fn handle_assertion_result, E>( ccx: &mut CheatsCtxt<'_, CTX>, executor: &mut dyn CheatcodesExecutor, err: E, error_formatter: Option<&dyn Fn(&E) -> String>, error_msg: Option<&str>, ) -> Result { let error_msg = error_msg.unwrap_or("assertion failed"); let msg = if let Some(error_formatter) = error_formatter { Cow::Owned(format!("{error_msg}: {}", error_formatter(&err))) } else { Cow::Borrowed(error_msg) }; handle_assertion_result_mono(ccx, executor, msg) } fn handle_assertion_result_mono>( ccx: &mut CheatsCtxt<'_, CTX>, executor: &mut dyn CheatcodesExecutor, msg: Cow<'_, str>, ) -> Result { if ccx.state.config.assertions_revert { Err(msg.into_owned().into()) } else { executor.console_log(ccx.state, &msg); ccx.ecx.journal_mut().sstore(CHEATCODE_ADDRESS, GLOBAL_FAIL_SLOT, U256::from(1))?; Ok(Default::default()) } } /// Implements [crate::Cheatcode] for pairs of cheatcodes. /// /// Accepts a list of pairs of cheatcodes, where the first cheatcode is the one that doesn't contain /// a custom error message, and the second one contains it at `error` field. /// /// Passed `args` are the common arguments for both cheatcode structs (excluding `error` field). /// /// Macro also accepts an optional closure that formats the error returned by the assertion. macro_rules! impl_assertions { (|$($arg:ident),*| $body:expr, false, $(($no_error:ident, $with_error:ident)),* $(,)?) => { impl_assertions! { @args_tt |($($arg),*)| $body, None, $(($no_error, $with_error)),* } }; (|$($arg:ident),*| $body:expr, $(($no_error:ident, $with_error:ident)),* $(,)?) => { impl_assertions! { @args_tt |($($arg),*)| $body, Some(&ToString::to_string), $(($no_error, $with_error)),* } }; (|$($arg:ident),*| $body:expr, $error_formatter:expr, $(($no_error:ident, $with_error:ident)),* $(,)?) => { impl_assertions! { @args_tt |($($arg),*)| $body, Some(&$error_formatter), $(($no_error, $with_error)),* } }; // We convert args to `tt` and later expand them back into tuple to allow usage of expanded args inside of // each assertion type context. (@args_tt |$args:tt| $body:expr, $error_formatter:expr, $(($no_error:ident, $with_error:ident)),* $(,)?) => { $( impl_assertions! { @impl $no_error, $with_error, $args, $body, $error_formatter } )* }; (@impl $no_error:ident, $with_error:ident, ($($arg:ident),*), $body:expr, $error_formatter:expr) => { impl crate::Cheatcode for $no_error { fn apply_full( &self, ccx: &mut CheatsCtxt<'_, CTX>, executor: &mut dyn CheatcodesExecutor, ) -> Result { let Self { $($arg),* } = self; match $body { Ok(()) => Ok(Default::default()), Err(err) => handle_assertion_result(ccx, executor, err, $error_formatter, None) } } } impl crate::Cheatcode for $with_error { fn apply_full( &self, ccx: &mut CheatsCtxt<'_, CTX>, executor: &mut dyn CheatcodesExecutor, ) -> Result { let Self { $($arg,)* error } = self; match $body { Ok(()) => Ok(Default::default()), Err(err) => handle_assertion_result(ccx, executor, err, $error_formatter, Some(error)) } } } }; } impl_assertions! { |condition| assert_true(*condition), false, (assertTrue_0Call, assertTrue_1Call), } impl_assertions! { |condition| assert_false(*condition), false, (assertFalse_0Call, assertFalse_1Call), } impl_assertions! { |left, right| assert_eq(left, right), ComparisonAssertionError::format_for_values, (assertEq_0Call, assertEq_1Call), (assertEq_2Call, assertEq_3Call), (assertEq_4Call, assertEq_5Call), (assertEq_6Call, assertEq_7Call), (assertEq_8Call, assertEq_9Call), (assertEq_10Call, assertEq_11Call), (assertEq_12Call, assertEq_13Call), } impl_assertions! { |left, right| assert_eq(left, right), ComparisonAssertionError::format_for_arrays, (assertEq_14Call, assertEq_15Call), (assertEq_16Call, assertEq_17Call), (assertEq_18Call, assertEq_19Call), (assertEq_20Call, assertEq_21Call), (assertEq_22Call, assertEq_23Call), (assertEq_24Call, assertEq_25Call), (assertEq_26Call, assertEq_27Call), } impl_assertions! { |left, right, decimals| assert_eq(left, right), |e| e.format_with_decimals(decimals), (assertEqDecimal_0Call, assertEqDecimal_1Call), (assertEqDecimal_2Call, assertEqDecimal_3Call), } impl_assertions! { |left, right| assert_not_eq(left, right), ComparisonAssertionError::format_for_values, (assertNotEq_0Call, assertNotEq_1Call), (assertNotEq_2Call, assertNotEq_3Call), (assertNotEq_4Call, assertNotEq_5Call), (assertNotEq_6Call, assertNotEq_7Call), (assertNotEq_8Call, assertNotEq_9Call), (assertNotEq_10Call, assertNotEq_11Call), (assertNotEq_12Call, assertNotEq_13Call), } impl_assertions! { |left, right| assert_not_eq(left, right), ComparisonAssertionError::format_for_arrays, (assertNotEq_14Call, assertNotEq_15Call), (assertNotEq_16Call, assertNotEq_17Call), (assertNotEq_18Call, assertNotEq_19Call), (assertNotEq_20Call, assertNotEq_21Call), (assertNotEq_22Call, assertNotEq_23Call), (assertNotEq_24Call, assertNotEq_25Call), (assertNotEq_26Call, assertNotEq_27Call), } impl_assertions! { |left, right, decimals| assert_not_eq(left, right), |e| e.format_with_decimals(decimals), (assertNotEqDecimal_0Call, assertNotEqDecimal_1Call), (assertNotEqDecimal_2Call, assertNotEqDecimal_3Call), } impl_assertions! { |left, right| assert_gt(left, right), ComparisonAssertionError::format_for_values, (assertGt_0Call, assertGt_1Call), (assertGt_2Call, assertGt_3Call), } impl_assertions! { |left, right, decimals| assert_gt(left, right), |e| e.format_with_decimals(decimals), (assertGtDecimal_0Call, assertGtDecimal_1Call), (assertGtDecimal_2Call, assertGtDecimal_3Call), } impl_assertions! { |left, right| assert_ge(left, right), ComparisonAssertionError::format_for_values, (assertGe_0Call, assertGe_1Call), (assertGe_2Call, assertGe_3Call), } impl_assertions! { |left, right, decimals| assert_ge(left, right), |e| e.format_with_decimals(decimals), (assertGeDecimal_0Call, assertGeDecimal_1Call), (assertGeDecimal_2Call, assertGeDecimal_3Call), } impl_assertions! { |left, right| assert_lt(left, right), ComparisonAssertionError::format_for_values, (assertLt_0Call, assertLt_1Call), (assertLt_2Call, assertLt_3Call), } impl_assertions! { |left, right, decimals| assert_lt(left, right), |e| e.format_with_decimals(decimals), (assertLtDecimal_0Call, assertLtDecimal_1Call), (assertLtDecimal_2Call, assertLtDecimal_3Call), } impl_assertions! { |left, right| assert_le(left, right), ComparisonAssertionError::format_for_values, (assertLe_0Call, assertLe_1Call), (assertLe_2Call, assertLe_3Call), } impl_assertions! { |left, right, decimals| assert_le(left, right), |e| e.format_with_decimals(decimals), (assertLeDecimal_0Call, assertLeDecimal_1Call), (assertLeDecimal_2Call, assertLeDecimal_3Call), } impl_assertions! { |left, right, maxDelta| uint_assert_approx_eq_abs(*left, *right, *maxDelta), (assertApproxEqAbs_0Call, assertApproxEqAbs_1Call), } impl_assertions! { |left, right, maxDelta| int_assert_approx_eq_abs(*left, *right, *maxDelta), (assertApproxEqAbs_2Call, assertApproxEqAbs_3Call), } impl_assertions! { |left, right, decimals, maxDelta| uint_assert_approx_eq_abs(*left, *right, *maxDelta), |e| e.format_with_decimals(decimals), (assertApproxEqAbsDecimal_0Call, assertApproxEqAbsDecimal_1Call), } impl_assertions! { |left, right, decimals, maxDelta| int_assert_approx_eq_abs(*left, *right, *maxDelta), |e| e.format_with_decimals(decimals), (assertApproxEqAbsDecimal_2Call, assertApproxEqAbsDecimal_3Call), } impl_assertions! { |left, right, maxPercentDelta| uint_assert_approx_eq_rel(*left, *right, *maxPercentDelta), (assertApproxEqRel_0Call, assertApproxEqRel_1Call), } impl_assertions! { |left, right, maxPercentDelta| int_assert_approx_eq_rel(*left, *right, *maxPercentDelta), (assertApproxEqRel_2Call, assertApproxEqRel_3Call), } impl_assertions! { |left, right, decimals, maxPercentDelta| uint_assert_approx_eq_rel(*left, *right, *maxPercentDelta), |e| e.format_with_decimals(decimals), (assertApproxEqRelDecimal_0Call, assertApproxEqRelDecimal_1Call), } impl_assertions! { |left, right, decimals, maxPercentDelta| int_assert_approx_eq_rel(*left, *right, *maxPercentDelta), |e| e.format_with_decimals(decimals), (assertApproxEqRelDecimal_2Call, assertApproxEqRelDecimal_3Call), } fn assert_true(condition: bool) -> Result<(), ()> { if condition { Ok(()) } else { Err(()) } } fn assert_false(condition: bool) -> Result<(), ()> { assert_true(!condition) } fn assert_eq<'a, T: PartialEq>(left: &'a T, right: &'a T) -> ComparisonResult<'a, T> { if left == right { Ok(()) } else { Err(ComparisonAssertionError { kind: AssertionKind::Eq, left, right }) } } fn assert_not_eq<'a, T: PartialEq>(left: &'a T, right: &'a T) -> ComparisonResult<'a, T> { if left != right { Ok(()) } else { Err(ComparisonAssertionError { kind: AssertionKind::Ne, left, right }) } } fn assert_gt<'a, T: PartialOrd>(left: &'a T, right: &'a T) -> ComparisonResult<'a, T> { if left > right { Ok(()) } else { Err(ComparisonAssertionError { kind: AssertionKind::Gt, left, right }) } } fn assert_ge<'a, T: PartialOrd>(left: &'a T, right: &'a T) -> ComparisonResult<'a, T> { if left >= right { Ok(()) } else { Err(ComparisonAssertionError { kind: AssertionKind::Ge, left, right }) } } fn assert_lt<'a, T: PartialOrd>(left: &'a T, right: &'a T) -> ComparisonResult<'a, T> { if left < right { Ok(()) } else { Err(ComparisonAssertionError { kind: AssertionKind::Lt, left, right }) } } fn assert_le<'a, T: PartialOrd>(left: &'a T, right: &'a T) -> ComparisonResult<'a, T> { if left <= right { Ok(()) } else { Err(ComparisonAssertionError { kind: AssertionKind::Le, left, right }) } } fn get_delta_int(left: I256, right: I256) -> U256 { let (left_sign, left_abs) = left.into_sign_and_abs(); let (right_sign, right_abs) = right.into_sign_and_abs(); if left_sign == right_sign { if left_abs > right_abs { left_abs - right_abs } else { right_abs - left_abs } } else { left_abs.wrapping_add(right_abs) } } /// Calculates the relative delta for an absolute difference. /// /// Avoids overflow in the multiplication by using [`U512`] to hold the intermediary result. fn calc_delta_full(abs_diff: U256, right: U256) -> Result> { let delta = U512::from(abs_diff) * U512::from(10).pow(U512::from(EQ_REL_DELTA_RESOLUTION)) / U512::from(right); U256::checked_from_limbs_slice(delta.as_limbs()).ok_or(EqRelAssertionError::Overflow) } fn uint_assert_approx_eq_abs( left: U256, right: U256, max_delta: U256, ) -> Result<(), Box>> { let delta = left.abs_diff(right); if delta <= max_delta { Ok(()) } else { Err(Box::new(EqAbsAssertionError { left, right, max_delta, real_delta: delta })) } } fn int_assert_approx_eq_abs( left: I256, right: I256, max_delta: U256, ) -> Result<(), Box>> { let delta = get_delta_int(left, right); if delta <= max_delta { Ok(()) } else { Err(Box::new(EqAbsAssertionError { left, right, max_delta, real_delta: delta })) } } fn uint_assert_approx_eq_rel( left: U256, right: U256, max_delta: U256, ) -> Result<(), EqRelAssertionError> { if right.is_zero() { if left.is_zero() { return Ok(()); } else { return Err(EqRelAssertionError::Failure(Box::new(EqRelAssertionFailure { left, right, max_delta, real_delta: EqRelDelta::Undefined, }))); }; } let delta = calc_delta_full::(left.abs_diff(right), right)?; if delta <= max_delta { Ok(()) } else { Err(EqRelAssertionError::Failure(Box::new(EqRelAssertionFailure { left, right, max_delta, real_delta: EqRelDelta::Defined(delta), }))) } } fn int_assert_approx_eq_rel( left: I256, right: I256, max_delta: U256, ) -> Result<(), EqRelAssertionError> { if right.is_zero() { if left.is_zero() { return Ok(()); } else { return Err(EqRelAssertionError::Failure(Box::new(EqRelAssertionFailure { left, right, max_delta, real_delta: EqRelDelta::Undefined, }))); } } let delta = calc_delta_full::(get_delta_int(left, right), right.unsigned_abs())?; if delta <= max_delta { Ok(()) } else { Err(EqRelAssertionError::Failure(Box::new(EqRelAssertionFailure { left, right, max_delta, real_delta: EqRelDelta::Defined(delta), }))) } } ================================================ FILE: crates/cheatcodes/src/test/assume.rs ================================================ use crate::{Cheatcode, Cheatcodes, CheatsCtxt, Error, Result}; use alloy_primitives::Address; use foundry_evm_core::constants::MAGIC_ASSUME; use revm::context::{ContextTr, JournalTr}; use spec::Vm::{ PotentialRevert, assumeCall, assumeNoRevert_0Call, assumeNoRevert_1Call, assumeNoRevert_2Call, }; use std::fmt::Debug; #[derive(Clone, Debug)] pub struct AssumeNoRevert { /// The call depth at which the cheatcode was added. 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. pub reasons: Vec, /// Address that reverted the call. pub reverted_by: Option
, } /// Parameters for a single anticipated revert, to be thrown out if encountered. #[derive(Clone, Debug)] pub struct AcceptableRevertParameters { /// The expected revert data returned by the revert pub reason: Vec, /// If true then only the first 4 bytes of expected data returned by the revert are checked. pub partial_match: bool, /// Contract expected to revert next call. pub reverter: Option
, } impl AcceptableRevertParameters { fn from(potential_revert: &PotentialRevert) -> Self { Self { reason: potential_revert.revertData.to_vec(), partial_match: potential_revert.partialMatch, reverter: if potential_revert.reverter == Address::ZERO { None } else { Some(potential_revert.reverter) }, } } } impl Cheatcode for assumeCall { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { condition } = self; if *condition { Ok(Default::default()) } else { Err(Error::from(MAGIC_ASSUME)) } } } impl Cheatcode for assumeNoRevert_0Call { fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { assume_no_revert(ccx.state, ccx.ecx.journal().depth(), vec![]) } } impl Cheatcode for assumeNoRevert_1Call { fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { potentialRevert } = self; assume_no_revert( ccx.state, ccx.ecx.journal().depth(), vec![AcceptableRevertParameters::from(potentialRevert)], ) } } impl Cheatcode for assumeNoRevert_2Call { fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { potentialReverts } = self; assume_no_revert( ccx.state, ccx.ecx.journal().depth(), potentialReverts.iter().map(AcceptableRevertParameters::from).collect(), ) } } fn assume_no_revert( state: &mut Cheatcodes, depth: usize, parameters: Vec, ) -> Result { ensure!( state.assume_no_revert.is_none(), "you must make another external call prior to calling assumeNoRevert again" ); state.assume_no_revert = Some(AssumeNoRevert { depth, reasons: parameters, reverted_by: None }); Ok(Default::default()) } ================================================ FILE: crates/cheatcodes/src/test/expect.rs ================================================ use std::{ collections::VecDeque, fmt::{self, Display}, }; use crate::{Cheatcode, Cheatcodes, CheatsCtxt, Error, Result, Vm::*}; use alloy_dyn_abi::{DynSolValue, EventExt}; use alloy_json_abi::Event; use alloy_primitives::{ Address, Bytes, LogData as RawLog, U256, hex, map::{AddressHashMap, HashMap, hash_map::Entry}, }; use foundry_common::{abi::get_indexed_event, fmt::format_token}; use foundry_evm_traces::DecodedCallLog; use revm::{ context::{ContextTr, JournalTr}, interpreter::{ InstructionResult, Interpreter, InterpreterAction, interpreter_types::LoopControl, }, }; use super::revert_handlers::RevertParameters; /// Tracks the expected calls per address. /// /// For each address, we track the expected calls per call data. We track it in such manner /// so that we don't mix together calldatas that only contain selectors and calldatas that contain /// selector and arguments (partial and full matches). /// /// This then allows us to customize the matching behavior for each call data on the /// `ExpectedCallData` struct and track how many times we've actually seen the call on the second /// element of the tuple. pub type ExpectedCallTracker = HashMap>; #[derive(Clone, Debug)] pub struct ExpectedCallData { /// The expected value sent in the call pub value: Option, /// The expected gas supplied to the call pub gas: Option, /// The expected *minimum* gas supplied to the call pub min_gas: Option, /// The number of times the call is expected to be made. /// If the type of call is `NonCount`, this is the lower bound for the number of calls /// that must be seen. /// If the type of call is `Count`, this is the exact number of calls that must be seen. pub count: u64, /// The type of expected call. pub call_type: ExpectedCallType, } /// The type of expected call. #[derive(Clone, Debug, PartialEq, Eq)] pub enum ExpectedCallType { /// The call is expected to be made at least once. NonCount, /// The exact number of calls expected. Count, } /// The type of expected revert. #[derive(Clone, Debug)] pub enum ExpectedRevertKind { /// Expects revert from the next non-cheatcode call. Default, /// Expects revert from the next cheatcode call. /// /// The `pending_processing` flag is used to track whether we have exited /// `expectCheatcodeRevert` context or not. /// We have to track it to avoid expecting `expectCheatcodeRevert` call to revert itself. Cheatcode { pending_processing: bool }, } #[derive(Clone, Debug)] 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: 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. pub partial_match: bool, /// Contract expected to revert next call. pub reverter: Option
, /// Address that reverted the call. pub reverted_by: Option
, /// Max call depth reached during next call execution. pub max_depth: usize, /// Number of times this revert is expected. pub count: u64, /// Actual number of times this revert has been seen. pub actual_count: u64, } #[derive(Clone, Debug)] pub struct ExpectedEmit { /// The depth at which we expect this emit to have occurred pub depth: usize, /// The log we expect pub log: Option, /// The checks to perform: /// ```text /// ┌───────┬───────┬───────┬───────┬────┐ /// │topic 0│topic 1│topic 2│topic 3│data│ /// └───────┴───────┴───────┴───────┴────┘ /// ``` pub checks: [bool; 5], /// If present, check originating address against this pub address: Option
, /// If present, relax the requirement that topic 0 must be present. This allows anonymous /// events with no indexed topics to be matched. pub anonymous: bool, /// Whether the log was actually found in the subcalls pub found: bool, /// Number of times the log is expected to be emitted pub count: u64, /// Stores mismatch details if a log didn't match pub mismatch_error: Option, } #[derive(Clone, Debug)] pub struct ExpectedCreate { /// The address that deployed the contract pub deployer: Address, /// Runtime bytecode of the contract pub bytecode: Bytes, /// Whether deployed with CREATE or CREATE2 pub create_scheme: CreateScheme, } #[derive(Clone, Debug)] pub enum CreateScheme { Create, Create2, } impl Display for CreateScheme { fn fmt(&self, f: &mut fmt::Formatter) -> std::fmt::Result { match self { Self::Create => write!(f, "CREATE"), Self::Create2 => write!(f, "CREATE2"), } } } 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: Self) -> bool { matches!( (self, create_scheme), (Self::Create, Self::Create) | (Self::Create2, Self::Create2 { .. }) ) } } impl Cheatcode for expectCall_0Call { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { callee, data } = self; expect_call(state, callee, data, None, None, None, 1, ExpectedCallType::NonCount) } } impl Cheatcode for expectCall_1Call { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { callee, data, count } = self; expect_call(state, callee, data, None, None, None, *count, ExpectedCallType::Count) } } impl Cheatcode for expectCall_2Call { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { callee, msgValue, data } = self; expect_call(state, callee, data, Some(msgValue), None, None, 1, ExpectedCallType::NonCount) } } impl Cheatcode for expectCall_3Call { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { callee, msgValue, data, count } = self; expect_call( state, callee, data, Some(msgValue), None, None, *count, ExpectedCallType::Count, ) } } impl Cheatcode for expectCall_4Call { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { callee, msgValue, gas, data } = self; expect_call( state, callee, data, Some(msgValue), Some(*gas), None, 1, ExpectedCallType::NonCount, ) } } impl Cheatcode for expectCall_5Call { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { callee, msgValue, gas, data, count } = self; expect_call( state, callee, data, Some(msgValue), Some(*gas), None, *count, ExpectedCallType::Count, ) } } impl Cheatcode for expectCallMinGas_0Call { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { callee, msgValue, minGas, data } = self; expect_call( state, callee, data, Some(msgValue), None, Some(*minGas), 1, ExpectedCallType::NonCount, ) } } impl Cheatcode for expectCallMinGas_1Call { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { callee, msgValue, minGas, data, count } = self; expect_call( state, callee, data, Some(msgValue), None, Some(*minGas), *count, ExpectedCallType::Count, ) } } impl Cheatcode for expectEmit_0Call { fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { checkTopic1, checkTopic2, checkTopic3, checkData } = *self; expect_emit( ccx.state, ccx.ecx.journal().depth(), [true, checkTopic1, checkTopic2, checkTopic3, checkData], None, false, 1, ) } } impl Cheatcode for expectEmit_1Call { fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { checkTopic1, checkTopic2, checkTopic3, checkData, emitter } = *self; expect_emit( ccx.state, ccx.ecx.journal().depth(), [true, checkTopic1, checkTopic2, checkTopic3, checkData], Some(emitter), false, 1, ) } } impl Cheatcode for expectEmit_2Call { fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self {} = self; expect_emit(ccx.state, ccx.ecx.journal().depth(), [true; 5], None, false, 1) } } impl Cheatcode for expectEmit_3Call { fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { emitter } = *self; expect_emit(ccx.state, ccx.ecx.journal().depth(), [true; 5], Some(emitter), false, 1) } } impl Cheatcode for expectEmit_4Call { fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { checkTopic1, checkTopic2, checkTopic3, checkData, count } = *self; expect_emit( ccx.state, ccx.ecx.journal().depth(), [true, checkTopic1, checkTopic2, checkTopic3, checkData], None, false, count, ) } } impl Cheatcode for expectEmit_5Call { fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { checkTopic1, checkTopic2, checkTopic3, checkData, emitter, count } = *self; expect_emit( ccx.state, ccx.ecx.journal().depth(), [true, checkTopic1, checkTopic2, checkTopic3, checkData], Some(emitter), false, count, ) } } impl Cheatcode for expectEmit_6Call { fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { count } = *self; expect_emit(ccx.state, ccx.ecx.journal().depth(), [true; 5], None, false, count) } } impl Cheatcode for expectEmit_7Call { fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { emitter, count } = *self; expect_emit(ccx.state, ccx.ecx.journal().depth(), [true; 5], Some(emitter), false, count) } } impl Cheatcode for expectEmitAnonymous_0Call { fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { checkTopic0, checkTopic1, checkTopic2, checkTopic3, checkData } = *self; expect_emit( ccx.state, ccx.ecx.journal().depth(), [checkTopic0, checkTopic1, checkTopic2, checkTopic3, checkData], None, true, 1, ) } } impl Cheatcode for expectEmitAnonymous_1Call { fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { checkTopic0, checkTopic1, checkTopic2, checkTopic3, checkData, emitter } = *self; expect_emit( ccx.state, ccx.ecx.journal().depth(), [checkTopic0, checkTopic1, checkTopic2, checkTopic3, checkData], Some(emitter), true, 1, ) } } impl Cheatcode for expectEmitAnonymous_2Call { fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self {} = self; expect_emit(ccx.state, ccx.ecx.journal().depth(), [true; 5], None, true, 1) } } impl Cheatcode for expectEmitAnonymous_3Call { fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { emitter } = *self; expect_emit(ccx.state, ccx.ecx.journal().depth(), [true; 5], Some(emitter), true, 1) } } impl Cheatcode for expectCreateCall { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { bytecode, deployer } = self; expect_create(state, bytecode.clone(), *deployer, CreateScheme::Create) } } impl Cheatcode for expectCreate2Call { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { bytecode, deployer } = self; expect_create(state, bytecode.clone(), *deployer, CreateScheme::Create2) } } impl Cheatcode for expectRevert_0Call { fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self {} = self; expect_revert(ccx.state, None, ccx.ecx.journal().depth(), false, false, None, 1) } } impl Cheatcode for expectRevert_1Call { fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { revertData } = self; expect_revert( ccx.state, Some(revertData.as_ref()), ccx.ecx.journal().depth(), false, false, None, 1, ) } } impl Cheatcode for expectRevert_2Call { fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { revertData } = self; expect_revert(ccx.state, Some(revertData), ccx.ecx.journal().depth(), false, false, None, 1) } } impl Cheatcode for expectRevert_3Call { fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { reverter } = self; expect_revert(ccx.state, None, ccx.ecx.journal().depth(), false, false, Some(*reverter), 1) } } impl Cheatcode for expectRevert_4Call { fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { revertData, reverter } = self; expect_revert( ccx.state, Some(revertData.as_ref()), ccx.ecx.journal().depth(), false, false, Some(*reverter), 1, ) } } impl Cheatcode for expectRevert_5Call { fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { revertData, reverter } = self; expect_revert( ccx.state, Some(revertData), ccx.ecx.journal().depth(), false, false, Some(*reverter), 1, ) } } impl Cheatcode for expectRevert_6Call { fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { count } = self; expect_revert(ccx.state, None, ccx.ecx.journal().depth(), false, false, None, *count) } } impl Cheatcode for expectRevert_7Call { fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { revertData, count } = self; expect_revert( ccx.state, Some(revertData.as_ref()), ccx.ecx.journal().depth(), false, false, None, *count, ) } } impl Cheatcode for expectRevert_8Call { fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { revertData, count } = self; expect_revert( ccx.state, Some(revertData), ccx.ecx.journal().depth(), false, false, None, *count, ) } } impl Cheatcode for expectRevert_9Call { fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { reverter, count } = self; expect_revert( ccx.state, None, ccx.ecx.journal().depth(), false, false, Some(*reverter), *count, ) } } impl Cheatcode for expectRevert_10Call { fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { revertData, reverter, count } = self; expect_revert( ccx.state, Some(revertData.as_ref()), ccx.ecx.journal().depth(), false, false, Some(*reverter), *count, ) } } impl Cheatcode for expectRevert_11Call { fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { revertData, reverter, count } = self; expect_revert( ccx.state, Some(revertData), ccx.ecx.journal().depth(), false, false, Some(*reverter), *count, ) } } impl Cheatcode for expectPartialRevert_0Call { fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { revertData } = self; expect_revert( ccx.state, Some(revertData.as_ref()), ccx.ecx.journal().depth(), false, true, None, 1, ) } } impl Cheatcode for expectPartialRevert_1Call { fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { revertData, reverter } = self; expect_revert( ccx.state, Some(revertData.as_ref()), ccx.ecx.journal().depth(), false, true, Some(*reverter), 1, ) } } impl Cheatcode for _expectCheatcodeRevert_0Call { fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { expect_revert(ccx.state, None, ccx.ecx.journal().depth(), true, false, None, 1) } } impl Cheatcode for _expectCheatcodeRevert_1Call { fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { revertData } = self; expect_revert( ccx.state, Some(revertData.as_ref()), ccx.ecx.journal().depth(), true, false, None, 1, ) } } impl Cheatcode for _expectCheatcodeRevert_2Call { fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { revertData } = self; expect_revert(ccx.state, Some(revertData), ccx.ecx.journal().depth(), true, false, None, 1) } } impl Cheatcode for expectSafeMemoryCall { fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { min, max } = *self; expect_safe_memory(ccx.state, min, max, ccx.ecx.journal().depth().try_into()?) } } impl Cheatcode for stopExpectSafeMemoryCall { fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self {} = self; ccx.state.allowed_mem_writes.remove(&ccx.ecx.journal().depth().try_into()?); Ok(Default::default()) } } impl Cheatcode for expectSafeMemoryCallCall { fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { min, max } = *self; expect_safe_memory(ccx.state, min, max, (ccx.ecx.journal().depth() + 1).try_into()?) } } impl RevertParameters for ExpectedRevert { fn reverter(&self) -> Option
{ self.reverter } fn reason(&self) -> Option<&[u8]> { self.reason.as_ref().map(|b| &***b) } fn partial_match(&self) -> bool { self.partial_match } } /// Handles expected calls specified by the `expectCall` cheatcodes. /// /// It can handle calls in two ways: /// - If the cheatcode was used with a `count` argument, it will expect the call to be made exactly /// `count` times. e.g. `vm.expectCall(address(0xc4f3), abi.encodeWithSelector(0xd34db33f), 4)` /// will expect the call to address(0xc4f3) with selector `0xd34db33f` to be made exactly 4 times. /// If the amount of calls is less or more than 4, the test will fail. Note that the `count` /// argument cannot be overwritten with another `vm.expectCall`. If this is attempted, /// `expectCall` will revert. /// - If the cheatcode was used without a `count` argument, it will expect the call to be made at /// least the amount of times the cheatcode was called. This means that `vm.expectCall` without a /// count argument can be called many times, but cannot be called with a `count` argument after it /// was called without one. If the latter happens, `expectCall` will revert. e.g /// `vm.expectCall(address(0xc4f3), abi.encodeWithSelector(0xd34db33f))` will expect the call to /// address(0xc4f3) and selector `0xd34db33f` to be made at least once. If the amount of calls is /// 0, the test will fail. If the call is made more than once, the test will pass. #[expect(clippy::too_many_arguments)] // It is what it is fn expect_call( state: &mut Cheatcodes, target: &Address, calldata: &Bytes, value: Option<&U256>, mut gas: Option, mut min_gas: Option, count: u64, call_type: ExpectedCallType, ) -> Result { let expecteds = state.expected_calls.entry(*target).or_default(); if let Some(val) = value && *val > U256::ZERO { // If the value of the transaction is non-zero, the EVM adds a call stipend of 2300 gas // to ensure that the basic fallback function can be called. let positive_value_cost_stipend = 2300; if let Some(gas) = &mut gas { *gas += positive_value_cost_stipend; } if let Some(min_gas) = &mut min_gas { *min_gas += positive_value_cost_stipend; } } match call_type { ExpectedCallType::Count => { // Get the expected calls for this target. // In this case, as we're using counted expectCalls, we should not be able to set them // more than once. ensure!( !expecteds.contains_key(calldata), "counted expected calls can only bet set once" ); expecteds.insert( calldata.clone(), (ExpectedCallData { value: value.copied(), gas, min_gas, count, call_type }, 0), ); } ExpectedCallType::NonCount => { // Check if the expected calldata exists. // If it does, increment the count by one as we expect to see it one more time. match expecteds.entry(calldata.clone()) { Entry::Occupied(mut entry) => { let (expected, _) = entry.get_mut(); // Ensure we're not overwriting a counted expectCall. ensure!( expected.call_type == ExpectedCallType::NonCount, "cannot overwrite a counted expectCall with a non-counted expectCall" ); expected.count += 1; } // If it does not exist, then create it. Entry::Vacant(entry) => { entry.insert(( ExpectedCallData { value: value.copied(), gas, min_gas, count, call_type }, 0, )); } } } } Ok(Default::default()) } fn expect_emit( state: &mut Cheatcodes, depth: usize, checks: [bool; 5], address: Option
, anonymous: bool, count: u64, ) -> Result { let expected_emit = ExpectedEmit { depth, checks, address, found: false, log: None, anonymous, count, mismatch_error: None, }; if let Some(found_emit_pos) = state.expected_emits.iter().position(|(emit, _)| emit.found) { // The order of emits already found (back of queue) should not be modified, hence push any // new emit before first found emit. state.expected_emits.insert(found_emit_pos, (expected_emit, Default::default())); } else { // If no expected emits then push new one at the back of queue. state.expected_emits.push_back((expected_emit, Default::default())); } Ok(Default::default()) } pub(crate) fn handle_expect_emit( state: &mut Cheatcodes, log: &alloy_primitives::Log, mut interpreter: Option<&mut Interpreter>, ) -> Option<&'static str> { // This function returns an optional string indicating a failure reason. // If the string is `Some`, it indicates that the expectation failed with the provided reason. let mut should_fail = None; // Fill or check the expected emits. // We expect for emit checks to be filled as they're declared (from oldest to newest), // so we fill them and push them to the back of the queue. // If the user has properly filled all the emits, they'll end up in their original order. // If not, the queue will not be in the order the events will be intended to be filled, // and we'll be able to later detect this and bail. // First, we can return early if all events have been matched. // This allows a contract to arbitrarily emit more events than expected (additive behavior), // as long as all the previous events were matched in the order they were expected to be. if state.expected_emits.iter().all(|(expected, _)| expected.found) { return should_fail; } // Check count=0 expectations against this log - fail immediately if violated for (expected_emit, _) in &state.expected_emits { if expected_emit.count == 0 && !expected_emit.found && let Some(expected_log) = &expected_emit.log && checks_topics_and_data(expected_emit.checks, expected_log, log) // Check revert address && (expected_emit.address.is_none() || expected_emit.address == Some(log.address)) { if let Some(interpreter) = &mut interpreter { // This event was emitted but we expected it NOT to be (count=0) // Fail immediately interpreter.bytecode.set_action(InterpreterAction::new_return( InstructionResult::Revert, Error::encode("log emitted but expected 0 times"), interpreter.gas, )); } else { should_fail = Some("log emitted but expected 0 times"); } return should_fail; } } let should_fill_logs = state.expected_emits.iter().any(|(expected, _)| expected.log.is_none()); let index_to_fill_or_check = if should_fill_logs { // If there's anything to fill, we start with the last event to match in the queue // (without taking into account events already matched). state .expected_emits .iter() .position(|(emit, _)| emit.found) .unwrap_or(state.expected_emits.len()) .saturating_sub(1) } else { // if all expected logs are filled, check any unmatched event // in the declared order, so we start from the front (like a queue). // Skip count=0 expectations as they are handled separately above state.expected_emits.iter().position(|(emit, _)| !emit.found && emit.count > 0).unwrap_or(0) }; // If there are only count=0 expectations left, we can return early if !should_fill_logs && state.expected_emits.iter().all(|(emit, _)| emit.found || emit.count == 0) { return should_fail; } let (mut event_to_fill_or_check, mut count_map) = state .expected_emits .remove(index_to_fill_or_check) .expect("we should have an emit to fill or check"); let Some(expected) = &event_to_fill_or_check.log else { // Unless the caller is trying to match an anonymous event, the first topic must be // filled. if event_to_fill_or_check.anonymous || !log.topics().is_empty() { event_to_fill_or_check.log = Some(log.data.clone()); // If we only filled the expected log then we put it back at the same position. state .expected_emits .insert(index_to_fill_or_check, (event_to_fill_or_check, count_map)); } else if let Some(interpreter) = &mut interpreter { interpreter.bytecode.set_action(InterpreterAction::new_return( InstructionResult::Revert, Error::encode("use vm.expectEmitAnonymous to match anonymous events"), interpreter.gas, )); } else { should_fail = Some("use vm.expectEmitAnonymous to match anonymous events"); } return should_fail; }; // Increment/set `count` for `log.address` and `log.data` match count_map.entry(log.address) { Entry::Occupied(mut entry) => { let log_count_map = entry.get_mut(); log_count_map.insert(&log.data); } Entry::Vacant(entry) => { let mut log_count_map = LogCountMap::new(&event_to_fill_or_check); if log_count_map.satisfies_checks(&log.data) { log_count_map.insert(&log.data); entry.insert(log_count_map); } } } event_to_fill_or_check.found = || -> bool { if !checks_topics_and_data(event_to_fill_or_check.checks, expected, log) { // Store detailed mismatch information // Try to decode the events if we have a signature identifier let (expected_decoded, actual_decoded) = if let Some(signatures_identifier) = state.signatures_identifier() && !event_to_fill_or_check.anonymous { ( decode_event(signatures_identifier, expected), decode_event(signatures_identifier, log), ) } else { (None, None) }; event_to_fill_or_check.mismatch_error = Some(get_emit_mismatch_message( event_to_fill_or_check.checks, expected, log, event_to_fill_or_check.anonymous, expected_decoded.as_ref(), actual_decoded.as_ref(), )); return false; } // Maybe match source address. if event_to_fill_or_check.address.is_some_and(|addr| addr != log.address) { event_to_fill_or_check.mismatch_error = Some(format!( "log emitter mismatch: expected={:#x}, got={:#x}", event_to_fill_or_check.address.unwrap(), log.address )); return false; } let expected_count = event_to_fill_or_check.count; match event_to_fill_or_check.address { Some(emitter) => count_map .get(&emitter) .is_some_and(|log_map| log_map.count(&log.data) >= expected_count), None => count_map .values() .find(|log_map| log_map.satisfies_checks(&log.data)) .is_some_and(|map| map.count(&log.data) >= expected_count), } }(); // If we found the event, we can push it to the back of the queue // and begin expecting the next event. if event_to_fill_or_check.found { state.expected_emits.push_back((event_to_fill_or_check, count_map)); } else { // We did not match this event, so we need to keep waiting for the right one to // appear. state.expected_emits.push_front((event_to_fill_or_check, count_map)); } should_fail } /// Handles expected emits specified by the `expectEmit` cheatcodes. /// /// The second element of the tuple counts the number of times the log has been emitted by a /// particular address pub type ExpectedEmitTracker = VecDeque<(ExpectedEmit, AddressHashMap)>; #[derive(Clone, Debug, Default)] pub struct LogCountMap { checks: [bool; 5], expected_log: RawLog, map: HashMap, } impl LogCountMap { /// Instantiates `LogCountMap`. fn new(expected_emit: &ExpectedEmit) -> Self { Self { checks: expected_emit.checks, expected_log: expected_emit.log.clone().expect("log should be filled here"), map: Default::default(), } } /// Inserts a log into the map and increments the count. /// /// The log must pass all checks against the expected log for the count to increment. /// /// Returns true if the log was inserted and count was incremented. fn insert(&mut self, log: &RawLog) -> bool { // If its already in the map, increment the count without checking. if self.map.contains_key(log) { self.map.entry(log.clone()).and_modify(|c| *c += 1); return true; } if !self.satisfies_checks(log) { return false; } self.map.entry(log.clone()).and_modify(|c| *c += 1).or_insert(1); true } /// Checks the incoming raw log against the expected logs topics and data. fn satisfies_checks(&self, log: &RawLog) -> bool { checks_topics_and_data(self.checks, &self.expected_log, log) } pub fn count(&self, log: &RawLog) -> u64 { if !self.satisfies_checks(log) { return 0; } self.count_unchecked() } pub fn count_unchecked(&self) -> u64 { self.map.values().sum() } } fn expect_create( state: &mut Cheatcodes, bytecode: Bytes, deployer: Address, create_scheme: CreateScheme, ) -> Result { let expected_create = ExpectedCreate { bytecode, deployer, create_scheme }; state.expected_creates.push(expected_create); Ok(Default::default()) } fn expect_revert( state: &mut Cheatcodes, reason: Option<&[u8]>, depth: usize, cheatcode: bool, partial_match: bool, reverter: Option
, count: u64, ) -> Result { ensure!( state.expected_revert.is_none(), "you must call another function prior to expecting a second revert" ); state.expected_revert = Some(ExpectedRevert { reason: reason.map(Bytes::copy_from_slice), depth, kind: if cheatcode { ExpectedRevertKind::Cheatcode { pending_processing: true } } else { ExpectedRevertKind::Default }, partial_match, reverter, reverted_by: None, max_depth: depth, count, actual_count: 0, }); Ok(Default::default()) } fn checks_topics_and_data(checks: [bool; 5], expected: &RawLog, log: &RawLog) -> bool { if log.topics().len() != expected.topics().len() { return false; } // Check topics. if !log .topics() .iter() .enumerate() .filter(|(i, _)| checks[*i]) .all(|(i, topic)| topic == &expected.topics()[i]) { return false; } // Check data if checks[4] && expected.data.as_ref() != log.data.as_ref() { return false; } true } fn decode_event( identifier: &foundry_evm_traces::identifier::SignaturesIdentifier, log: &RawLog, ) -> Option { let topics = log.topics(); if topics.is_empty() { return None; } let t0 = topics[0]; // event sig // Try to identify the event let event = foundry_common::block_on(identifier.identify_event(t0))?; // Check if event already has indexed information from signatures let has_indexed_info = event.inputs.iter().any(|p| p.indexed); // Only use get_indexed_event if the event doesn't have indexing info let indexed_event = if has_indexed_info { event } else { get_indexed_event(event, log) }; // Try to decode the event if let Ok(decoded) = indexed_event.decode_log(log) { let params = reconstruct_params(&indexed_event, &decoded); let decoded_params = params .into_iter() .zip(indexed_event.inputs.iter()) .map(|(param, input)| (input.name.clone(), format_token(¶m))) .collect(); return Some(DecodedCallLog { name: Some(indexed_event.name), params: Some(decoded_params), }); } None } /// Restore the order of the params of a decoded event fn reconstruct_params(event: &Event, decoded: &alloy_dyn_abi::DecodedEvent) -> Vec { let mut indexed = 0; let mut unindexed = 0; let mut inputs = vec![]; for input in &event.inputs { if input.indexed && indexed < decoded.indexed.len() { inputs.push(decoded.indexed[indexed].clone()); indexed += 1; } else if unindexed < decoded.body.len() { inputs.push(decoded.body[unindexed].clone()); unindexed += 1; } } inputs } /// Gets a detailed mismatch message for emit assertions pub(crate) fn get_emit_mismatch_message( checks: [bool; 5], expected: &RawLog, actual: &RawLog, is_anonymous: bool, expected_decoded: Option<&DecodedCallLog>, actual_decoded: Option<&DecodedCallLog>, ) -> String { // Early return for completely different events or incompatible structures // 1. Different number of topics if actual.topics().len() != expected.topics().len() { return name_mismatched_logs(expected_decoded, actual_decoded); } // 2. Different event signatures (for non-anonymous events) if !is_anonymous && checks[0] && (!expected.topics().is_empty() && !actual.topics().is_empty()) && expected.topics()[0] != actual.topics()[0] { return name_mismatched_logs(expected_decoded, actual_decoded); } let expected_data = expected.data.as_ref(); let actual_data = actual.data.as_ref(); // 3. Check data if checks[4] && expected_data != actual_data { // Different lengths or not ABI-encoded if expected_data.len() != actual_data.len() || !expected_data.len().is_multiple_of(32) || expected_data.is_empty() { return name_mismatched_logs(expected_decoded, actual_decoded); } } // expected and actual events are the same, so check individual parameters let mut mismatches = Vec::new(); // Check topics (indexed parameters) for (i, (expected_topic, actual_topic)) in expected.topics().iter().zip(actual.topics().iter()).enumerate() { // Skip topic[0] for non-anonymous events (already checked above) if i == 0 && !is_anonymous { continue; } // Only check if the corresponding check flag is set if i < checks.len() && checks[i] && expected_topic != actual_topic { let param_idx = if is_anonymous { i // For anonymous events, topic[0] is param 0 } else { i - 1 // For regular events, topic[0] is event signature, so topic[1] is param 0 }; mismatches .push(format!("param {param_idx}: expected={expected_topic}, got={actual_topic}")); } } // Check data (non-indexed parameters) if checks[4] && expected_data != actual_data { let num_indexed_params = if is_anonymous { expected.topics().len() } else { expected.topics().len().saturating_sub(1) }; for (i, (expected_chunk, actual_chunk)) in expected_data.chunks(32).zip(actual_data.chunks(32)).enumerate() { if expected_chunk != actual_chunk { let param_idx = num_indexed_params + i; mismatches.push(format!( "param {}: expected={}, got={}", param_idx, hex::encode_prefixed(expected_chunk), hex::encode_prefixed(actual_chunk) )); } } } if mismatches.is_empty() { name_mismatched_logs(expected_decoded, actual_decoded) } else { // Build the error message with event names if available let event_prefix = match (expected_decoded, actual_decoded) { (Some(expected_dec), Some(actual_dec)) if expected_dec.name == actual_dec.name => { format!( "{} param mismatch", expected_dec.name.as_ref().unwrap_or(&"log".to_string()) ) } _ => { if is_anonymous { "anonymous log mismatch".to_string() } else { "log mismatch".to_string() } } }; // Add parameter details if available from decoded events let detailed_mismatches = if let (Some(expected_dec), Some(actual_dec)) = (expected_decoded, actual_decoded) && let (Some(expected_params), Some(actual_params)) = (&expected_dec.params, &actual_dec.params) { mismatches .into_iter() .map(|basic_mismatch| { // Try to find the parameter name and decoded value if let Some(param_idx) = basic_mismatch .split(' ') .nth(1) .and_then(|s| s.trim_end_matches(':').parse::().ok()) && param_idx < expected_params.len() && param_idx < actual_params.len() { let (expected_name, expected_value) = &expected_params[param_idx]; let (_actual_name, actual_value) = &actual_params[param_idx]; let param_name = if !expected_name.is_empty() { expected_name } else { &format!("param{param_idx}") }; return format!( "{param_name}: expected={expected_value}, got={actual_value}", ); } basic_mismatch }) .collect::>() } else { mismatches }; format!("{} at {}", event_prefix, detailed_mismatches.join(", ")) } } /// Formats the generic mismatch message: "log != expected log" to include event names if available fn name_mismatched_logs( expected_decoded: Option<&DecodedCallLog>, actual_decoded: Option<&DecodedCallLog>, ) -> String { let expected_name = expected_decoded.and_then(|d| d.name.as_deref()).unwrap_or("log"); let actual_name = actual_decoded.and_then(|d| d.name.as_deref()).unwrap_or("log"); format!("{actual_name} != expected {expected_name}") } fn expect_safe_memory(state: &mut Cheatcodes, start: u64, end: u64, depth: u64) -> Result { ensure!(start < end, "memory range start ({start}) is greater than end ({end})"); #[expect(clippy::single_range_in_vec_init)] // Wanted behaviour let offsets = state.allowed_mem_writes.entry(depth).or_insert_with(|| vec![0..0x60]); offsets.push(start..end); Ok(Default::default()) } ================================================ FILE: crates/cheatcodes/src/test/revert_handlers.rs ================================================ use crate::{Error, Result}; use alloy_dyn_abi::{DynSolValue, ErrorExt}; use alloy_primitives::{Address, Bytes, address, hex}; use alloy_sol_types::{SolError, SolValue}; use foundry_common::{ContractsByArtifact, abi::get_error}; use foundry_evm_core::decode::RevertDecoder; use revm::interpreter::{InstructionResult, return_ok}; use spec::Vm; use super::{ assume::{AcceptableRevertParameters, AssumeNoRevert}, expect::ExpectedRevert, }; /// For some cheatcodes we may internally change the status of the call, i.e. in `expectRevert`. /// Solidity will see a successful call and attempt to decode the return data. Therefore, we need /// to populate the return with dummy bytes so the decode doesn't fail. /// /// 8192 bytes was arbitrarily chosen because it is long enough for return values up to 256 words in /// size. static DUMMY_CALL_OUTPUT: Bytes = Bytes::from_static(&[0u8; 8192]); /// Same reasoning as [DUMMY_CALL_OUTPUT], but for creates. const DUMMY_CREATE_ADDRESS: Address = address!("0x0000000000000000000000000000000000000001"); fn stringify(data: &[u8]) -> String { if let Ok(s) = String::abi_decode(data) { return s; } if data.is_ascii() { return std::str::from_utf8(data).unwrap().to_owned(); } hex::encode_prefixed(data) } /// Common parameters for expected or assumed reverts. Allows for code reuse. pub(crate) trait RevertParameters { fn reverter(&self) -> Option
; fn reason(&self) -> Option<&[u8]>; fn partial_match(&self) -> bool; } impl RevertParameters for AcceptableRevertParameters { fn reverter(&self) -> Option
{ self.reverter } fn reason(&self) -> Option<&[u8]> { Some(&self.reason) } fn partial_match(&self) -> bool { self.partial_match } } /// Core logic for handling reverts that may or may not be expected (or assumed). fn handle_revert( is_cheatcode: bool, revert_params: &impl RevertParameters, status: InstructionResult, retdata: &Bytes, known_contracts: &Option, reverter: Option<&Address>, ) -> Result<(), Error> { // If expected reverter address is set then check it matches the actual reverter. if let (Some(expected_reverter), Some(&actual_reverter)) = (revert_params.reverter(), reverter) && expected_reverter != actual_reverter { return Err(fmt_err!( "Reverter != expected reverter: {} != {}", actual_reverter, expected_reverter )); } let expected_reason = revert_params.reason(); // If None, accept any revert. let Some(expected_reason) = expected_reason else { return Ok(()); }; if !expected_reason.is_empty() && retdata.is_empty() { bail!("call reverted as expected, but without data"); } let mut actual_revert: Vec = retdata.to_vec(); // Compare only the first 4 bytes if partial match. if revert_params.partial_match() && actual_revert.get(..4) == expected_reason.get(..4) { return Ok(()); } // Try decoding as known errors. actual_revert = decode_revert(actual_revert); if actual_revert == expected_reason || (is_cheatcode && memchr::memmem::find(&actual_revert, expected_reason).is_some()) { return Ok(()); } // If expected reason is `Error(string)` then decode and compare with actual revert. // See if expected_reason.len() >= 4 && let Ok(e) = get_error("Error(string)") && let Ok(dec) = e.decode_error(expected_reason) && let Some(DynSolValue::String(revert_str)) = dec.body.first() && revert_str.as_str() == String::from_utf8_lossy(&actual_revert) { return Ok(()); } let (actual, expected) = if let Some(contracts) = known_contracts { let decoder = RevertDecoder::new().with_abis(contracts.values().map(|c| &c.abi)); ( &decoder.decode(actual_revert.as_slice(), Some(status)), &decoder.decode(expected_reason, Some(status)), ) } else { (&stringify(&actual_revert), &stringify(expected_reason)) }; if expected == actual { return Ok(()); } Err(fmt_err!("Error != expected error: {} != {}", actual, expected)) } pub(crate) fn handle_assume_no_revert( assume_no_revert: &AssumeNoRevert, status: InstructionResult, retdata: &Bytes, known_contracts: &Option, ) -> Result<()> { // if a generic AssumeNoRevert, return Ok(). Otherwise, iterate over acceptable reasons and try // to match against any, otherwise, return an Error with the revert data if assume_no_revert.reasons.is_empty() { Ok(()) } else { assume_no_revert .reasons .iter() .find_map(|reason| { handle_revert( false, reason, status, retdata, known_contracts, assume_no_revert.reverted_by.as_ref(), ) .ok() }) .ok_or_else(|| retdata.clone().into()) } } pub(crate) fn handle_expect_revert( is_cheatcode: bool, is_create: bool, internal_expect_revert: bool, expected_revert: &ExpectedRevert, status: InstructionResult, retdata: Bytes, known_contracts: &Option, ) -> Result<(Option
, Bytes)> { let success_return = || { if is_create { (Some(DUMMY_CREATE_ADDRESS), Bytes::new()) } else { (None, DUMMY_CALL_OUTPUT.clone()) } }; // Check depths if it's not an expect cheatcode call and if internal expect reverts not enabled. if !is_cheatcode && !internal_expect_revert { ensure!( expected_revert.max_depth > expected_revert.depth, "call didn't revert at a lower depth than cheatcode call depth" ); } if expected_revert.count == 0 { // If no specific reason or reverter is expected, we just check if it reverted if expected_revert.reverter.is_none() && expected_revert.reason.is_none() { ensure!( matches!(status, return_ok!()), "call reverted when it was expected not to revert" ); return Ok(success_return()); } // Flags to track if the reason and reverter match. let mut reason_match = expected_revert.reason.as_ref().map(|_| false); let mut reverter_match = expected_revert.reverter.as_ref().map(|_| false); // If we expect no reverts with a specific reason/reverter, but got a revert, // we need to check if it matches our criteria if !matches!(status, return_ok!()) { // We got a revert, but we expected 0 reverts // We need to check if this revert matches our expected criteria // Reverter check if let (Some(expected_reverter), Some(actual_reverter)) = (expected_revert.reverter, expected_revert.reverted_by) && expected_reverter == actual_reverter { reverter_match = Some(true); } // Reason check let expected_reason = expected_revert.reason(); if let Some(expected_reason) = expected_reason { let mut actual_revert: Vec = retdata.to_vec(); actual_revert = decode_revert(actual_revert); if actual_revert == expected_reason { reason_match = Some(true); } } match (reason_match, reverter_match) { (Some(true), Some(true)) => Err(fmt_err!( "expected 0 reverts with reason: {}, from address: {}, but got one", stringify(expected_reason.unwrap_or_default()), expected_revert.reverter.unwrap() )), (Some(true), None) => Err(fmt_err!( "expected 0 reverts with reason: {}, but got one", stringify(expected_reason.unwrap_or_default()) )), (None, Some(true)) => Err(fmt_err!( "expected 0 reverts from address: {}, but got one", expected_revert.reverter.unwrap() )), _ => { // The revert doesn't match our criteria, which means it's a different revert // For expectRevert with count=0, any revert should fail the test let decoded_revert = decode_revert(retdata.to_vec()); // Provide more specific error messages based on what was expected if let Some(reverter) = expected_revert.reverter { if expected_revert.reason.is_some() { Err(fmt_err!( "call reverted with '{}' from {}, but expected 0 reverts with reason '{}' from {}", stringify(&decoded_revert), expected_revert.reverted_by.unwrap_or_default(), stringify(expected_reason.unwrap_or_default()), reverter )) } else { Err(fmt_err!( "call reverted with '{}' from {}, but expected 0 reverts from {}", stringify(&decoded_revert), expected_revert.reverted_by.unwrap_or_default(), reverter )) } } else { Err(fmt_err!( "call reverted with '{}' when it was expected not to revert", stringify(&decoded_revert) )) } } } } else { // No revert occurred, which is what we expected Ok(success_return()) } } else { ensure!(!matches!(status, return_ok!()), "next call did not revert as expected"); handle_revert( is_cheatcode, expected_revert, status, &retdata, known_contracts, expected_revert.reverted_by.as_ref(), )?; Ok(success_return()) } } fn decode_revert(revert: Vec) -> Vec { if matches!( revert.get(..4).map(|s| s.try_into().unwrap()), Some(Vm::CheatcodeError::SELECTOR | alloy_sol_types::Revert::SELECTOR) ) && let Ok(decoded) = Vec::::abi_decode(&revert[4..]) { return decoded; } revert } ================================================ FILE: crates/cheatcodes/src/test.rs ================================================ //! Implementations of [`Testing`](spec::Group::Testing) cheatcodes. use crate::{Cheatcode, Cheatcodes, CheatsCtxt, EthCheatCtx, Result, Vm::*}; use alloy_chains::Chain as AlloyChain; use alloy_primitives::{Address, U256}; use alloy_sol_types::SolValue; use foundry_common::version::SEMVER_VERSION; use foundry_evm_core::constants::MAGIC_SKIP; use revm::context::{ContextTr, JournalTr}; use std::str::FromStr; pub(crate) mod assert; pub(crate) mod assume; pub(crate) mod expect; pub(crate) mod revert_handlers; impl Cheatcode for breakpoint_0Call { fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { char } = self; breakpoint(ccx.state, &ccx.caller, char, true) } } impl Cheatcode for breakpoint_1Call { fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { char, value } = self; breakpoint(ccx.state, &ccx.caller, char, *value) } } impl Cheatcode for getFoundryVersionCall { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self {} = self; Ok(SEMVER_VERSION.abi_encode()) } } impl Cheatcode for rpcUrlCall { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { rpcAlias } = self; let url = state.config.rpc_endpoint(rpcAlias)?.url()?.abi_encode(); Ok(url) } } impl Cheatcode for rpcUrlsCall { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self {} = self; state.config.rpc_urls().map(|urls| urls.abi_encode()) } } impl Cheatcode for rpcUrlStructsCall { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self {} = self; state.config.rpc_urls().map(|urls| urls.abi_encode()) } } impl Cheatcode for sleepCall { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { duration } = self; let sleep_duration = std::time::Duration::from_millis(duration.saturating_to()); std::thread::sleep(sleep_duration); Ok(Default::default()) } } impl Cheatcode for skip_0Call { fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { skipTest } = *self; skip_1Call { skipTest, reason: String::new() }.apply_stateful(ccx) } } impl Cheatcode for skip_1Call { fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { skipTest, reason } = self; 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.journal().depth() <= 1, "`skip` can only be used at test level"); Err([MAGIC_SKIP, reason.as_bytes()].concat().into()) } else { Ok(Default::default()) } } } impl Cheatcode for getChain_0Call { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { chainAlias } = self; get_chain(state, chainAlias) } } impl Cheatcode for getChain_1Call { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { chainId } = self; // Convert the chainId to a string and use the existing get_chain function let chain_id_str = chainId.to_string(); get_chain(state, &chain_id_str) } } /// Adds or removes the given breakpoint to the state. fn breakpoint(state: &mut Cheatcodes, caller: &Address, s: &str, add: bool) -> Result { let mut chars = s.chars(); let (Some(point), None) = (chars.next(), chars.next()) else { bail!("breakpoints must be exactly one character"); }; ensure!(point.is_alphabetic(), "only alphabetic characters are accepted as breakpoints"); if add { state.breakpoints.insert(point, (*caller, state.pc)); } else { state.breakpoints.remove(&point); } Ok(Default::default()) } /// Gets chain information for the given alias. 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 chain_name == chain_id.to_string() { return Err(fmt_err!("invalid chain alias: {chain_alias}")); } // 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: chain_name, chainId: U256::from(chain_id), chainAlias: chain_alias, rpcUrl: rpc_url, }; Ok(chain_struct.abi_encode()) } ================================================ FILE: crates/cheatcodes/src/toml.rs ================================================ //! Implementations of [`Toml`](spec::Group::Toml) cheatcodes. use crate::{ Cheatcode, Cheatcodes, Result, Vm::*, json::{ check_json_key_exists, parse_json, parse_json_coerce, parse_json_keys, resolve_type, upsert_json_value, }, }; use alloy_dyn_abi::DynSolType; use alloy_sol_types::SolValue; use foundry_common::{fmt::StructDefinitions, fs}; use foundry_config::fs_permissions::FsAccessKind; use serde_json::Value as JsonValue; use toml::Value as TomlValue; impl Cheatcode for keyExistsTomlCall { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { toml, key } = self; check_json_key_exists(&toml_to_json_string(toml)?, key) } } impl Cheatcode for parseToml_0Call { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { toml } = self; parse_toml( toml, "$", state.analysis.as_ref().and_then(|analysis| analysis.struct_defs().ok()), ) } } impl Cheatcode for parseToml_1Call { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { toml, key } = self; parse_toml( toml, key, state.analysis.as_ref().and_then(|analysis| analysis.struct_defs().ok()), ) } } impl Cheatcode for parseTomlUintCall { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { toml, key } = self; parse_toml_coerce(toml, key, &DynSolType::Uint(256)) } } impl Cheatcode for parseTomlUintArrayCall { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { toml, key } = self; parse_toml_coerce(toml, key, &DynSolType::Array(Box::new(DynSolType::Uint(256)))) } } impl Cheatcode for parseTomlIntCall { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { toml, key } = self; parse_toml_coerce(toml, key, &DynSolType::Int(256)) } } impl Cheatcode for parseTomlIntArrayCall { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { toml, key } = self; parse_toml_coerce(toml, key, &DynSolType::Array(Box::new(DynSolType::Int(256)))) } } impl Cheatcode for parseTomlBoolCall { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { toml, key } = self; parse_toml_coerce(toml, key, &DynSolType::Bool) } } impl Cheatcode for parseTomlBoolArrayCall { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { toml, key } = self; parse_toml_coerce(toml, key, &DynSolType::Array(Box::new(DynSolType::Bool))) } } impl Cheatcode for parseTomlAddressCall { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { toml, key } = self; parse_toml_coerce(toml, key, &DynSolType::Address) } } impl Cheatcode for parseTomlAddressArrayCall { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { toml, key } = self; parse_toml_coerce(toml, key, &DynSolType::Array(Box::new(DynSolType::Address))) } } impl Cheatcode for parseTomlStringCall { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { toml, key } = self; parse_toml_coerce(toml, key, &DynSolType::String) } } impl Cheatcode for parseTomlStringArrayCall { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { toml, key } = self; parse_toml_coerce(toml, key, &DynSolType::Array(Box::new(DynSolType::String))) } } impl Cheatcode for parseTomlBytesCall { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { toml, key } = self; parse_toml_coerce(toml, key, &DynSolType::Bytes) } } impl Cheatcode for parseTomlBytesArrayCall { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { toml, key } = self; parse_toml_coerce(toml, key, &DynSolType::Array(Box::new(DynSolType::Bytes))) } } impl Cheatcode for parseTomlBytes32Call { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { toml, key } = self; parse_toml_coerce(toml, key, &DynSolType::FixedBytes(32)) } } impl Cheatcode for parseTomlBytes32ArrayCall { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { toml, key } = self; parse_toml_coerce(toml, key, &DynSolType::Array(Box::new(DynSolType::FixedBytes(32)))) } } impl Cheatcode for parseTomlType_0Call { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { toml, typeDescription } = self; parse_toml_coerce( toml, "$", &resolve_type( typeDescription, state.analysis.as_ref().and_then(|analysis| analysis.struct_defs().ok()), )?, ) .map(|v| v.abi_encode()) } } impl Cheatcode for parseTomlType_1Call { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { toml, key, typeDescription } = self; parse_toml_coerce( toml, key, &resolve_type( typeDescription, state.analysis.as_ref().and_then(|analysis| analysis.struct_defs().ok()), )?, ) .map(|v| v.abi_encode()) } } impl Cheatcode for parseTomlTypeArrayCall { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { toml, key, typeDescription } = self; let ty = resolve_type( typeDescription, state.analysis.as_ref().and_then(|analysis| analysis.struct_defs().ok()), )?; parse_toml_coerce(toml, key, &DynSolType::Array(Box::new(ty))).map(|v| v.abi_encode()) } } impl Cheatcode for parseTomlKeysCall { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { toml, key } = self; parse_toml_keys(toml, key) } } impl Cheatcode for writeToml_0Call { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { json, path } = self; let value = serde_json::from_str(json).unwrap_or_else(|_| JsonValue::String(json.to_owned())); let toml_string = format_json_to_toml(value)?; super::fs::write_file(state, path.as_ref(), toml_string.as_bytes()) } } impl Cheatcode for writeToml_1Call { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { json: value, path, valueKey } = self; // Read and parse the TOML file. // If the file doesn't exist, start with an empty object so the file is created. let data_path = state.config.ensure_path_allowed(path, FsAccessKind::Read)?; let mut json_data: JsonValue = if data_path.exists() { let toml_data = fs::locked_read_to_string(&data_path)?; toml::from_str(&toml_data).map_err(|e| fmt_err!("failed parsing TOML: {e}"))? } else { JsonValue::Object(Default::default()) }; upsert_json_value(&mut json_data, value, valueKey)?; // Serialize back to TOML and write the updated content back to the file let toml_string = format_json_to_toml(json_data)?; super::fs::write_file(state, path.as_ref(), toml_string.as_bytes()) } } /// Parse fn parse_toml_str(toml: &str) -> Result { toml::from_str(toml).map_err(|e| fmt_err!("failed parsing TOML: {e}")) } /// Parse a TOML string and return the value at the given path. fn parse_toml(toml: &str, key: &str, struct_defs: Option<&StructDefinitions>) -> Result { parse_json(&toml_to_json_string(toml)?, key, struct_defs) } /// Parse a TOML string and return the value at the given path, coercing it to the given type. fn parse_toml_coerce(toml: &str, key: &str, ty: &DynSolType) -> Result { parse_json_coerce(&toml_to_json_string(toml)?, key, ty) } /// Parse a TOML string and return an array of all keys at the given path. fn parse_toml_keys(toml: &str, key: &str) -> Result { parse_json_keys(&toml_to_json_string(toml)?, key) } /// Convert a TOML string to a JSON string. fn toml_to_json_string(toml: &str) -> Result { let toml = parse_toml_str(toml)?; let json = toml_to_json_value(toml); serde_json::to_string(&json).map_err(|e| fmt_err!("failed to serialize JSON: {e}")) } /// Format a JSON value to a TOML pretty string. fn format_json_to_toml(json: JsonValue) -> Result { let toml = json_to_toml_value(json); toml::to_string_pretty(&toml).map_err(|e| fmt_err!("failed to serialize TOML: {e}")) } /// Convert a TOML value to a JSON value. pub(super) fn toml_to_json_value(toml: TomlValue) -> JsonValue { match toml { TomlValue::String(s) => match s.as_str() { "null" => JsonValue::Null, _ => JsonValue::String(s), }, TomlValue::Integer(i) => JsonValue::Number(i.into()), TomlValue::Float(f) => match serde_json::Number::from_f64(f) { Some(n) => JsonValue::Number(n), None => JsonValue::String(f.to_string()), }, TomlValue::Boolean(b) => JsonValue::Bool(b), TomlValue::Array(a) => JsonValue::Array(a.into_iter().map(toml_to_json_value).collect()), TomlValue::Table(t) => { JsonValue::Object(t.into_iter().map(|(k, v)| (k, toml_to_json_value(v))).collect()) } TomlValue::Datetime(d) => JsonValue::String(d.to_string()), } } /// Convert a JSON value to a TOML value. fn json_to_toml_value(json: JsonValue) -> TomlValue { match json { JsonValue::String(s) => TomlValue::String(s), JsonValue::Number(n) => match n.as_i64() { Some(i) => TomlValue::Integer(i), None => match n.as_f64() { Some(f) => TomlValue::Float(f), None => TomlValue::String(n.to_string()), }, }, JsonValue::Bool(b) => TomlValue::Boolean(b), JsonValue::Array(a) => TomlValue::Array(a.into_iter().map(json_to_toml_value).collect()), JsonValue::Object(o) => { TomlValue::Table(o.into_iter().map(|(k, v)| (k, json_to_toml_value(v))).collect()) } JsonValue::Null => TomlValue::String("null".to_string()), } } ================================================ FILE: crates/cheatcodes/src/utils.rs ================================================ //! Implementations of [`Utilities`](spec::Group::Utilities) cheatcodes. use crate::{Cheatcode, Cheatcodes, CheatcodesExecutor, CheatsCtxt, Result, Vm::*}; use alloy_dyn_abi::{DynSolType, DynSolValue, Resolver, TypedData, eip712_parser::EncodeType}; use alloy_ens::namehash; use alloy_primitives::{B64, Bytes, I256, U256, aliases::B32, keccak256, map::HashMap}; use alloy_rlp::{Decodable, Encodable}; use alloy_sol_types::SolValue; use foundry_common::{TYPE_BINDING_PREFIX, fs}; use foundry_config::fs_permissions::FsAccessKind; use foundry_evm_core::constants::DEFAULT_CREATE2_DEPLOYER; use foundry_evm_fuzz::strategies::BoundMutator; use proptest::prelude::Strategy; use rand::{Rng, RngCore, seq::SliceRandom}; use revm::{ context::{ContextTr, JournalTr}, inspector::JournalExt, }; use std::path::PathBuf; /// Contains locations of traces ignored via cheatcodes. /// /// The way we identify location in traces is by (node_idx, item_idx) tuple where node_idx is an /// index of a call trace node, and item_idx is a value between 0 and `node.ordering.len()` where i /// represents point after ith item, and 0 represents the beginning of the node trace. #[derive(Debug, Default, Clone)] pub struct IgnoredTraces { /// Mapping from (start_node_idx, start_item_idx) to (end_node_idx, end_item_idx) representing /// ranges of trace nodes to ignore. pub ignored: HashMap<(usize, usize), (usize, usize)>, /// Keeps track of (start_node_idx, start_item_idx) of the last `vm.pauseTracing` call. pub last_pause_call: Option<(usize, usize)>, } impl Cheatcode for labelCall { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { account, newLabel } = self; state.labels.insert(*account, newLabel.clone()); Ok(Default::default()) } } impl Cheatcode for getLabelCall { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { account } = self; Ok(match state.labels.get(account) { Some(label) => label.abi_encode(), None => format!("unlabeled:{account}").abi_encode(), }) } } impl Cheatcode for computeCreateAddressCall { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { nonce, deployer } = self; ensure!(*nonce <= U256::from(u64::MAX), "nonce must be less than 2^64"); Ok(deployer.create(nonce.to()).abi_encode()) } } impl Cheatcode for computeCreate2Address_0Call { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { salt, initCodeHash, deployer } = self; Ok(deployer.create2(salt, initCodeHash).abi_encode()) } } impl Cheatcode for computeCreate2Address_1Call { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { salt, initCodeHash } = self; Ok(DEFAULT_CREATE2_DEPLOYER.create2(salt, initCodeHash).abi_encode()) } } impl Cheatcode for ensNamehashCall { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { name } = self; Ok(namehash(name).abi_encode()) } } impl Cheatcode for bound_0Call { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { current, min, max } = *self; let Some(mutated) = U256::bound(current, min, max, state.test_runner()) else { bail!("cannot bound {current} in [{min}, {max}] range") }; Ok(mutated.abi_encode()) } } impl Cheatcode for bound_1Call { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { current, min, max } = *self; let Some(mutated) = I256::bound(current, min, max, state.test_runner()) else { bail!("cannot bound {current} in [{min}, {max}] range") }; Ok(mutated.abi_encode()) } } impl Cheatcode for randomUint_0Call { fn apply(&self, state: &mut Cheatcodes) -> Result { random_uint(state, None, None) } } impl Cheatcode for randomUint_1Call { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { min, max } = *self; random_uint(state, None, Some((min, max))) } } impl Cheatcode for randomUint_2Call { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { bits } = *self; random_uint(state, Some(bits), None) } } impl Cheatcode for randomAddressCall { fn apply(&self, state: &mut Cheatcodes) -> Result { Ok(DynSolValue::type_strategy(&DynSolType::Address) .new_tree(state.test_runner()) .unwrap() .current() .abi_encode()) } } impl Cheatcode for randomInt_0Call { fn apply(&self, state: &mut Cheatcodes) -> Result { random_int(state, None) } } impl Cheatcode for randomInt_1Call { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { bits } = *self; random_int(state, Some(bits)) } } impl Cheatcode for randomBoolCall { fn apply(&self, state: &mut Cheatcodes) -> Result { let rand_bool: bool = state.rng().random(); Ok(rand_bool.abi_encode()) } } impl Cheatcode for randomBytesCall { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { len } = *self; ensure!( len <= U256::from(usize::MAX), format!("bytes length cannot exceed {}", usize::MAX) ); let mut bytes = vec![0u8; len.to::()]; state.rng().fill_bytes(&mut bytes); Ok(bytes.abi_encode()) } } impl Cheatcode for randomBytes4Call { fn apply(&self, state: &mut Cheatcodes) -> Result { let rand_u32 = state.rng().next_u32(); Ok(B32::from(rand_u32).abi_encode()) } } impl Cheatcode for randomBytes8Call { fn apply(&self, state: &mut Cheatcodes) -> Result { let rand_u64 = state.rng().next_u64(); Ok(B64::from(rand_u64).abi_encode()) } } impl Cheatcode for pauseTracingCall { fn apply_full( &self, ccx: &mut CheatsCtxt<'_, CTX>, executor: &mut dyn CheatcodesExecutor, ) -> Result { let Some(tracer) = executor.tracing_inspector() else { // No tracer -> nothing to pause return Ok(Default::default()); }; // If paused earlier, ignore the call if ccx.state.ignored_traces.last_pause_call.is_some() { return Ok(Default::default()); } let cur_node = &tracer.traces().nodes().last().expect("no trace nodes"); ccx.state.ignored_traces.last_pause_call = Some((cur_node.idx, cur_node.ordering.len())); Ok(Default::default()) } } impl Cheatcode for resumeTracingCall { fn apply_full( &self, ccx: &mut CheatsCtxt<'_, CTX>, executor: &mut dyn CheatcodesExecutor, ) -> Result { let Some(tracer) = executor.tracing_inspector() else { // No tracer -> nothing to unpause return Ok(Default::default()); }; let Some(start) = ccx.state.ignored_traces.last_pause_call.take() else { // Nothing to unpause return Ok(Default::default()); }; let node = &tracer.traces().nodes().last().expect("no trace nodes"); ccx.state.ignored_traces.ignored.insert(start, (node.idx, node.ordering.len())); Ok(Default::default()) } } impl Cheatcode for interceptInitcodeCall { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self {} = self; if !state.intercept_next_create_call { state.intercept_next_create_call = true; } else { bail!("vm.interceptInitcode() has already been called") } Ok(Default::default()) } } impl Cheatcode for setArbitraryStorage_0Call { fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { target } = self; ccx.state.arbitrary_storage().mark_arbitrary(target, false); Ok(Default::default()) } } impl Cheatcode for setArbitraryStorage_1Call { fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result { let Self { target, overwrite } = self; ccx.state.arbitrary_storage().mark_arbitrary(target, *overwrite); Ok(Default::default()) } } impl Cheatcode for copyStorageCall { fn apply_stateful>( &self, ccx: &mut CheatsCtxt<'_, CTX>, ) -> Result { let Self { from, to } = self; ensure!( !ccx.state.has_arbitrary_storage(to), "target address cannot have arbitrary storage" ); if let Ok(from_account) = ccx.ecx.journal_mut().load_account(*from) { let from_storage = from_account.storage.clone(); if ccx.ecx.journal_mut().load_account(*to).is_ok() { // SAFETY: We ensured the account was already loaded. ccx.ecx.journal_mut().evm_state_mut().get_mut(to).unwrap().storage = from_storage; if let Some(arbitrary_storage) = &mut ccx.state.arbitrary_storage { arbitrary_storage.mark_copy(from, to); } } } Ok(Default::default()) } } impl Cheatcode for sortCall { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { array } = self; let mut sorted_values = array.clone(); sorted_values.sort(); Ok(sorted_values.abi_encode()) } } impl Cheatcode for shuffleCall { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { array } = self; let mut shuffled_values = array.clone(); let rng = state.rng(); shuffled_values.shuffle(rng); Ok(shuffled_values.abi_encode()) } } impl Cheatcode for setSeedCall { fn apply_stateful(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> 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 { if let Some(bits) = bits { // Generate random with specified bits. ensure!(bits <= U256::from(256), "number of bits cannot exceed 256"); return Ok(DynSolValue::type_strategy(&DynSolType::Uint(bits.to::())) .new_tree(state.test_runner()) .unwrap() .current() .abi_encode()); } if let Some((min, max)) = bounds { 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().random(); if exclusive_modulo != U256::MAX { let inclusive_modulo = exclusive_modulo + U256::from(1); random_number %= inclusive_modulo; } random_number += min; return Ok(random_number.abi_encode()); } // Generate random `uint256` value. Ok(DynSolValue::type_strategy(&DynSolType::Uint(256)) .new_tree(state.test_runner()) .unwrap() .current() .abi_encode()) } /// Helper to generate a random `int` value (with given bits if specified) from type strategy. fn random_int(state: &mut Cheatcodes, bits: Option) -> Result { let no_bits = bits.unwrap_or(U256::from(256)); ensure!(no_bits <= U256::from(256), "number of bits cannot exceed 256"); Ok(DynSolValue::type_strategy(&DynSolType::Int(no_bits.to::())) .new_tree(state.test_runner()) .unwrap() .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()) } impl Cheatcode for toRlpCall { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { data } = self; let mut buf = Vec::new(); data.encode(&mut buf); Ok(Bytes::from(buf).abi_encode()) } } impl Cheatcode for fromRlpCall { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { rlp } = self; let decoded: Vec = Vec::::decode(&mut rlp.as_ref()) .map_err(|e| fmt_err!("Failed to decode RLP: {e}"))?; Ok(decoded.abi_encode()) } } ================================================ FILE: crates/cheatcodes/src/version.rs ================================================ use crate::{Cheatcode, Cheatcodes, Result, Vm::*}; use alloy_sol_types::SolValue; use foundry_common::version::SEMVER_VERSION; use semver::Version; use std::cmp::Ordering; impl Cheatcode for foundryVersionCmpCall { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { version } = self; foundry_version_cmp(version).map(|cmp| (cmp as i8).abi_encode()) } } impl Cheatcode for foundryVersionAtLeastCall { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { version } = self; foundry_version_cmp(version).map(|cmp| cmp.is_ge().abi_encode()) } } fn foundry_version_cmp(version: &str) -> Result { version_cmp(SEMVER_VERSION.split('-').next().unwrap(), version) } fn version_cmp(version_a: &str, version_b: &str) -> Result { let version_a = parse_version(version_a)?; let version_b = parse_version(version_b)?; Ok(version_a.cmp(&version_b)) } fn parse_version(version: &str) -> Result { let version = Version::parse(version).map_err(|e| fmt_err!("invalid version `{version}`: {e}"))?; if !version.pre.is_empty() { return Err(fmt_err!( "invalid version `{version}`: pre-release versions are not supported" )); } if !version.build.is_empty() { return Err(fmt_err!("invalid version `{version}`: build metadata is not supported")); } Ok(version) } ================================================ FILE: crates/chisel/Cargo.toml ================================================ [package] name = "chisel" description = "Fast, utilitarian, and verbose Solidity REPL" authors.workspace = true version.workspace = true edition.workspace = true rust-version.workspace = true license.workspace = true homepage.workspace = true repository.workspace = true [lints] workspace = true [[bin]] name = "chisel" path = "bin/main.rs" [dependencies] # forge forge-doc.workspace = true forge-fmt.workspace = true foundry-cli.workspace = true foundry-common.workspace = true foundry-compilers.workspace = true foundry-config.workspace = true foundry-evm.workspace = true tempfile.workspace = true solar.workspace = true alloy-dyn-abi = { workspace = true, features = ["arbitrary"] } alloy-primitives = { workspace = true, features = [ "serde", "getrandom", "arbitrary", "rlp", ] } alloy-json-abi.workspace = true clap = { version = "4", features = ["derive", "env", "wrap_help"] } dirs.workspace = true eyre.workspace = true reqwest.workspace = true rustyline = "17" itertools.workspace = true semver.workspace = true serde_json.workspace = true serde.workspace = true solang-parser.workspace = true time = { version = "0.3", features = ["formatting"] } yansi.workspace = true tracing.workspace = true walkdir.workspace = true [dev-dependencies] tracing-subscriber.workspace = true # REPL tests only work on Unix. [target.'cfg(unix)'.dev-dependencies] foundry-test-utils.workspace = true rexpect = "0.6" [features] default = ["jemalloc"] asm-keccak = ["alloy-primitives/asm-keccak"] jemalloc = ["foundry-cli/jemalloc"] mimalloc = ["foundry-cli/mimalloc"] tracy-allocator = ["foundry-cli/tracy-allocator"] ================================================ FILE: crates/chisel/assets/preview.tape ================================================ # VHS File source # https://github.com/charmbracelet/vhs # # Output: # Output .gif Create a GIF output at the given # Output .mp4 Create an MP4 output at the given # Output .webm Create a WebM output at the given # # Settings: # Set FontSize Set the font size of the terminal # Set FontFamily Set the font family of the terminal # Set Height Set the height of the terminal # Set Width Set the width of the terminal # Set LetterSpacing Set the font letter spacing (tracking) # Set LineHeight Set the font line height # Set Theme Set the theme of the terminal (JSON) # Set Padding Set the padding of the terminal # Set Framerate Set the framerate of the recording # Set PlaybackSpeed Set the playback speed of the recording # # Sleep: # Sleep